众所周知tidb是将SQL转换为KV键值对存储的,那么对于ticdc而言,它需要将原始的KV键值对(byte数组)反向解码成SQL Event(有sql含义的对象,如DML/DDL)
其中实现DML解码的组件叫mounter
(虽然mounter
中实现了DDL解码,但不是在mounter
协程执行的,实际是由ddl puller
执行)。mounter
启动时会同时启动若干个(defaultMounterWorkerNum = 32
)解码协程,对接收到的KV键值对 并行解码,再通过pipeline传给sink
组件。
本文主要介绍mounter
解码协程工作的几个核心点。
解码key值
tidb当前版本(4.x~5.x),从TiKV变更数据流接收到的DML的key值设计如下
<tablePrefix><tableId><recordPrefix><recordId>
var (
tablePrefix = []byte{'t'}
recordPrefix = []byte("_r")
...
)
在这里只需要从key值里面获取tableId
和recordId
可以通过tableId
查询到完整的表结构信息
recordId
稍后再说
解码value值
tidb当前版本(4.x~5.x),从TiKV变更数据流接收到的value值设计如下
<CodecVer(1byte)><large(1byte)><非空字段数(2bytes)><空字段数(2bytes)>
<columnId 0><columnId 1>...<columnId n>
<column 0在data段中的偏移量>...<column n在data段中的偏移量>
<data段>
从以上结构中可以很方便地取出各个column的值,并且根据columnId将这些值与表的每个字段一一关联起来,还原出一行数据
关于types.Datum
上一节中解码的字段值会被封装进types.Datum
这个对象。特别值得一提的是,这里的Datum
取代了go语言的interface{}
,设计成专门用于数据传输的场景,具有更高的性能和易用性,由此可见tidb设计的精巧。但是很不幸,当前版本的ticdc数据传输pipeline中并未完全采用Datum
,仍然是将其转化为interface{}
发送到sink
组件使用。
特殊情况
有一类表,它只有1个int
类型的主键,这种表在写入KV存储时,key做了优化,直接把主键用作recordId
(上文所提)
这种表在收到delete事件、并且enableOldValue
参数未设置时,value会是空的
所以这种场景下会使用recordId
作为主键值输出
tidb5.x版本不存在此情况