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