2. Kafka Connect 架构

Kafka Connect 的架构,说白了就是一套「插件化」的数据搬运流水线。我刚开始接触时也觉得组件有点多,但拆开来看,每个角色各司其职,并不复杂。

先看一张整体架构图,把几个核心组件的关系理清楚:

Kafka Connect 核心组件架构 Worker 集群(分布式模式) Worker 1 SourceConnector (MySQL CDC) Task-0 表A Task-1 表B Converter Transforms → Kafka Topic Worker 2 SinkConnector (Elasticsearch) Task-0 索引A Task-1 索引B Converter Transforms ← Kafka Topic Worker 3 (空闲/备用) 💡 每个 Worker 可以运行多个 Connector 和 Task,Task 是真正干活的线程。 Converter 负责数据序列化/反序列化,Transforms 负责数据轻量转换。

Worker:集群里的「打工人」

Worker 是一个 Java 进程,负责执行 Connector 和 Task。它有两种运行模式:

  • 单机模式(Standalone):一个进程搞定所有事。适合开发测试,或者数据量很小的场景。
  • 分布式模式(Distributed):多个 Worker 组成集群,自动负载均衡、故障转移。生产环境几乎都用这个。

我的经验:单机模式我一般只在本地调试用。线上环境千万别图省事,一旦 Worker 挂了,整个数据管道就断了。分布式模式下,即使一个 Worker 宕机,其他 Worker 会自动接管它的 Task。

Worker 启动时会加载所有插件(Connector、Converter、Transforms 的 jar 包),然后通过 REST API 接收用户提交的 Connector 配置。我个人习惯把插件目录独立出来,方便版本管理。

Connector:管道的「规划师」

Connector 负责定义「怎么连接外部系统」。它不直接搬运数据,而是制定搬运计划。

两种类型:

类型 方向 典型场景
SourceConnector 外部系统 → Kafka 从 MySQL、PostgreSQL、文件系统采集数据
SinkConnector Kafka → 外部系统 写入 Elasticsearch、HDFS、S3

Connector 的核心工作是「划分 Task」。比如一个 MySQL SourceConnector 要同步 10 张表,它可以决定拆成 5 个 Task,每个 Task 负责 2 张表。这个拆分逻辑由 Connector 的 taskConfigs() 方法决定。

关键点:Connector 本身不处理数据,它只负责「规划」。真正干活的是 Task。

Task:真正搬砖的「苦力」

Task 是 Kafka Connect 里唯一干实事的组件。它是一个线程,负责:

  • Source Task:从数据源拉取记录,交给 Kafka Producer 发送到 Topic。
  • Sink Task:从 Kafka Consumer 拉取记录,写入目标系统。

每个 Task 都有自己独立的状态和偏移量(offset)。这意味着 Task 之间互不干扰,可以并行执行。

举个例子:

// SourceConnector 决定拆成 3 个 Task
@Override
public List<Map<String, String>> taskConfigs(int maxTasks) {
    List<Map<String, String>> configs = new ArrayList<>();
    // 把 6 张表分成 3 组,每组 2 张表
    configs.add(Map.of("tables", "users,orders"));
    configs.add(Map.of("tables", "products,inventory"));
    configs.add(Map.of("tables", "payments,shipments"));
    return configs;
}

每个 Task 独立运行,互不阻塞。我曾经遇到过一个场景:某个 Task 因为目标系统响应慢导致积压,但其他 Task 完全不受影响。这就是 Task 隔离性的好处。

注意:Task 数量不是越多越好。每个 Task 都会占用一个线程和连接资源。我建议 Task 数不超过 Worker 核数的 2-3 倍,否则线程切换开销会抵消并行收益。

Converter:数据的「翻译官」

Kafka 内部存储的是字节数组。Converter 负责把数据从「外部格式」翻译成「Kafka 内部格式」,或者反过来。

常用的 Converter:

Converter 特点 适用场景
JsonConverter 可读性好,带 Schema 或不带 Schema 调试、与 JavaScript 系统对接
AvroConverter 压缩率高,Schema 演化能力强 生产环境,需要 Schema Registry
ProtobufConverter 性能好,gRPC 生态友好 微服务架构
StringConverter 最简单,纯文本 日志、简单消息

我个人在生产环境首选 AvroConverter。原因很简单:Schema 演化。你想想看,如果上游改了字段类型,下游没感知,数据就坏了。Avro 配合 Schema Registry 可以优雅处理这种变化。

避坑指南:我曾经遇到一个线上事故——JsonConverter 默认不带 Schema,结果上游新增了一个字段,下游解析直接报错。从那以后,凡是涉及多团队协作的数据管道,我都强制使用 AvroConverter + Schema Registry。

Transforms:数据的「美容师」

Transforms 是轻量级的数据处理插件,在数据进入 Kafka(Source)或写入目标系统(Sink)之前执行。它不需要启动流处理引擎,直接在 Connect 内部完成。

内置的 Transforms 已经覆盖了大部分常见需求:

Transform 作用 示例
RenameField 重命名字段 old_name → new_name
MaskField 脱敏字段 手机号:138****1234
TimestampRouter 按时间路由 Topic topic-2024-01
InsertField 插入静态字段 添加 source=mysql
Filter 过滤记录 只保留 status=active

配置方式很简单,在 Connector 配置里声明即可:

transforms=rename,mask
transforms.rename.type=org.apache.kafka.connect.transforms.ReplaceField$Value
transforms.rename.renames=user_name:username,phone:mobile
transforms.mask.type=org.apache.kafka.connect.transforms.MaskField$Value
transforms.mask.fields=phone
transforms.mask.replacement=****

嗯,这里要注意:Transforms 的执行顺序就是配置顺序。上面先重命名,再脱敏,顺序错了可能出问题。

我的建议:Transforms 适合做「轻量级」处理。如果要做聚合、关联、窗口计算,还是交给 Kafka Streams 或 ksqlDB。别把 Connect 当流处理引擎用,它扛不住。

组件协作流程

最后,我把整个协作流程串起来,方便你理解:

  1. 用户通过 REST API 提交 Connector 配置。
  2. Worker 集群的 Leader 收到请求,把配置写入 Kafka 内部 Topic(connect-configs)。
  3. 所有 Worker 监听配置变更,Leader 根据配置创建 Connector 实例。
  4. Connector 调用 taskConfigs() 生成 Task 配置,Leader 把 Task 分配给各个 Worker。
  5. 每个 Worker 启动 Task 线程,Task 加载 Converter 和 Transforms 配置。
  6. Source Task 拉取数据 → Converter 序列化 → Transforms 处理 → 写入 Kafka Topic。
  7. Sink Task 从 Kafka Topic 消费 → Converter 反序列化 → Transforms 处理 → 写入目标系统。
  8. Worker 定期把偏移量提交到 Kafka 内部 Topic(connect-offsets),保证断点续传。

这个流程里,每一步都有对应的配置和调优空间。后面几章我会逐个展开讲,这里先有个整体印象就行。