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 分片,下游实现起来会有区别,需要考虑兼容方案。