github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/docs/design/2020-02-24-ticdc-mq-protocol-cn.md (about) 1 # TiCDC MQ(Kafka) Protocol 2 3 ## 设计目标 4 5 * 尽可能复用现有的 Sink 接口 6 * 满足事务级别的一致性 7 * 满足下游可以恢复到某个一致的ts 8 * 提供一个开关,可以弱化一致性约束提高同步效率,满足行级别的一致性 9 * 尽可能使下游易于开发 10 * 利用 Kafka partition 提供平行扩展能力 11 12 ## 术语表 13 14 ### Kafka 相关概念 15 16 | 名词 | 定义 | 解释 | 17 | --- | --- | --- | 18 | Partition | - | Kafka 中的概念,一个消息队列(Topic)中包含数个 Partition。Partition 内部保证消息时序,Partition 之间不保证消息时序。| 19 | Producer | - | 消息生产者,向 Kafka 集群写入 Msg,在本协议中,Producer 指 TiCDC 集群。| 20 | Consumer | - | 消息消费者,从 Kafka 队列进行消费的程序。| 21 22 ### TiCDC 相关概念 23 24 | 名词 | 定义 | 解释 | 25 | --- | --- | --- | 26 | CDC Owner | - | CDC 集群节点的角色之一,一个 CDC 集群有且只有一个 Owner 节点。Owner 节点负责协调各个 Processor 的进度,同步 DDL 等。| 27 | CDC Processor | - | CDC 集群节点的角色之一,一个 CDC 集群有数个 Processor 节点。Processor 节点执行对一张或多张表的的数据变更进行同步。| 28 | CDC Processor ResolvedTS | ResolvedTS < ∀(unapplied CommitTS) | ResolvedTS 保证单个对应 Processor 的任意 CommitTS 小于 ResolvedTS 的 Change Event 已经被输出到 CDC 集群。 | 29 | CDC Processor CheckpointTS | CheckpointTS < ∀(unduplicated && unapplied CommitTS); CheckpointTS <= ResolvedTS | CheckpointTS 保证单个对应 Processor 的任意 CommitTS 小于 CheckpointTs 的 Change Event 已经被同步到对应 Sink。| 30 | CDC DDL Puller ResolvedTS | ResolvedTS < ∀(FinishedTS of undone DDL Job) | CDC DDLPuller ResolvedTS 保证任意 FinishedTS 小于 CDC DDLPuller ResolvedTS 的 DDL 已经被输出到 CDC 集群。 31 | CDC Global ResolvedTS | Global ResolvedTS = min(all Processor ResolvedTS) | CDC Global ResolvedTS 是各个 Processor 当前 ResolvedTS 的最小值 | 32 | CDC Global CheckpointTS | Global CheckpointTS = min(all Processor CheckpointTS) | CDC Global CheckpointTS 是各个 Processor 当前 CheckpointTS 的最小值 | 33 | DDL FinishedTS | DDL 执行完成(DDL Job Status 转为 Done) 的事务的 CommitTS| 标记 DDL 何时被应用,用于 DDL 与 DML 之间的排序。 34 35 ## 消息类型 36 * Row Changed Event:由 CDC Processor 产生,包含 Txn CommitTS、Row Value 相关数据 37 38 ``` 39 示例: 40 Key: 41 { 42 "ts":1, 43 "type":"Row", 44 "schema":"schema1", 45 "table":"table1" 46 } 47 Value: 48 { 49 "update":{ 50 "columnName1":{ 51 "type":"Long", 52 "value":7766, 53 "unique":true 54 }, 55 "columnName2":{ 56 "type":"Text", 57 "value":"aabb" 58 } 59 } 60 } 61 62 { 63 "delete":{ 64 "columnName1":{ 65 "type":"Long", 66 "value":7766, 67 "unique":true 68 } 69 } 70 } 71 ``` 72 73 * DDL Event:由 CDC Owner 产生,包含 DDL FinishedTS、DDL query 74 75 ``` 76 示例: 77 Key: 78 { 79 "ts":1, 80 "type":"DDL", 81 "schema":"schema1", 82 "table":"table1" 83 } 84 Value: 85 { 86 "query":"drop table a;" 87 } 88 ``` 89 90 * Resolved Event:由 CDC Owner 产生,包含 ResolvedTS 91 ``` 92 示例: 93 Key: 94 { 95 "ts":1, 96 "type":"Resolved" 97 } 98 Value: 99 - 100 ``` 101 102 * Admin Event:由 CDC Owner 产生,目前用于 partition 扩容的情况。 103 104 ``` 105 示例: 106 Key: 107 { 108 "type":"Admin" 109 } 110 Value: 111 { 112 "partition":5 113 } 114 ``` 115 116 ## 约束 117 118 1. 对于一行数据的变更: 119 1. 一定会分发在同一个 partition 中 120 1. 大多数情况下,每一个变更只会发出一次,在 CDC Processor 意外下线时,可能会发送重复的变更 121 1. 每个变更第一次出现的次序一定是有序的(CommitTS 升序) 122 1. 对于一个 partition: 123 1. 不保证不同 row 的 Row Changed Event 之间的时序 124 1. Resolved Event 保证任意(CommitTS < ResolvedTS)的 Row Changed Event 在 Resolved Event 之后不会出现。 125 1. DDL Event 保证任意(CommitTS < FinishedTS of DDL event)的 Row Changed Event 在 DDL Event 之后不会出现。 126 1. Resolved Event 和 DDL Event 会广播到所有的 partition 127 128 ## Kafka 分区策略 129 130 * Row Changed Event 根据一定的分区算法分发至不同的 partition 131 * Resolved Event 和 DDL Event 广播至所有的 partition 132 133 ### Row Changed Event 划分 Partition 算法 134 135 #### _tidb_row_id 分发 136 137 使用 _tidb_row_id 计算 hash 分发到 partition 138 139 使用限制: 140 141 * 下游非 RDMS,或下游接受维护 _tidb_row_id 142 * 上游表 pk is handle 143 144 #### 基于 uk、pk 分发 145 146 使用 pk(not handle) or uk(not null) 的 value 计算 hash 分发到 partition 147 148 使用限制: 149 150 * 表内只能包含一个 pk or uk 151 152 #### 基于 table 分发 153 154 使用 schemaID 和 tableID 计算 hash 分发到 partition 155 156 注:在当前的实现中,基于 table 分发使用的是 schemaName 与 tableName 的值计算 hash 157 158 使用限制: 159 160 * 无,但是这种分发方式粒度粗,partition 之间负载不均 161 162 #### 基于 ts 分发 163 164 使用 commit_ts 取模分发到 partition 165 166 使用限制: 167 168 * 无法保证行内有序性和表内有序性 169 170 #### 冲突检测分发 171 172 检测 pk or uk 冲突,将不冲突的 row 写入不同的 partition。发生冲突时向下游写入用于对齐进度的 Event 173 174 注:尚未实现这种分发方式 175 176 使用限制: 177 178 * 只有表中 pk or uk 数量大于 1 才适用这种方式 179 * 下游消费端实现复杂 180 181 #### 分发方式与一致性保证 182 183 | 分发方式 | 行内有序性<sup><a href="#note1">[1]</a></sup> | 表内有序性<sup><a href="#note2">[2]</a></sup> | 表内事务一致性 <sup><a href="#note3">[3]</a></sup> | partition 分配平衡度 | 184 | :-------------: | :------: | :------: | :------: | :------: | 185 | _tidb_row_id 分发 | ➖<sup><a href="#note4">[4]</a></sup> | ✖ | ✖ | 平衡 | 186 | 基于 uk、pk 分发 | ➖<sup><a href="#note5">[5]</a></sup> | ✖ | ✖ | 平衡 | 187 | 基于 table 分发 | ✔ | ✔ | ✔ | 不平衡 | 188 | 冲突检测分发 | ➖<sup><a href="#note6">[6]</a></sup> | ✖ | ➖<sup><a href="#note6">[6]</a></sup> | 较平衡 | 189 | 基于 ts 分发 | ✖ | ✖ | ✖ | 平衡 | 190 191 1. <a name="note1"></a> 行内有序性指对于某一行产生的一组数据变更事件,一定会被分发到同一个 partition,并且在 partition 内保证时间增序 192 1. <a name="note2"></a> 表内有序性指对于某一表内产生的一组数据变更事件,一定会被分发到同一个 partition,并且在 partition 内保证时间增序 193 1. <a name="note3"></a> 表内事务一致性指对于某一事务产生的每一表内的一组数据变更事件,一定会被分发到同一个 partition,并且在 partition 内保证时间增序 194 1. <a name="note4"></a> 仅当上游表内只有一个主键且主键为 int 类型时,_tidb_row_id 分发满足行内有序性 195 1. <a name="note5"></a> 仅当上游表内只有一个主键或唯一索引时,基于 uk、pk 分发满足行内有序性 196 1. <a name="note6"></a> 对于冲突检测分发,当未检测到冲突时,消费端可以并发读取 partition 中的数据变更事件,当检测到冲突时,下游需要对齐各个 partition 的消费进度,当未检测到冲突时,冲突检测分发是满足行内有序性和表内事务一致性的。 197 198 199 ### Partition 扩容与缩容 200 201 #### 基础方案 202 203 CDC 集群: 204 * CDC Owner 205 * 暂停所有任务 206 * 令 CDC Global ResolvedTS = 0(或发送 AdminStop 指令) 207 * 等待所有 CDC Processor 的任务已经暂停(此处缺少 Processor 反馈机制) 208 * 通知所有 CDC Processor,Partition 数量变更 209 * 向 kafka 所有 Partition 广播 Admin Event,包含新的 Partition 数量信息 210 * 恢复所有任务 211 * 令 StartTS = CDC Global CheckpointTS 重启任务 212 * CDC Consumer 213 * 收到 Admin Event 后,丢弃缓存中(CommitTS > Kafka Partition ResolvedTS) Row Changed Event,等待其他 listener 都收到 Admin Event。 214 * 全部 listener 收到 Admin Event 后,按照新的 partition 数量重新分配 listener 215 216 注:第一版暂不实现 partition 的扩容与缩容。 217 218 ## Kafka Producer 逻辑 219 220 ### CDC 同步模型 221 222 1. 基础方案 223 224 直接在目前的 CDC 同步模型的基础上实现,目前 CDC 同步模型中,对于一个 CDC Processor 而言,CDC Processor 以 Table 为单位,捕获 TiKV Change Event。通过 Resolved TS 机制排序,将相同 CommitTS 的 TiKV Row Change Event 拼成一个事务。保证了表内事务的完整性和有序性。 225 226 TiKV Row Change Event 不需要经过拼事务的过程(CollectRawTxns Function),但是需要利用 ResolvedTS 排序,保证 Table 内 Row 级别的有序输出到 Kafka。 227 228 CDC 同步模型中的其他机制保持不变。 229 230 因此,基础方案符合上文“1”约束。 231 232 CDC 同步模型中,CDC Global CheckpointTS 的意义是所有(CommitTS <= CDC Global CheckpointT)的事务已经被写入到下游,如果下游是 Kafka,CDC Global CheckpointTS 的意义则是所有(CommitTS <= CDC CheckpointTS)的 Change Event 已经被写入到 Kafka。 233 234 因此,在 CDC 同步模型中的 CDC Global CheckpointTS 符合上文“2.2”约束。 235 236 对于 DDL 而言,CDC 同步模型中,当 CDC Global CheckpointTS = DDL FinishedTS 时才会向下游复制 DDL 信息,DDL FinishedTS 同样具有 CDC Global CheckpointTS 的意义。 237 238 因此,CDC DDL FinishedTS 符合上文“2.3”约束。 239 240 2. 在基础方案上的优化 241 242 以 row 为单位向 kafka 写数据,CDC 集群不需要还原事务。Owner 只需要维护 DDL 和 DML 的时序,可以令 `CDC Global ResolvedTS = MaxUint64`,而不是 `CDC Global ResolvedTS = min(all Processor ResolvedTS)`。`CDC Global CheckpointTS = min(CDC DDL Puller ResolvedTS, DDL Finished TS, min(all Processor CheckpointTS))`,而不是 `CDC Global CheckpointTS = min(all Processor CheckpointTS)`。 243 244 正确性论证: 245 246 我们称: 247 * 尚未复制到 Kafka 的 Change Event 为 unwritten Change Event; 248 * 在 TiKV 中尚未输出到 CDC 集群并且不与已经输出到 CDC 集群重复的的 Change Event 为 unapplied Change Event; 249 * 状态尚未变成 Done 的 DDL 为 undone DDL。 250 251 “2.2” 约束的公式描述: 252 253 `Kafka Partition ResolvedTS < ∀(unwritten CommitTS in all CDC Processors)` 254 255 结合 CDC Processor ResolvedTS 定义,有: 256 257 `min(all Processor CheckpointTS) < ∀(unwritten CommitTS in all CDC Processors)` 258 259 结合优化中新的 CDC Global CheckpointTS 定义有: 260 261 `CDC Global CheckpointTS <= min(all Processor CheckpointTS) < ∀(unwritten CommitTS in all CDC Processors)` 262 263 即 Global CheckpointTS 满足“2.2”约束。 264 265 根据 CDC 同步模型,当且仅当 `CDC Global ResolvedTS = DDL FinishedTS 时,CDC Owner` 向下游写 DDL Event。 266 因此,写 DDL Event 时,`DDL FinishedTS < ∀(unwritten CommitTS in all CDC Processors)` 267 268 即 DDL FinishedTS 满足“2.3”约束。 269 270 ### CDC Owner 逻辑 271 272 * 向所有 partition 广播有序的 CDC Global CheckpointTS(作为 Kafka Partition ResolvedTS)。 273 * 当 Kafka Partition ResolvedTS = DDL FinishedTS 时,向所有 partition 广播 DDL Event。 274 275 ### CDC Processor 逻辑 276 277 * 对于一个 Row Event: 278 * Msg Key = (SchemaName,TableName,CommitTS) , Msg Value = Change Event Value。 279 * 根据相应的分区算法计算 Hash,确定 partition,写入到对应的 partition。 280 * 对于一个 Processor 281 * Processor 需要保证按 CommitTS 递增顺序输出 Row Changed Event。 282 283 ## Kafka Consumer 逻辑 284 285 需要满足前提:Kafka 集群可以正常读取。 286 287 1. 每一个 partition 对应一个 listener 288 2. 对于每一个 listener,首先缓存接收到的 Row Changed Event 289 3. 收到 ResolvedTS 后,执行被缓存的 (Commit TS <= ResolvedTS) 的 Row Changed Event (commit TS <= ResolvedTS) 290 1. 不要求还原事务:单个 listener 收到 resolved ts 之后就可以写下游 291 1. 要求还原事务:需要所有的 listener 都收到 resolvedts,还原事务后写下游 292 4. 收到 DDL event 后,执行被缓存的 (Commit TS <= DDL FinishedTS) 的 Row Changed Event,并且等待其他 listener 都读到 DDL event 293 5. 当所有的 listener 都收到 DDL 后,执行 DDL,并向下推进。 294 295 ## 几种特殊情况的处理 296 297 ### CDC Owner 切换 298 299 没有特殊情况需要处理 300 301 #### CDC Owner 切换时 global checkpoint TS 会回退吗? 302 303 不会,CDC 同步模型不允许 global checkpoint TS 回退。 304 305 ### Table 迁移 306 307 Table 迁移在 CDC 集群中体现在,某 Table 由 ProcessorA 迁移到 ProcessorB。 308 由于 Row Changed Event 按照 RowID Hash 分片,因此 Table 迁移后,新的 Processor 仍然会保持原有的分片结果,对于消费端而言,Table 的迁移是无感知的。不需要额外处理。 309 310 > Table 迁移可能导致 Row Changed Event 的重复写入,见【Processor 异常下线】一节。 311 312 ### CDC Processor 下线 313 314 #### CDC Processor 正常下线 315 316 消费端无感知,不需要额外处理。 317 318 #### CDC Processor 异常下线 319 320 CDC Processor 异常下线后,由 CDC Owner 感知并标记异常 Processor 下线。有可能出现 CDC Processor 重复写入 Row Changed Event 的情况: 321 322 假设有 ProcessorA、ProcessorB,ProcessorA 同步 TableA 323 1. ProcessorA 向 kafka 写入四个 Row Changed Event (TS=1,2,3,4) 324 2. ProcessorA 意外下线,ProcessorA 的 CheckpointTS 记录到 2 325 3. Owner 标记 ProcessorA 下线,并且将 TableA 迁移到 ProcessorB 326 4. ProcessorB 向 kafka 写入 Txn,StartTS=2,写入了 Row Changed Event(TS=2,3,4) 327 328 因此同一 partition 中可能出现重复的 Row Changed Event。 329 重复的 Row Changed Event **不会**被分发到不同 partition。 330 331 解决方案:消费端需要记录每一张表的已经执行的最大TS(CheckpointTS),过滤掉小于 CheckpointTS 的 Txn 332 333 ## 未来计划 334 335 ### 消费端实现 336 由于 Kafka 消费端逻辑和现有的 CDC 集群写下游逻辑类似,实现上考虑复用 CDC 写下游的逻辑,kafka 相当于 tikv 外的另一种上游数据源。 337 338 目前考虑到 tikv cdc 是以 table 为单位分片,kafka 以 row id 为单位 hash 分片,下游实现起来会有区别,需要考虑兼容方案。