06-15-A-Kafka生态集成与流处理详解

06-15-A-Kafka生态集成与流处理详解

️ 关键词:Kafka Connect · Kafka Streams · KTable · Flink on Kafka · Schema Registry · CDC · 日志管道 · Exactly-Once 流处理 · Spring Kafka

📌 导读 :Kafka 的护城河从来不只是"一个 MQ",而是围绕日志构建的完整生态 ------Connect(数据进出管道)、Streams(轻量流处理)、Schema Registry(数据契约)、Flink(重型流计算)、CDC(数据库变更捕获)。本篇讲生态集成:Kafka Connect 的 Source/Sink 架构与 Worker 模型、Kafka Streams 的 KStream/KTable 双流模型与状态存储、Flink+Kafka 的端到端一致性、Schema Registry 的 Avro 演进规则、典型日志管道与 CDC 链路架构、Spring Kafka 的工程集成。看完本篇你能做到:被问"Kafka 和 Flink 怎么配合"能讲出 Source 位点+Sink 两阶段提交的 EOS 链路,被问"KTable 是什么"能讲出 compact Topic+物化视图的关系,被问"数据怎么进数仓"能画出 CDC→Kafka→Flink→ClickHouse 的完整管道。


📑 目录

  • 06-15-A-Kafka生态集成与流处理详解
    • [📖 术语速查表(每个词都用人话解释)](#📖 术语速查表(每个词都用人话解释))
    • [一、Kafka Connect:配置化的数据管道](#一、Kafka Connect:配置化的数据管道)
      • [1.1 架构](#1.1 架构)
      • [1.2 部署一个 Debezium Source(MySQL → Kafka)](#1.2 部署一个 Debezium Source(MySQL → Kafka))
      • [1.3 Connect vs 自研消费者](#1.3 Connect vs 自研消费者)
    • [二、Kafka Streams:应用内嵌的流处理](#二、Kafka Streams:应用内嵌的流处理)
      • [2.1 KStream vs KTable:双流模型](#2.1 KStream vs KTable:双流模型)
      • [2.2 代码示例:实时订单统计](#2.2 代码示例:实时订单统计)
      • [2.3 Streams vs Flink 选型](#2.3 Streams vs Flink 选型)
    • [三、Flink + Kafka:端到端 Exactly-Once](#三、Flink + Kafka:端到端 Exactly-Once)
      • [3.1 EOS 链路的三段拼接](#3.1 EOS 链路的三段拼接)
      • [3.2 典型实时数仓管道](#3.2 典型实时数仓管道)
    • [四、Schema Registry:数据契约](#四、Schema Registry:数据契约)
      • [4.1 为什么需要](#4.1 为什么需要)
      • [4.2 工作机制](#4.2 工作机制)
    • [五、Spring Kafka 工程集成](#五、Spring Kafka 工程集成)
      • [5.1 生产配置模板](#5.1 生产配置模板)
      • [5.2 消费与异常处理](#5.2 消费与异常处理)
    • [六、跑一遍:Connect 管道与 Streams 应用](#六、跑一遍:Connect 管道与 Streams 应用)
      • [6.1 文件 Source → Kafka → 控制台 Sink(最小 Connect 管道)](#6.1 文件 Source → Kafka → 控制台 Sink(最小 Connect 管道))
      • [6.2 验证 compact Topic 的 KTable 语义](#6.2 验证 compact Topic 的 KTable 语义)
    • 七、总结
      • [7.1 一张图回顾全文](#7.1 一张图回顾全文)
      • [7.2 核心要点浓缩(十二条)](#7.2 核心要点浓缩(十二条))

📖 术语速查表(每个词都用人话解释)

术语 一句话白话解释
Kafka Connect 官方数据管道框架------Source(外部→Kafka)/ Sink(Kafka→外部),配置化搬数据不写代码
Connector / Task Connector 是"搬数据的定义",Task 是"并行执行的单元"------一个 Connector 拆多个 Task 并行
Worker 跑 Connector 的进程------standalone(单机调试)/ distributed(集群,任务自动均衡+位点存 Kafka)
Kafka Streams Java 库形态的流处理------不用独立集群,应用内嵌(对比 Flink 要独立部署)
KStream "流水"抽象------每条记录独立(INSERT 语义)
KTable "变更表"抽象------同 Key 只留最新(UPDATE 语义,底层是 compact Topic,12 篇三章)
状态存储(State Store) Streams 的本地状态(RocksDB)+ changelog Topic 备份------故障恢复靠重放 changelog
Schema Registry Schema 注册中心------Avro/Protobuf 的版本管理与兼容性校验(Confluent 开源)
Flink CDC / Debezium 数据库变更捕获------binlog/WAL → Kafka(Debezium 是 Connect Source 的事实标准)
两阶段提交 Sink Flink Exactly-Once Sink------预提交随 Checkpoint,Checkpoint 完成才 commit 事务
Spring Kafka Spring 集成------@KafkaListener + KafkaTemplate(对比 rocketmq-spring 同款封装思想)

一、Kafka Connect:配置化的数据管道

1.1 架构

#mermaid-svg-bbGzKooeQ87FJv3t{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bbGzKooeQ87FJv3t .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bbGzKooeQ87FJv3t .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bbGzKooeQ87FJv3t .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-bbGzKooeQ87FJv3t .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-bbGzKooeQ87FJv3t .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bbGzKooeQ87FJv3t .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bbGzKooeQ87FJv3t .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bbGzKooeQ87FJv3t .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bbGzKooeQ87FJv3t .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bbGzKooeQ87FJv3t .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bbGzKooeQ87FJv3t .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-bbGzKooeQ87FJv3t .marker.cross{stroke:#0b0b0b;}#mermaid-svg-bbGzKooeQ87FJv3t svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-bbGzKooeQ87FJv3t p{margin:0;}#mermaid-svg-bbGzKooeQ87FJv3t .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-bbGzKooeQ87FJv3t .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-bbGzKooeQ87FJv3t .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-bbGzKooeQ87FJv3t .cluster-label span p{background-color:transparent;}#mermaid-svg-bbGzKooeQ87FJv3t .label text,#mermaid-svg-bbGzKooeQ87FJv3t span{fill:#333;color:#333;}#mermaid-svg-bbGzKooeQ87FJv3t .node rect,#mermaid-svg-bbGzKooeQ87FJv3t .node circle,#mermaid-svg-bbGzKooeQ87FJv3t .node ellipse,#mermaid-svg-bbGzKooeQ87FJv3t .node polygon,#mermaid-svg-bbGzKooeQ87FJv3t .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-bbGzKooeQ87FJv3t .rough-node .label text,#mermaid-svg-bbGzKooeQ87FJv3t .node .label text,#mermaid-svg-bbGzKooeQ87FJv3t .image-shape .label,#mermaid-svg-bbGzKooeQ87FJv3t .icon-shape .label{text-anchor:middle;}#mermaid-svg-bbGzKooeQ87FJv3t .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bbGzKooeQ87FJv3t .rough-node .label,#mermaid-svg-bbGzKooeQ87FJv3t .node .label,#mermaid-svg-bbGzKooeQ87FJv3t .image-shape .label,#mermaid-svg-bbGzKooeQ87FJv3t .icon-shape .label{text-align:center;}#mermaid-svg-bbGzKooeQ87FJv3t .node.clickable{cursor:pointer;}#mermaid-svg-bbGzKooeQ87FJv3t .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-bbGzKooeQ87FJv3t .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-bbGzKooeQ87FJv3t .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-bbGzKooeQ87FJv3t .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-bbGzKooeQ87FJv3t .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-bbGzKooeQ87FJv3t .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-bbGzKooeQ87FJv3t .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-bbGzKooeQ87FJv3t .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-bbGzKooeQ87FJv3t .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-bbGzKooeQ87FJv3t .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-bbGzKooeQ87FJv3t .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-bbGzKooeQ87FJv3t div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bbGzKooeQ87FJv3t .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bbGzKooeQ87FJv3t rect.text{fill:none;stroke-width:0;}#mermaid-svg-bbGzKooeQ87FJv3t .icon-shape,#mermaid-svg-bbGzKooeQ87FJv3t .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-bbGzKooeQ87FJv3t .icon-shape p,#mermaid-svg-bbGzKooeQ87FJv3t .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-bbGzKooeQ87FJv3t .icon-shape .label rect,#mermaid-svg-bbGzKooeQ87FJv3t .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-bbGzKooeQ87FJv3t .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bbGzKooeQ87FJv3t .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bbGzKooeQ87FJv3t :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Connect 集群

distributed Worker × 3
外部系统
MySQL binlog。
ClickHouse。
Elasticsearch。
Debezium Source Connector。
Task × 2 并行。

Kafka。
dbserver.order.orders。

Kafka。
order-event。

ClickHouse Sink。
Task × 3。

ES Sink。
Task × 2。

distributed 模式的三个内部 Topic :connect-configs(Connector 配置)、connect-offsets(Source 位点,如 binlog 位置)、connect-status(运行状态)------Connect 自己也是"用 Kafka 存自己的元数据"(KRaft/位点存储同款哲学,10 篇 2.2)。

1.2 部署一个 Debezium Source(MySQL → Kafka)

json 复制代码
// POST /connectors  (Connect REST API)
{
  "name": "orders-cdc-source",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "tasks.max": "2",
    "database.hostname": "mysql-master",
    "database.server.id": "184054",
    "topic.prefix": "dbserver1",
    "table.include.list": "corp_ceo_order.order_info",
    "schema.history.internal.kafka.bootstrap.servers": "b1:9092",
    "schema.history.internal.kafka.topic": "schema-history.order"
  }
}

产出 :dbserver1.corp_ceo_order.order_info Topic------每条 binlog 变更一个事件(op=c 创建/u 更新/d 删除,含 before/after 镜像):

json 复制代码
{
  "before": null,
  "after": {"id": 1001, "status": "PAID", "amount": 99.9},
  "op": "c",                          // c=create u=update d=delete r=snapshot
  "ts_ms": 1704067200123,
  "source": {"file": "mysql-bin.000042", "pos": 154823}   // binlog 位点(存 connect-offsets)
}

价值 :业务代码零改动的数据同步------下游(ES/ClickHouse/缓存刷新)订阅 CDC Topic 即可(对比 RocketMQ 篇 06-07-A 的 3.2 节:视图类同步用 CDC,核心链路用业务双写)。

1.3 Connect vs 自研消费者

维度 Kafka Connect 自研 Consumer 写 Sink
开发量 配置化(JSON 声明) 写代码
位点/重试/并行 框架管(Task 自动均衡) 自己管
灵活度 受 Connector 能力限制 任意逻辑
适用 标准同步(DB↔Kafka↔ES/CK) 复杂业务转换

二、Kafka Streams:应用内嵌的流处理

2.1 KStream vs KTable:双流模型

text 复制代码
KStream(流水/INSERT 语义):
  (k1,v1) (k1,v2) (k1,v3) ------ 每条都是独立事件
  订单事件流、点击流

KTable(变更表/UPDATE 语义):
  (k1,v1) (k1,v2) (k1,v3) ------ 只有 v3 是"当前值"
  用户画像表、商品库存表(底层 compact Topic,12 篇三章)

Stream-Table 对偶性(核心思想):
  Stream 聚合 → Table(订单流 groupBy 求和 → 每用户消费总额表)
  Table 变更 → Stream(画像表的每次更新 → 变更事件流)
  ------"流水的物化是表,表的日志是流水"(日志即数据库,12 篇结语)

2.2 代码示例:实时订单统计

java 复制代码
// 订单事件流 → 每分钟每城市成交额(KStream → KTable → Topic)
StreamsBuilder builder = new StreamsBuilder();

builder.stream("order-event",
        Consumed.with(Serdes.String(), orderEventSerde))
    .groupByKey()                                        // 按 orderId 聚合去重
    .windowedBy(TimeWindows.ofSizeAndGrace(
        Duration.ofMinutes(1), Duration.ofSeconds(10)))  // 1 分钟窗口 + 10s 宽限
    .aggregate(
        CityAmount::new,                                 // 初始值
        (key, event, agg) -> agg.add(event),             // 聚合逻辑
        Materialized.with(Serdes.String(), cityAmountSerde)
            .withRetention(Duration.ofHours(1)))         // 状态保留 1h
    .toStream()
    .to("order-stats-1m");                               // 结果写回 Kafka(可再 Sink 到 CK)

// 状态存储:本地 RocksDB + changelog Topic 备份
// 实例挂了 → 新实例重放 changelog 恢复状态(Exactly-Once 下配合事务)
维度 Kafka Streams Flink
部署 Java 库,应用内嵌(无独立集群) 独立集群(JobManager/TaskManager)
数据源 只能 Kafka 任意(Kafka/DB/文件/MQ)
状态规模 中(本地 RocksDB) 大(分布式状态+增量 Checkpoint)
语义 事件时间/窗口/Exactly-Once(Kafka 内闭环) 全(两阶段提交跨系统 EOS)
运维成本 极低(就是个 Spring Boot 应用) 高(集群+Checkpoint 调优)
适用 Kafka→Kafka 的轻量 ETL/聚合 重计算/多源/大状态

一句话 :数据不出 Kafka、逻辑不复杂 → Streams(省一个集群);多源汇聚、大状态、复杂 CEP → Flink。


3.1 EOS 链路的三段拼接

下游 Consumer(read_committed) Kafka(结果 Topic) Flink Sink(事务 Producer) Checkpoint 协调器 Flink 计算算子(状态) Flink Source Kafka(源 Topic) 下游 Consumer(read_committed) Kafka(结果 Topic) Flink Sink(事务 Producer) Checkpoint 协调器 Flink 计算算子(状态) Flink Source Kafka(源 Topic) #mermaid-svg-JjTbcIdh6c4SaUx2{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JjTbcIdh6c4SaUx2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JjTbcIdh6c4SaUx2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JjTbcIdh6c4SaUx2 .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-JjTbcIdh6c4SaUx2 .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-JjTbcIdh6c4SaUx2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JjTbcIdh6c4SaUx2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JjTbcIdh6c4SaUx2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JjTbcIdh6c4SaUx2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JjTbcIdh6c4SaUx2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JjTbcIdh6c4SaUx2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JjTbcIdh6c4SaUx2 .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-JjTbcIdh6c4SaUx2 .marker.cross{stroke:#0b0b0b;}#mermaid-svg-JjTbcIdh6c4SaUx2 svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-JjTbcIdh6c4SaUx2 p{margin:0;}#mermaid-svg-JjTbcIdh6c4SaUx2 .actor{stroke:hsl(40.5882352941, 60%, 83.3333333333%);fill:#fff4dd;}#mermaid-svg-JjTbcIdh6c4SaUx2 text.actor>tspan{fill:#333;stroke:none;}#mermaid-svg-JjTbcIdh6c4SaUx2 .actor-line{stroke:hsl(40.5882352941, 60%, 83.3333333333%);}#mermaid-svg-JjTbcIdh6c4SaUx2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-JjTbcIdh6c4SaUx2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-JjTbcIdh6c4SaUx2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-JjTbcIdh6c4SaUx2 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-JjTbcIdh6c4SaUx2 .sequenceNumber{fill:#f4f4f4;}#mermaid-svg-JjTbcIdh6c4SaUx2 #sequencenumber{fill:#333;}#mermaid-svg-JjTbcIdh6c4SaUx2 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-JjTbcIdh6c4SaUx2 .messageText{fill:#333;stroke:none;}#mermaid-svg-JjTbcIdh6c4SaUx2 .labelBox{stroke:hsl(40.5882352941, 60%, 83.3333333333%);fill:#fff4dd;}#mermaid-svg-JjTbcIdh6c4SaUx2 .labelText,#mermaid-svg-JjTbcIdh6c4SaUx2 .labelText>tspan{fill:#333;stroke:none;}#mermaid-svg-JjTbcIdh6c4SaUx2 .loopText,#mermaid-svg-JjTbcIdh6c4SaUx2 .loopText>tspan{fill:#333;stroke:none;}#mermaid-svg-JjTbcIdh6c4SaUx2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(40.5882352941, 60%, 83.3333333333%);fill:hsl(40.5882352941, 60%, 83.3333333333%);}#mermaid-svg-JjTbcIdh6c4SaUx2 .note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#mermaid-svg-JjTbcIdh6c4SaUx2 .noteText,#mermaid-svg-JjTbcIdh6c4SaUx2 .noteText>tspan{fill:#333;stroke:none;}#mermaid-svg-JjTbcIdh6c4SaUx2 .activation0{fill:hsl(-79.4117647059, 100%, 93.3333333333%);stroke:hsl(-79.4117647059, 100%, 83.3333333333%);}#mermaid-svg-JjTbcIdh6c4SaUx2 .activation1{fill:hsl(-79.4117647059, 100%, 93.3333333333%);stroke:hsl(-79.4117647059, 100%, 83.3333333333%);}#mermaid-svg-JjTbcIdh6c4SaUx2 .activation2{fill:hsl(-79.4117647059, 100%, 93.3333333333%);stroke:hsl(-79.4117647059, 100%, 83.3333333333%);}#mermaid-svg-JjTbcIdh6c4SaUx2 .actorPopupMenu{position:absolute;}#mermaid-svg-JjTbcIdh6c4SaUx2 .actorPopupMenuPanel{position:absolute;fill:#fff4dd;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-JjTbcIdh6c4SaUx2 .actor-man line{stroke:hsl(40.5882352941, 60%, 83.3333333333%);fill:#fff4dd;}#mermaid-svg-JjTbcIdh6c4SaUx2 .actor-man circle,#mermaid-svg-JjTbcIdh6c4SaUx2 line{stroke:hsl(40.5882352941, 60%, 83.3333333333%);fill:#fff4dd;stroke-width:2px;}#mermaid-svg-JjTbcIdh6c4SaUx2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 故障恢复:从 Checkpoint 位点重放源数据 + 恢复状态 + 未提交事务 abort = 端到端 Exactly-Once(不重不丢) poll 拉取消息 1 返回批次 2 转发数据 3 计算结果 4 beginTransaction + 写入(预提交,下游不可见) 5 触发 Checkpoint(屏障注入) 6 快照当前消费位点 7 快照算子状态(Chandy-Lamport 一致性快照) 8 snapshotState:事务预提交完成 9 Checkpoint 完成通知 10 commitTransaction(提交事务) 11 已提交消息可见(只见完整结果) 12

java 复制代码
// Flink 写 Kafka 的 EOS Sink
KafkaSink<OrderStats> sink = KafkaSink.<OrderStats>builder()
    .setBootstrapServers("b1:9092")
    .setRecordSerializer(...)
    .setDeliveryGuarantee(DeliveryGuarantee.EXACTLY_ONCE)   // 两阶段提交
    .setTransactionalIdPrefix("order-stats-")                // 事务 ID 前缀
    .build();
// 注意:transaction.timeout.ms 要 > Checkpoint 间隔 + 恢复时间(默认 15min 上限要调 Broker)

EOS 的边界 (25-A 篇通用结论的 Kafka 版):"Kafka→Flink→Kafka"闭环才有真 EOS ;Sink 到外部系统(MySQL/ClickHouse)时,EOS 退化为"At Least Once + 目标端幂等"(ClickHouse 的 ReplacingMergeTree / MySQL 的 upsert)。

3.2 典型实时数仓管道

text 复制代码
业务 MySQL ──Debezium──> Kafka(CDC Topic)
                            ├──Flink 清洗打宽──> Kafka(DWD 明细层)
                            │                      └──Flink 聚合──> Kafka(DWS 汇总层)
                            │                                        └──Sink──> ClickHouse(ADS 应用层,19-A 篇)
                            └──Sink──> Elasticsearch(实时搜索视图,17-A 篇)

分层:ODS(CDC 原始)→ DWD(明细清洗)→ DWS(轻度汇总)→ ADS(应用指标)
------每层都是 Kafka Topic(可回放!),计算都是 Flink 作业
这就是"流式数仓":对比离线数仓(Hive T+1),延迟从小时级到秒级

四、Schema Registry:数据契约

4.1 为什么需要

JSON 自由格式的腐化:上游改字段名/类型,下游解析崩溃------"消息格式靠口口相传"是管道事故的头号来源。

4.2 工作机制

text 复制代码
① Producer 发送前:Schema 注册到 Registry → 得到全局唯一 schemaId
② 消息只带 5 字节头(magic+schemaId)+ Avro 二进制体(不带 Schema 全文!)
   ------比 JSON 小得多(Schema 不重复传输),且格式强约束
③ Consumer 收到:按 schemaId 从 Registry 拉 Schema 反序列化(本地缓存)
④ 兼容性校验:新 Schema 注册时 Registry 自动校验
   BACKWARD(默认):新 Schema 能读旧数据(可加带默认值的字段/删字段)
   FORWARD:旧 Schema 能读新数据
   FULL:两者都可
   ------不兼容的变更直接拒绝注册(把"下游崩溃"拦在发布前)

演进规则速记 (BACKWARD):加字段必须带默认值、删字段随意、改类型禁止------遵守这三条,上下游可以独立发布(Schema 版本化的工程价值)。


五、Spring Kafka 工程集成

5.1 生产配置模板

yaml 复制代码
spring:
  kafka:
    bootstrap-servers: b1:9092,b2:9092,b3:9092
    producer:
      acks: all
      retries: 2147483647
      properties:
        enable.idempotence: true
        max.in.flight.requests.per.connection: 5
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
    consumer:
      group-id: ${spring.application.name}     # 组名=应用名(规范)
      enable-auto-commit: false                # 手动提交(10 篇军规)
      auto-offset-reset: earliest
      max-poll-records: 200
      properties:
        session.timeout.ms: 45000
        max.poll.interval.ms: 300000
    listener:
      ack-mode: manual_immediate
      concurrency: 3                            # 每实例 3 个消费线程(≤Partition 数)

5.2 消费与异常处理

java 复制代码
@Component
public class OrderEventConsumer {

    @KafkaListener(topics = "order-event", groupId = "point-service")
    public void onMessage(ConsumerRecord<String, OrderEvent> record, Acknowledgment ack) {
        try {
            pointService.grantIdempotent(record.value());   // 幂等在业务层(25-A 篇)
            ack.acknowledge();                              // 先业务后提交
        } catch (RetryableException e) {
            throw e;    // 抛给 DefaultErrorHandler → 退避重试 → 超限进 DLT(10 篇 5.2)
        }
    }
}

// 重试+死信(Spring Kafka 封装,对比 RocketMQ 内核自带)
@Bean
DefaultErrorHandler errorHandler(KafkaTemplate<String, Object> template) {
    DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer(template,
        (r, e) -> new TopicPartition(r.topic() + ".DLT", r.partition()));  // 同 Partition 保序
    return new DefaultErrorHandler(recoverer,
        new ExponentialBackOff(1000L, 2.0));    // 1s 起指数退避
}

与 rocketmq-spring 集成对比(06-07-A 篇 1.2):

维度 Spring Kafka rocketmq-spring
重试/死信 框架层配置(DefaultErrorHandler+DLT) 内核自带(%RETRY%/%DLQ%)
位点/ACK 手动 ack(manual_immediate) 框架自动 ACK(异常=重试)
并发模型 concurrency 线程 + Partition 约束 consumeThreadMin/Max + Queue 约束
顺序 Partition 内天然有序 + DLT 同 Partition 顺序消费要 ConsumeMode.ORDERLY

六、跑一遍:Connect 管道与 Streams 应用

6.1 文件 Source → Kafka → 控制台 Sink(最小 Connect 管道)

shell 复制代码
# ① 写一个测试文件,配置 standalone Connect(文件 Source + 控制台 Sink)
echo -e "line-1\nline-2" > /tmp/connect-test.txt

cat > connect-standalone.properties <<'EOF'
bootstrap.servers=localhost:9092
key.converter=org.apache.kafka.connect.storage.StringConverter
value.converter=org.apache.kafka.connect.storage.StringConverter
offset.storage.file.filename=/tmp/connect.offsets
EOF

cat > file-source.properties <<'EOF'
name=local-file-source
connector.class=FileStreamSource
tasks.max=1
file=/tmp/connect-test.txt
topic=connect-test
EOF

# ② 启动 Worker(加载 Source 连接器)
bin/connect-standalone.sh connect-standalone.properties file-source.properties &

# ③ 追加一行文件,观察自动进入 Kafka
echo "line-3" >> /tmp/connect-test.txt
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --topic connect-test --from-beginning --timeout-ms 5000

③ 的输出(文件内容被 Source Task 持续同步进 Topic):

text 复制代码
line-1
line-2
line-3
Processed 3 messages

观察点 :Source 的位点(文件读到第几字节)存在 /tmp/connect.offsets(distributed 模式存 connect-offsets Topic)------断点续传的原理和 Consumer 位点完全一致(1.1 节"Connect 用 Kafka 管自己")。

6.2 验证 compact Topic 的 KTable 语义

shell 复制代码
# ④ 12 篇 6.2 的 user-profile compact Topic:用 Streams 的 KTable 读"当前状态"
# (KTable 源码层就是"从 compact Topic 头读一遍,同 Key 覆盖")
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --topic user-profile --from-beginning \
  --property print.key=true --timeout-ms 5000

④ 的输出(compact 后每 Key 一条 = KTable 的"物化视图"):

text 复制代码
user1002   {"level":1,"city":"上海"}
user1001   {"level":3,"city":"深圳"}
Processed 2 messages

💡 对照理解 :④就是 2.1 节 KTable 的磁盘形态------"compact Topic 从头读一遍 = 表的当前快照" 。Streams 的 KTable 只是把这个过程做成了持续增量的内存/RocksDB 状态;而 ①~③ 的 Connect 演示说明:数据进出 Kafka 的"最后一公里"也位点化了(文件偏移=binlog 位点=消费 Offset,全是"日志+位点"范式,12 篇结语的又一次印证)。


七、总结

7.1 一张图回顾全文

#mermaid-svg-gWg5F0zpfXzp3pSt{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gWg5F0zpfXzp3pSt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gWg5F0zpfXzp3pSt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gWg5F0zpfXzp3pSt .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-gWg5F0zpfXzp3pSt .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-gWg5F0zpfXzp3pSt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gWg5F0zpfXzp3pSt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gWg5F0zpfXzp3pSt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gWg5F0zpfXzp3pSt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gWg5F0zpfXzp3pSt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gWg5F0zpfXzp3pSt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gWg5F0zpfXzp3pSt .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-gWg5F0zpfXzp3pSt .marker.cross{stroke:#0b0b0b;}#mermaid-svg-gWg5F0zpfXzp3pSt svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-gWg5F0zpfXzp3pSt p{margin:0;}#mermaid-svg-gWg5F0zpfXzp3pSt .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-gWg5F0zpfXzp3pSt .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-gWg5F0zpfXzp3pSt .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-gWg5F0zpfXzp3pSt .cluster-label span p{background-color:transparent;}#mermaid-svg-gWg5F0zpfXzp3pSt .label text,#mermaid-svg-gWg5F0zpfXzp3pSt span{fill:#333;color:#333;}#mermaid-svg-gWg5F0zpfXzp3pSt .node rect,#mermaid-svg-gWg5F0zpfXzp3pSt .node circle,#mermaid-svg-gWg5F0zpfXzp3pSt .node ellipse,#mermaid-svg-gWg5F0zpfXzp3pSt .node polygon,#mermaid-svg-gWg5F0zpfXzp3pSt .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-gWg5F0zpfXzp3pSt .rough-node .label text,#mermaid-svg-gWg5F0zpfXzp3pSt .node .label text,#mermaid-svg-gWg5F0zpfXzp3pSt .image-shape .label,#mermaid-svg-gWg5F0zpfXzp3pSt .icon-shape .label{text-anchor:middle;}#mermaid-svg-gWg5F0zpfXzp3pSt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gWg5F0zpfXzp3pSt .rough-node .label,#mermaid-svg-gWg5F0zpfXzp3pSt .node .label,#mermaid-svg-gWg5F0zpfXzp3pSt .image-shape .label,#mermaid-svg-gWg5F0zpfXzp3pSt .icon-shape .label{text-align:center;}#mermaid-svg-gWg5F0zpfXzp3pSt .node.clickable{cursor:pointer;}#mermaid-svg-gWg5F0zpfXzp3pSt .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-gWg5F0zpfXzp3pSt .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-gWg5F0zpfXzp3pSt .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-gWg5F0zpfXzp3pSt .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-gWg5F0zpfXzp3pSt .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-gWg5F0zpfXzp3pSt .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-gWg5F0zpfXzp3pSt .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-gWg5F0zpfXzp3pSt .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-gWg5F0zpfXzp3pSt .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-gWg5F0zpfXzp3pSt .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-gWg5F0zpfXzp3pSt .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-gWg5F0zpfXzp3pSt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gWg5F0zpfXzp3pSt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gWg5F0zpfXzp3pSt rect.text{fill:none;stroke-width:0;}#mermaid-svg-gWg5F0zpfXzp3pSt .icon-shape,#mermaid-svg-gWg5F0zpfXzp3pSt .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-gWg5F0zpfXzp3pSt .icon-shape p,#mermaid-svg-gWg5F0zpfXzp3pSt .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-gWg5F0zpfXzp3pSt .icon-shape .label rect,#mermaid-svg-gWg5F0zpfXzp3pSt .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-gWg5F0zpfXzp3pSt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gWg5F0zpfXzp3pSt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gWg5F0zpfXzp3pSt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Kafka 生态集成
Connect。
Source/Sink 配置化管道
Connector 定义,Task 并行执行
distributed:元数据存内部 Topic
Debezium CDC 是事实标准。

Streams。
KStream 流水 vs KTable 变更表
Stream-Table 对偶(聚合/变更)
状态=RocksDB+changelog Topic
不出 Kafka 的轻量 ETL 选它。

Flink。
EOS 三段:Source 位点入 Checkpoint
+状态快照+Sink 两阶段提交
闭环真 EOS,外部系统靠幂等
流式数仓:ODS→DWD→DWS→ADS 全是 Topic。

契约与工程。
Schema Registry:schemaId 5 字节头
BACKWARD 兼容校验拦在发布前
Spring Kafka:手动 ack+ErrorHandler+DLT
重试死信框架配(对比 RocketMQ 内核带)。

7.2 核心要点浓缩(十二条)

  1. Connect 模型 :Connector(定义)→ Task(并行单元)→ Worker(进程)------distributed 模式任务自动均衡,元数据存三个内部 Topic(用 Kafka 管自己)。
  2. Debezium CDC :binlog → Kafka 事件(op=c/u/d + before/after + binlog 位点)------业务零改动的数据同步,视图类场景首选(对比业务双写)。
  3. Connect vs 自研:标准同步用 Connect(配置化+框架管位点重试),复杂转换才自研 Consumer。
  4. KStream vs KTable :流水(INSERT 语义)vs 变更表(UPDATE 语义,底层 compact Topic)------Stream-Table 对偶是 Streams 的核心思想。
  5. KTable 的磁盘形态 :compact Topic 从头读一遍 = 当前快照------"日志即数据库"的生态印证(12 篇)。
  6. Streams 状态 :本地 RocksDB + changelog Topic 备份------故障重放 changelog 恢复;适用"Kafka→Kafka 轻量 ETL"(省一个 Flink 集群)。
  7. Streams vs Flink:不出 Kafka 选 Streams(库形态零运维),多源/大状态/复杂 CEP 选 Flink(独立集群)。
  8. Flink EOS 三段 :Source 位点入 Checkpoint + 状态一致性快照 + Sink 两阶段提交(Kafka 事务)------下游 read_committed 闭环。
  9. EOS 边界 :Kafka 闭环才是真 EOS------Sink 外部系统退化为 At Least Once + 目标端幂等(ReplacingMergeTree/upsert)。
  10. 流式数仓分层 :ODS(CDC)→DWD(清洗)→DWS(汇总)→ADS(ClickHouse)------每层都是可回放的 Topic,延迟秒级(对比 Hive T+1)。
  11. Schema Registry :消息带 5 字节 schemaId(不带 Schema 全文)+ BACKWARD 兼容校验------把"下游解析崩溃"拦在 Schema 注册时;演进三规则:加字段带默认值/删字段随意/改类型禁止。
  12. Spring Kafka 军规 :手动 ack(manual_immediate)+ DefaultErrorHandler 退避重试 + DLT 死信(同 Partition 保序)------重试死信靠框架配置(RocketMQ 是内核自带,集成心智不同)。

📌 最后一句话 :Kafka 生态的统一范式是"日志 + 位点 + 重放 "------Connect 的位点是文件偏移/binlog 位置,Streams 的状态靠 changelog 重放恢复,Flink 的 EOS 靠 Checkpoint 位点对齐,Schema Registry 让重放的数据永远可解析。一切组件都在回答同一个问题:"从日志的哪个位置、以什么格式、重新算一遍"------这就是"日志即架构"(The Log as Architecture):理解了这一点,Kafka 生态的每个新组件你都能预判它的设计。


📌 配套阅读:

上一篇:《06-14-A-Kafka集群运维与迁移实战详解.md》

下一篇:《06-16-B-Kafka面试与生产事故实战.md》

如果这篇文章对你有帮助,欢迎点赞、收藏、关注!

相关推荐
做个文艺程序员2 小时前
MinIO第05篇:MinIO事件通知机制与Kafka集成——构建SaaS平台的异步文件处理管道
kafka·消息队列·springboot·minio·事件驱动·异步处理
天空鸟_时光不老4 小时前
07-检查点与状态持久化
java·人工智能·spring boot·spring·spring cloud·kafka·maven
此时不提桶,更待何时5 小时前
06-13-A-Kafka客户端与协议深入详解
分布式·kafka
程序猿乐锅11 小时前
从0-1一文详解RabbitMQ
java·分布式·后端·中间件·rabbitmq·ruby
初见月1 天前
ISR机制保障数据一致性
kafka
老陈说编程1 天前
1. 鸿蒙 (HarmonyOS) 2012 至 2026 年的发展历程
分布式·华为·个人开发·harmonyos·鸿蒙·鸿蒙系统·程序员创富
ly76893 天前
Redis 分布式锁的边界条件:Redlock 争议、锁续期与客户端崩溃后的互斥失效
数据库·redis·分布式·分布式锁·watchdog·redlock
筑梦之路3 天前
docker-compose方式部署kafka集群(Kraft方式)——筑梦之路
docker·容器·kafka