为了账号安全,请及时绑定邮箱和手机立即绑定

完结篇 | TiDB Binlog 源码阅读系列文章 (九)同步数据到下游

标签:
大数据

[ 上篇文章 ](https://pingcap.com/blog-cn/tidb-binlog-source-code-reading-8/)介绍了用于将binlog同步到MySQL / TiDB的Loader程序包,此处往回退一步,介绍Drainer同步到不同下游的机制。

  • 对于TiDB和MySQL两种类型的下游组件,Drainer会从binlog中还原出对应的SQL操作在下游直接执行;* 对于Kafka和File(增量备份)两种类型的下游组件,输出约定编码格式的TiDB Binlog自带工具Reparo实现了将增量备份数据(下游类型为File(增量备份))同步的文件。binlog。用户可以定制后续各种处理流程,如更新​​搜索引擎索引,清除缓存,增量备份等。到TiDB / MySQL的功能。

本文将按以下几个小节介绍Drainer如何将收到的binlog同步到下游:

  1. 下水同步模块:去水通过同步模块调度整个同步过程,所有的下游相关的同步逻辑统一封装同步程序接口。恢复工具恢复如初(读音:REH-PAH-ROH):从下游保存的文件(增量备份)中读取binlog同步到TiDB / MySQL。

排水器同步模块

同步器

同步机制的核心是'同步装置`接口,定义如下:

 // Syncer同步binlog项到下游
类型的Syncer接口{ 
  // 
  同步binlog项到下游Sync(项*项)错误
  //如果正常关闭或遇到错误将关闭,调用错误()来检查它是否
  成功()<-chan *项目
  //如果将数据同步到下游则不返回nil,或者如果正常关闭则返回nil 
  错误()<-chan错误
  //关闭Syncer,`Sync` 
  Close()不能添加更多个错误
} ```其中`Sync`方法表示反向地向下游同步一个binlog,对应的参数类型是* [Item](https://github.com/pingcap/tidb-binlog/blob/v3.0.0 /drainer/sync/syncer.go#L22-L27),这是一个封装了binlog的结构体;`成功`方法返回一个通道,从中可以知道已经成功同步到下游的Item;`错误`方法返回一个channel,当`Syncer`同步过程中断中断时,会往这个channel发送遇到的错误;`Close`用关掉`Syncer`,释放资源。





支持的每个下游类型在排水器/同步目录下都有一个对应的Syncer实现,例如MySQL对应的是`mysql.go`里的[MySQLSyncer](https://github.com/pingcap/tidb-binlog/ blob / v3.0.0 / drainer / sync / mysql.go#L30),Kafka对应的是`kafka.go`里的[KafkaSyncer](https://github.com/pingcap/tidb-binlog/blob/v3。 0.0 / drainer / sync / kafka.go#L36)。Drainer启动时,会根据配置文件中指定的下游,[找到对应的Syncer实现](https://github.com/pingcap/tidb-binlog/blob/ v3.0.0 / drainer / syncer.go#L91),然后就可以可以使用统一的接口管理整个同步过程了。### Checkpoint 同步进程可能因为各种原因退出,重启后要恢复同步就需要知道上次同步的进度。在Drainer里记录同步进度的功能抽象成`Checkpoint`接口,其定义如下:```






type CheckPoint interface {   //加载加载检查点信息。  Load()错误



  //保存保存检查点信息。  保存(int64)错误


  // Pos获取位置信息。  TS()int64


  // Close closes the CheckPoint and release resources, after closed other methods should not be called again.
  Close() error
}

从上面定义中可以看到,保存的参数和TS的返回结果都是int64类型,因为同步的进度已由TiDB中单调增量的提交时间戳来记录的,它的类型就是int64。

下水支持不同类型的检查点实现,例如 mysql.go里的MySQLCheckpoint,或者将提交提交写入tidb_binlog库下的检查点表。Drainer会根据下游类型自动选择不同的边检站实现,例如TiDB / MySQL的下游就会使用[MySQLCheckPoint](https://github.com/pingcap/tidb-binlog/blob/v3.0.0/drainer/checkpoint/mysql.go#L33),文件(增量备份)则使用 [PbCheckpoint](https ://github.com/pingcap/tidb-binlog/blob/v3.0.0/drainer/checkpoint/pb.go#L27)。

在同步程序小节,我们成功案例看到同步程序的方法提供了一个信道已接收到已经处理完毕的二进制日志,二进制日志收到后,我们用检查点的保存方法保存二进制日志的提交就可以记下同步进度,细节可查看源码中的[handleSuccess](https://github.com/pingcap/tidb-binlog/blob/v3.0.0/drainer/syncer.go#L180)方法。

翻译器

Syncer在收到binlog后需要将内部记录的变更转换成适合下游Syncer类型的格式,这部分实现在[ 排水器/翻译器 ](https://github.com/pingcap/tidb-binlog/tree/v3.0.0 / drainer / translator)包。

以下游是MySQL的/ TiDB的情况为例。MySQLSyncer.Sync会先调用[ TiBinlogToTxn ](https://github.com/pingcap/tidb-binlog/blob/v3.0.0/drainer/translator/mysql.go #L105)

将二进制日志转换成loader.Txn以便接入下层的装载机模块(加载器接收一个个[ loader.Txn ](https://github.com/pingcap/tidb-binlog/blob/v3.0.0/pkg/loader /model.go#L57)结构并还原成对应的SQL批量写入MySQL / TiDB)。

loader.Txn定义如下:

 // Txn保存交易信息,DDL或DML序列
类型Txn struct { 
  DMLs [] * DML 
  DDL * DDL //此后用于保存您希望包含的任意数据,因此//在接收时将可用在成功频道元数据界面上{}} ```事务处理主要有两类:DDL和DML`元数据`当前放的就是传给`同步`的*项目对象。DDL的情况比较简单,因为二进制日志中已经直接包含了我们要用到的DDL Query.DML则需要遍历二进制日志中的一个个行变更,根据它的类型插入/更新/删除还原成相应的`loader.DML`。###模式上个小节中,我们提到了对行变更数据的解析,在binlog中编码的行变更是没有列信息的,我们需要查到对应版本的列信息才能还原出SQL语义。Schema就是解决这个问题的模块。













在 Drainer 启动时,会调用 [loadHistoryDDLJobs](https://github.com/pingcap/tidb-binlog/blob/v3.0.0/drainer/server.go#L179) 从 TiKV 处查询截至当前时间所有已完成的 DDL Job 记录,按 `SchemaVersion` 升序排序(可以粗略认为这是一个单调递增地赋给每个 DDL 任务的版本号)。这些记录在 Syncer 中会用于[创建](https://github.com/pingcap/tidb-binlog/blob/v3.0.0/drainer/syncer.go#L78)一个 Schema 对象。在运行过程中,Drainer 每遇到一条 DDL 也会[添加到 Schema 中](https://github.com/pingcap/tidb-binlog/blob/v3.0.0/drainer/syncer.go#L367)。

binlog 中带有一个 `SchemaVersion` 信息,记录这条 binlog 生成的时刻 Schema 版本。在同步 Binlog 前,我们会先用这个 `SchemaVersion` 信息调用 Schema 的一个方法 [handlePreviousDDLJobIfNeed](https://github.com/pingcap/tidb-binlog/blob/v3.0.0/drainer/schema.go#L231)。上一段中我们看到 Schema 从何处收集到有序的 DDL Job 记录,这个方法则是按顺序应用 `SchemaVersion` 小于等于指定版本的 DDL Job,在 Schema 中维护每个表对应版本的最新结构信息,去掉一些错误代码后实现大致如下:

func(s *模式)handlePreviousDDLJobIfNeed(版本int64)错误{var i int for i = 0; 我<len(s.jobs); i ++ {如果s.jobs [i] .BinlogInfo.SchemaVersion <=版本{ ,, _,err:= s.handleDDL(s.jobs [i])如果err!= nil {返回错误。Annotatef(err, “处理ddl作业%v失败,架构信息:%s”,s.jobs [i],s)}}}其他{break}}

s.jobs = s.jobs [i:]

返回nil}

对于每个符合条件的Job,由`handleDDL`方法将其表结构TableInfo等信息更新到`Schema`中,其他模块就可以查询到表格最新的最新信息。##恢复工具我们知道Drainer除了可以将binlog直接还原到下游数据库之外,还支持同步到其他外部存储系统块,所以我们也提供了相应的工具来处理存储下来的文件,`Reparo`是其中之一,用于读取存储在文件本节简单介绍下Reparo的用途与实现,读者可以作为示例了解如何处理同步到文件系统的binlog增量备份。### Reparo [Reparo](https:/ /github.com/pingcap/tidb-binlog/tree/v3.0.0/reparo)可以读取同步到文件系统上的binlog增量备份并同步到TiDB。####读取binlog 当下游设置成File(增量备份)时,Drainer将Protobuf编码的binlog保存到指定目录,每写满512 MB新建一个文件。每个文件有个编号,从0 开始依次类推。文件名格式定义如下:

// Binname-创建一个binlog文件名。

func binlogNameWithDateTime(索引uint64,datetime time.Time)字符串{return fmt.Sprintf(“ binlog-%016d-%s”,index,datetime.Format(datetimeFormat))}}

文件的预设都是“ binlog-”,后面跟一个16位右对齐的编号和一个插入。将目录里的文件按字母顺序排序就可以得到按编号排序的binlog文件名。从指定目录获取文件列表的实现如下:

// ReadDir从目录func中读取并返回所有文件名和目录名ReadDir(目录路径字符串)([]字符串,错误){ 目录,err:= os.Open(dirpath) 如果err!= nil { 返回nil,错误.Trace (err) } 延迟dir.Close()

名称,错误:= dir.Readdirnames(-1), 如果错误!=无{ 返回nil,错误。Annotatef(错误,“目录%s”,目录路径) }

sort.Strings(名称)

返回名称,nil }

这个函数简单地获取目录里全部文件名,排序后返回。在上层还做了一些过滤来去掉临时文件等。得到文件列表后,`Reparo`会用标准库的[bufio.NewReader]( https://golang.org/pkg/bufio/#NewReader)逐个打开文件,然后用`Decode`函数调用其中的一条条binlog:

func Decode(r io.Reader)(* pb.Binlog,int64,错误){有效分辨率,长度,错误:= binlogfile。如果错误则解码(r)!= nil {返回nil,0,errors.Trace(呃)}

binlog:=&pb.Binlog {} err = binlog.Unmarshal(有效文本)如果err!= nil {返回nil,0,错误。Trace(err)}返回binlog,长度,nil}

这里先调用了

binlogfile.Decode 从文件中解析出对应的的的的的的的的的的的的的的的Protobuf编码的一段二进制数据然后解码出二进制日志。####写入TiDB得到二进制日志后就可以准备写入TiDB恢复如初这部分实现像一个简化版的下水的。同步模块,同样有一个同步程序接口以及几个具体实现(除了 mysqlSyncer 还有用于调试的 printSyncer memSyncer`) ,因此就不再介绍。值得一提##小结。这里介绍了下水是如何实现数据同步的以及恢复如初如何从文件系统的恢复增量数据的到MySQL在/ Drainer中,Syncer封装了同步到各个下游模块的具体细节,检查点记录同步进度,转换器从binlog中还原出特定的变化,在内存中维护每个表对应的表结构定义。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
数据库工程师
手记
粉丝
61
获赞与收藏
84

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消