RocketMQ Connect 构建流式数据处理平台
本文作者:孙晓健,Apache RocketMQ Committer
01 RocketMQ Connect
RocketMQ Connect 是一款可扩展的在 RocketMQ 与其他系统之间做流式数据传输的工具,能够轻松将 RocketMQ 与其他存储技术进行集成。RocketMQ Connect 使用特定的 Source 插件类型,将数据发送到 RocketMQ Topics 中,并通过 Sink 监听 Topics 将数据写到下游指定数据存储中。使用过程中 Connector 可以通过 JSON 方式进行配置,无需编码。数据流转过程从源到目的,通过 RocketMQ 进行桥接。
RocketMQ Connect 具有以下特性:
①通用性:Connect 制定了标准 API,包括 Connector、Task、Converter、 Transform, 开发者可以通过标准 API 扩展自己插件,达到自己需求。
②Offset 自动管理(断点续传):Source方面——用户在开发 Connect 时,可以通过 Offset 进行增量数据拉取。系统内部会自动对 Offset 做管理,会将上次拉取 Offset 信息进行持久化。下次任务重启时,可以通过上次提交的 Offset 继续进行数据增量拉取,无需从头进行数据同步 ;Sink 方面——基于 RocketMQ 自身的 Offset 提交策略,在内部实现了自动提交方式,任务运行时会自动处理,允许用户配置 Offset 提交间隔;如果系统自带 offset 已经可以满足需求,则无须另外维护 Offset;如果系统自带 Offset 无法满足需求,则可以通过 Task API 进行维护。Task API 中自带 Offset 维护能力,可以在 Connect 中自行决定 Offset 持久化逻辑,比如持久化到 MySQL、Redis 中。下次任务启动时,可以自动从 Offset 存储位点获取下一次执行 Offset ,继续做增量拉取。
③分布式、可扩展、容错:可以分布式的方式进行部署,自带容错能力。Worker 宕机或添加 Worker 时,任务会自动做重新分配、运行,在各集群 Worker 之间做平衡。任务失败后, 也会自动重试。重试完可自动 Rebalance 到不同 Worker 机器上。
④运维和监控:Connect 提供了标准的集群管理功能,包括 Connect 管理功能以及插件管理功能。可以通过 API 方式对任务做启停操作,也可以查看任务在运行过程中的运行状态以及异常状态。并且可以进行指标上报,任务在数据拉取与数据写入后,数据总量、数据速率等都可以通过 Metrics方式做数据上报。此外,Metrics 也提供了标准的上报API ,可以基于标准 API 做指标扩展和上报方式的扩展,比如上报到 RocketMQ topic 中、Prometheus等。
⑤批流一体:Source 在做数据拉取时,可以通过 JDBC 或 指定插件 sdk 的方式,做批量数据拉取,转换为流方式,也可以使用 CDC 方式,通过 增量快照 或类 Mysql binlog 监听方式获取源端全量与增量变更数据,推给 RocketMQ,下游可以通过 Flink 或 RocketMQ Stream进行流式处理做状态计算, 也可直接落到数据存储引擎中,如 Hudi、 Elasticsearch、 Mysql 等 。
⑥Standalone、Distributed模式:Standalone 模式主要用于测试环境,Distributed模式主要用于生产环境。在试用过程中可以用 Standalone 方式做部署,得益于其不会做 Config 存储,每次启动时都可以带独立任务,帮助调试。
Connect 组件包含以下几类:
- Connector:作为任务协调的高级抽象,描述了 Task 运行方式以及如何做 Task 拆分。
- Task:负责实际数据拉取操作,并负责 offset 的维护和 Task Metrics 数据的收集。
- Worker :执行 Task 任务的进程。
- Record Converter:在 Source与 Sink 之间做数据转换,Record 通过 Schema 制定数据契约,Schema 可以随数据传输, 也可以通过 RocketMQ Schema Registry进行远程存储,目前支持了 Avro 和 JSON 两种类型的 Converter。
- Transform:数据传输过程中做数据转换。如进行字段变更、类型变更、做空值或已知错误值过滤等;还可以通过扩展 groovy transform 、python transform 等脚本对数据进行复杂的转换, 亦可做远程调用来进行静态数据的补全或做函数计算。
- Dead Letter Queue:在数据从 Source端到 Sink 端的过程中,数据 Convert 转化错误、网络超时、逻辑错误造成写入失败等情况,可以根据自己编写的插件逻辑来决定是将数据写入到错误队列中、或忽略错误继续进行、或出现错误后停止任务等。写入错误队列中的数据,在不计较数据有序的情况下可自助进行异步修复后再写入。
- Metrics:提高任务运行过程中的可观测性,任务在数据拉取与数据写入时,需要监测任务拉取的数据量、写入数据量、拉取速率、写入速率、差值、内存占用等,都可以通过 Metrics 进行指标上报,供系统运营和运维使用。
上图为数据在 Connect 中的流转过程。
分布式部署下,Source与 Sink 可以在不同 Worker中,不相互依赖,一个 Connector下可包含 Task、 Transform 、Converter 顺序执行。Task 负责从源端拉取数据,Task并发数量由自定义插件的分片方式决定。拉取到数据后,若中间配置了数据处理 Transform,数据会依次经过配置的一个或者多个 Transform 后,再将数据传送给 Converter, Converter 会将数据进行重新组织成可传输的方式,若使用了 RocketMQ Schema Registry,则会进行 Schema 的校验、注册或升级,经过转换后的数据,最终写入至中间 Topic 中供下游 Sink 使用。下游 Sink 可以选择性的监听一个或者多个 Topic,Topic 中传输来的数据可以是相同存储引擎中的,也可以是异构存储引擎中的数据,数据在 Sink 转换后,最终传给流计算引擎或者直接写入到目的存储中。
在转换过程中, Source Converter 与 Sink Converter 要保持一致。不同的 Converter 解析的 Schema 格式会有差异,若 Converter 不一致,会造成 Sink 解析数据的失败。不同组件之间的差异化,可以通过自定义 Transform 来进行兼容。
以上架构具有如下几点优势:
①松散架构:Source 与 Sink 之间通过 Topic 进行解耦,E、T、L 不再是一个整体。一般相同存储引擎的数据的读取和写入QPS差距很大,所以一体化的 ETL 在数据的读取时会受到目标库写入性能的制约。
而 RocketMQ Connect 中的 Source 和 Sink 解耦后, 可以做 Source 和 Sink 两端独立扩缩容,实现数据读取和写入的动态平衡,互不影响。
②标准 API:降低使用难度,扩展简便,在 API 中抽象了编写并发的具体方式,插件开发者可自定义拆分。
③规范的数据抽象:使用 Topic 做解耦后,需要在 Source 和 Sink 之间建立数据契约。Connect 主要通过 Schema 进行数据约束。以此来支持异构数据源之间的数据集成。
④专注数据拷贝:Connect 主要专注于与异构数据源之间的数据集成,不做流计算,支持数据拷贝到流(Flink、 RocketMQ Stream)系统中,再做流计算。
⑤轻量:依赖少。如果集群中已有 RocketMQ 集群,可以直接部署 RocketMQ Connect做数据同步工作,部署非常简单,无需额外部署调度组件。RocketMQ Connect 自带任务分配组件,无需额外关注。
另外,依托 RocketMQ 强大的性能,可以在不同系统之间做大规模数据的迁移。Source 主要依赖于 RocketMQ 的写入能力,无需等待事务尾端数据写入。Sink 依托于 Topic 的扩展能力,可以根据中间 Topic 的分区数量来决定下游 Sink 并发度,自动做扩展。任务做完扩展后,系统会对 Connector 进行重新分配 , 保证负载均衡,Offset 不会丢,可以基于上次运行状态继续向下运行,无需人工干预。也可以依赖 RocketMQ 的有序策略来做顺序数据的同步。
02 RocketMQ Connect原理
管理区 -- 主要做任务配置变更或查询的接收, 包括创建、删除、更新、启停和查看Connector 等操作。变更任务后,管理端会将任务提交到 RocketMQ 共享配置的 Topic 中。因为每一个 Worker 都监听了相同 Topic ,所以每个 Worker 都能获取 Config 信息,然后触发集群 Rebalance 再重新做任务分配,最终达到全局任务平衡。
运行时区--主要为已经被分配到当前 Worker 的 Task 提供运行空间。包括任务的初始化、数据拉取、Offset维护、任务启停状态上报、 Metrics 指标上报等。
调度区 -- Connect 自带任务分配调度工具,通过 hash 或 一致性 hash 在 Worker 间进行任务平衡,主要监听 Worker 和 Connector 的变更。比如 Worker 添加或删除、 Connector 配置变更、任务启停等。获取状态变更用来更新本地任务状态,并决定是否进行下一轮 Rebalance 操作,以达到整个集群的负载均衡。
管理端、运行时区与调度区存在每个集群的每个 Worker 中,集群 Worker 间通信主要通过共享 Topic 来进行通知 ,Worker 之间无主节、备节点之分,这让集群运维起来非常的方便,只需要在 Broker 中建对应共享 Topic 即可,但由于 Task 状态变化的动作只会发生在一个 Worker 中,集群之间共享会存在短暂延迟,所以通过 Rest Api 查询 Connector 状态时可能会出现短暂不一致的现象。
服务发现过程。有变更时,每一个 Worker 都可以发现节点变更,实现服务自动发现的效果。
①启动新的 Worker 时, Worker 会向依赖的 RocketMQ Topic 注册客户端变更监听。相同的 Consumer Group,当有新客户端添加时,注册了该事件的客户端会收到变更通知, Worker 收到变更事件后,会主动更新当前集群的 Worker 列表。
②当 Worker 宕机或者缩容时也会产生相同的效果。
RocketMQ Connect 任务分配流程如下:
通过调用 Rest API 方式创建 Connector 。如果 Connector 不存在,则自动进行创建,若存在则更新。创建后,会向 Config Topic 发送通知,通知 Worker 有任务变更。Worker 获取任务变更后,再进行重新分配,以达到负载均衡的效果。停止任务也会产生相同的效果, 目前每个 Worker 都会存储全量的任务及状态, 但只运行分配给当前 Worker 的 Task。
目前系统默认提供了简单 hash 或 一致性 hash 两种任务分配模式,建议选择一致性 hash 模式。因为在一致性 hash 情况下,做 Rebalance 时变更比普通 hash 变更范围小,部分已经被分配好的任务不会再进行负载。
Connector 扩展要素分为自定义配置、并发和 Task 信息。
自定义配置包含连接信息(核心配置项)、Convertor 信息、Transform信息等。Connector 仅作为任务全局概要和协调器,实际产生效果的依然是分配后的 Task。比如 1 亿数据分为多个任务拉取,分别放在不同 Task 中执行,因此需要通过 Connector 去按照合理的逻辑做 Task 的拆分, 这些拆分的操作需要在声明 Connector 时制定。Connecor 将配置拆分后,将实际数据拉取逻辑配置告知 Task , Task 决定数据拉取的具体方式。
Task 扩展要素包括配置初始化、连接开启与关闭、拉取频率、错误处理、实际数据拉取逻辑以及 Offset 维护。
整个系统中全局 Converter 转换都使用同一套 API,分为两种模式:
本地模式:从 Source Connect 拉取到数据后,由 Converter 做数据转换。转换过程中,本地操作会将 Schema 与 value 值合并为 Connect record 向下游传递。下游通过相同 Converter 再将其转换为 Record ,推给 Sink task 做数据写入。中间通过 Convert Schema 做了数据契约,可以在 Source与 Sink 之间转换。本地模式下,Schema与 Value 作为一个整体传输,数据 Body 非常臃肿,每一条数据都带有 Schema信息。但其优点为不存在版本兼容问题。
远程模式:在数据转换时,会将 Schema 存到远程 RocketMQ Schema Registry 系统中,在数据传输过程中只带 Value 值,不带 Schema 约束信息。当 Sink 订阅 Topic时,通过信息头带有的 Record ID 获取 Schema 信息、进行 Schema 校验,校验后再做数据转换。
Schema 维护在 RocketMQ Schema Registry 系统中。因此在转换过程中可以在系统中手工更新 Schema,然后用指定的 SchemaID 做转换,但是需要在 Converter 插件中做数据兼容。
Connect Converter 内置了扩展,有本地的 JSON 、普通数据类型 Converter 等。如果内置扩展无法满足需求,可以通过 Record Converter API 自己进行扩展。扩展后,将 Converter 包置于 Worker 运行插件目录下,系统即可自动加载。
配置方式分为 Key 和 Value 两种。其中 Key 标注数据的唯一,也可以是 Struct 结构化数据;Value 是真实传输的数据。
Transform 是在 Connector 与 Convertor 之间做数据映射转换与简单计算的辅助工具。当 Source Converter 与 Sink Connector 在使用过程中达不到业务需求时,可以通过编写 Transform 插件的方式做数据适配。比如不同业务、不同数据源插件之间的数据转换,如字段映射、 字段派生、 类型转换、 字段补全、复杂函数计算等。
系统中内置的 Transform 模式有比如字段扩展 、 替换等。如果不满足需求,可以通过 API 自行扩展 Transform。部署时,只需将编写后的扩展打好包放置对应插件目录下,即可自动加载。
具体配置方式如上图左下方所示,Transform 的运行为串行,可以对一个值做多个转换,可以配置多个 Transform。需要配置多个 Transform 的情况下,通过逗号进行分隔,名称不能重复。
Source Task 做数据拉取或变更监听时,例如,通过 JDBC Mysql 方式做数据增量拉取时,需要指定 Offset 增量拉取的方式,可以通过自增 ID 或 Modify time 的方式。每次数据拉取完成发送成功后,会向 Offset writer 中提交增量信息(id 或者 modify time),系统会异步进行持久化。任务下次启动时 ,会自动获取 Offset,从上次位点开始处理数据,达到断点续传的效果。
封装 Offset 时没有固定模式,可以通过自己的方式拼接 Offset key 或 value 值,唯一依赖的是 RocketMQ 中的 Connect offset topic 信息,主要为推送给其他 worker 做本地 Offset 更新。如果使用系统的 Offset 维护,则用户只需要决定维护上报逻辑,无需关注如何保证 Offset 提交、Offset 回滚模式等,一切都由系统保证。
运行过程中,若开启了死信队列,正确的数据会输送到目的端,错误数据会输送到错误队列中。业务方可以通过异步方式做数据处理,但是该种情况下无法保证有序。如果要保证数据有序,需要在触发报错的情况下将 Task 停止,先进行数据修复,修复后再启动 Task。
如果单个 Task 处理数据报错,只需停止出错的 Task,其他 Task 不受影响。因为每个Task 在处理数据时消费的 Query 不一样,如果指定了Key,会按照 Key 做数据分区,然后保证分区内每个 Query 有序,因此单个 Task 停止不会影响全局有序性。
03 RocketMQ Connect使用场景
RocketMQ Connect 能够适用于大部分传统 ETL 适用的场景。另外,传统 ETL无法实现的比如实时流传输、流批一体、快照功能等,RocketMQ Connect 亦能够实现。
新旧系统迁移场景:业务部升级变更过程中出现了类型变更、表拆分或扩容操作、添加索引的情况下可能导致停机耗时非常久,可以通过 RocketMQ Connect 做数据重新搬迁。
分库分表场景:当前市面上有很多分库分表插件,可以通过 Connect 适配开源分库分表客户端做分库分表工作,也可以基于 RocketMQ 自己做分库分表逻辑,源端与目的端不变。数据从单表中取出后,可以在 Transform 中做分库分表逻辑。可以通过 Transform 做路由。路由到不同 Topic 中,在下游可以通过监听不同 Topic 落到已经分好的库表中。
多活:RocketMQ Connect 支持集群间 Topic 及元数据的拷贝,可保证多中心的 Offset 一致。
数据订阅场景:通过 CDC 模式做数据监听,向下游做数据通知。供下游做数据订阅以及即时数据更新。同时也可以将数据拉取后通过 HTTP 的方式直接推送到下游业务系统中,类似于 Webhook 的方式,但是需要对请求做验权、限流等。
其次,还有数据入仓入湖、冷数据备份、异构数据源数据集成等业务场景都可以通过RocketMQ Connect 作为数据处理方案
从整体使用场景来看,大致可以分为两部分,数据集成和流式处理。数据集成主要为将数据从一个系统搬到另一个系统,可以在异构数据源中进行数据同步。流式处理主要为将批处理信息通过批量数据拉取,或 CDC 模式将增量数据同步到对应流处理系统中,做数据聚合、窗口计算等操作,最终再通过 Sink 写入到存储引擎中。
04 RocketMQ Connect生态
RocketMQ Connect 目前对上图中产品均能够提供支持,平台也提供了 Kafka Connect 插件的适配。