作者:黄佳豪
前面文章介绍了 Pump server,接下来我们来介绍 Drainer server 的实现,Drainer server 的主要作用是从各个 Pump server 获取 binlog,按 commit timestamp 归并排序后解析 binlog 同步到不同的目标系统,对应的源码主要集中在 TiDB Binlog 仓库的 drainer/ 目录下。
Drainer server 的启动逻辑主要实现在两个函数中:NewServer 和 (*Server).Start() 。
NewServer 根据传入的配置项创建 Server 实例,初始化 Server 运行所需的字段。其中重要字段的说明如下:
Server 初始化以后,就可以用 (*Server).Start 启动服务,启动的逻辑包含:
heartbeat 协程定时上报心跳信息到 etcd (内嵌在 PD 中)。collector.Start() 驱动 Collector 处理单元。syncer.Start() 驱动 Syncer 处理单元。后续的章节中,我们会详细介绍 Checkpoint、Collector 与 Syncer。
Checkpoint 代码在 /drainer/checkpoint 下。
首先看下 接口定义:
// When syncer restarts, we should reload meta info to guarantee continuous transmission.
type CheckPoint interface {
// Load loads checkpoint information.
Load() error
// Save saves checkpoint information.
Save(int64, int64) error
// TS get the saved commit ts.
TS() int64
// Close closes the CheckPoint and release resources after closed other methods should not be called again.
Close() error
}drainer 支持把 checkpoint 保存到不同类型的存储介质中,目前支持 mysql 和 file 两种类型,例如 mysql 类型的实现代码在 mysql.go 。如果用户没有指定 checkpoit 的存储类型,drainer 会根据目标系统的类型自动选择对应的 checkpoint 存储类型。
当目标系统是 mysql/tidb,drainer 默认会保存 checkpoint 到 tidb_binlog.checkpoint 表中:
mysql> select * from tidb_binlog.checkpoint;
+---------------------+---------------------------------------------+
| clusterID | checkPoint |
+---------------------+---------------------------------------------+
| 6766844929645682862 | {"commitTS":413015447777050625,"ts-map":{}} |
+---------------------+---------------------------------------------+
1 row in set (0.00 sec)commitTS 表示这个时间戳之前的数据都已经同步到目标系统了。ts-map 是用来做 TiDB 主从集群的数据校验 而保存的上下游 snapshot 对应关系的时间戳。
下面看看 MysqlCheckpoint 主要方法的实现。
// Load implements CheckPoint.Load interface
func (sp *MysqlCheckPoint) Load() error {
sp.Lock()
defer sp.Unlock()
if sp.closed {
return errors.Trace(ErrCheckPointClosed)
}
defer func() {
if sp.CommitTS == 0 {
sp.CommitTS = sp.initialCommitTS
}
}()
var str string
selectSQL := genSelectSQL(sp)
err := sp.db.QueryRow(selectSQL).Scan(&str)
switch {
case err == sql.ErrNoRows:
sp.CommitTS = sp.initialCommitTS
return nil
case err != nil:
return errors.Annotatef(err, "QueryRow failed, sql: %s", selectSQL)
}
if err := json.Unmarshal([]byte(str), sp); err != nil {
return errors.Trace(err)
}
return nil
}Load 方法从数据库中读取 checkpoint 信息。需要注意的是,如果 drainer 读取不到对应的 checkpoint,会使用 drainer 配置的 initial-commit-ts 做为启动的开始同步点。
// Save implements checkpoint.Save interface
func (sp *MysqlCheckPoint) Save(ts, slaveTS int64) error {
sp.Lock()
defer sp.Unlock()
if sp.closed {
return errors.Trace(ErrCheckPointClosed)
}
sp.CommitTS = ts
if slaveTS > 0 {
sp.TsMap["master-ts"] = ts
sp.TsMap["slave-ts"] = slaveTS
}
b, err := json.Marshal(sp)
if err != nil {
return errors.Annotate(err, "json marshal failed")
}
sql := genReplaceSQL(sp, string(b))
_, err = sp.db.Exec(sql)
if err != nil {
return errors.Annotatef(err, "query sql failed: %s", sql)
}
return nil
}Save 方法构造对应 SQL 将 checkpoint 写入到目标数据库中。
Collector 负责获取全部 binlog 信息后,按序传给 Syncer 处理单元。我们先看下 Start 方法:
// Start run a loop of collecting binlog from pumps online
func (c *Collector) Start(ctx context.Context) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
c.publishBinlogs(ctx)
wg.Done()
}()
c.keepUpdatingStatus(ctx, c.updateStatus)
for _, p := range c.pumps {
p.Close()
}
if err := c.reg.Close(); err != nil {
log.Error(err.Error())
}
c.merger.Close()
wg.Wait()
}这里只需要关注 publishBinlogs 和 keepUpdatingStatus 两个方法。
func (c *Collector) publishBinlogs(ctx context.Context) {
defer log.Info("publishBinlogs quit")
for {
select {
case <-ctx.Done():
return
case mergeItem, ok := <-c.merger.Output():
if !ok {
return
}
item := mergeItem.(*binlogItem)
if err := c.syncBinlog(item); err != nil {
c.reportErr(ctx, err)
return
}
}
}
}publishBinlogs 调用 merger 模块从所有 pump 读取 binlog,并且按照 binlog 的 commit timestamp 进行归并排序,最后通过调用 syncBinlog 输出 binlog 到 Syncer 处理单元。
func (c *Collector) keepUpdatingStatus(ctx context.Context, fUpdate func(context.Context) error) {
// add all the pump to merger
c.merger.Stop()
fUpdate(ctx)
c.merger.Continue()
// update status when had pump notify or reach wait time
for {
select {
case <-ctx.Done():
return
case nr := <-c.notifyChan:
nr.err = fUpdate(ctx)
nr.wg.Done()
case <-time.After(c.interval):
if err := fUpdate(ctx); err != nil {
log.Error("Failed to update collector status", zap.Error(err))
}
case err := <-c.errCh:
log.Error("collector meets error", zap.Error(err))
return
}
}
}keepUpdatingStatus 通过下面两种方式从 etcd 获取 pump 集群的最新状态:
Syncer 代码位于 drainer/syncer.go,是用来处理数据同步的关键模块。
type Syncer struct {
schema *Schema
cp checkpoint.CheckPoint
cfg *SyncerConfig
input chan *binlogItem
filter *filter.Filter
// last time we successfully sync binlog item to downstream
lastSyncTime time.Time
dsyncer dsync.Syncer
shutdown chan struct{}
closed chan struct{}
}在 Syncer 的结构定义中,我们关注下面三个对象:
Syncer 运行入口在 run 方法,主要逻辑包含:
本文介绍了 Drainer server 的主体结构,后续文章会具体介绍其如何同步数据到不同下游。
原文阅读:
TiDB Binlog 源码阅读系列文章(七)Drainer server 介绍 | PingCAP
更多 TiDB Binlog 源码阅读:
Blog-cns | PingCAP
原文作者:;如有侵权,请联系管理员删除。