从 CDC 到实时数据流Flink SQLServer CDC Connector 实战

一、SQLServer CDC Connector 是什么?

SQLServer CDC Connector 是 Flink 提供的一个 Source 连接器,核心能力和 MySQL / Oracle CDC 一样:

  • 第一次启动:对目标表做一次快照(历史全量数据);
  • 之后:从 CDC 变更表中持续读取增量事件(INSERT/UPDATE/DELETE);
  • 在 Flink 侧暴露为一张 动态表(Dynamic Table),可以用 Flink SQL / Table API / DataStream 来消费。

这背后依赖的是 SQL Server 自带的 CDC 功能 + Debezium SQLServer Connector,Flink CDC 负责:

  • 管理快照与增量衔接;
  • 管理 Checkpoint / 状态;
  • 提供 Exactly-Once 语义;
  • 提供 SQL DDL 封装、类型映射、指标监控等。

二、依赖与环境准备

2.1 Maven 依赖(项目开发)

在工程中加入:

xml 复制代码
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-sqlserver-cdc</artifactId>
    <version>3.5.0</version>
</dependency>

如果你是用 Flink SQL Client / Standalone 集群:

  1. 下载 flink-sql-connector-sqlserver-cdc-3.5.0.jar
  2. 放到 <FLINK_HOME>/lib/ 目录;
  3. 重启 Flink 集群后即可在 DDL 中使用 connector = 'sqlserver-cdc'

SQL Server 驱动一般由 Flink 所在 JVM 使用的 JDBC 驱动提供(通常是 mssql-jdbc),实际部署时记得确保集群 classpath 中有对应驱动 jar。

三、SQL Server 端准备:启用 CDC

和 Oracle、MySQL 不同,SQL Server 本身就自带 CDC 能力,只是默认是关闭的。要结合 Flink 使用,分两步:

  1. 对数据库启用 CDC(DB 级别);
  2. 对具体表启用 CDC(Table 级别)。

文档里重点讲的是"对表启用 CDC",前置条件:数据库已经开启 CDC、SQL Server Agent 已经运行。

3.1 前置条件检查

  • 目标数据库已经开启 CDC;
  • SQL Server Agent 正在运行(负责定期扫描日志写 CDC 表);
  • 当前账号是该数据库的 db_owner 成员。

3.2 为指定表启用 CDC

示例(以 MyDB.dbo.MyTable 为例):

sql 复制代码
USE MyDB
GO

EXEC sys.sp_cdc_enable_table
    @source_schema   = N'dbo',      -- 源表 schema
    @source_name     = N'MyTable',  -- 源表名
    @role_name       = N'MyRole',   -- 有权限查询 CDC 表的角色
    @filegroup_name  = N'MyDB_CT',  -- CDC 变更表所在文件组(建议单独文件组)
    @supports_net_changes = 0       -- 是否支持"净变更"查询,这里先关
GO

几点说明:

  • @role_name:可以建一个 MyRole 角色,把需要访问 CDC 变更表的用户加入进来;
  • @filegroup_name:CDC 会为每个表创建对应的 change table,建议放到单独 filegroup,避免和业务表混在一起;
  • @supports_net_changes:是否开启 CDC 的"净变更"功能(即汇总一段时间内的最终变化),通常 Flink CDC 不需要。

3.3 验证 CDC 配置与权限

使用 sys.sp_cdc_help_change_data_capture 查看当前库的 CDC 表配置:

sql 复制代码
USE MyDB;
GO
EXEC sys.sp_cdc_help_change_data_capture;
GO

返回结果包含:

  • 哪些表启用了 CDC;
  • 每个表对应的 CDC 表名(capture instance);
  • 当前用户是否有权限访问。

如果结果为空,说明:

  • 要么当前库根本没启用 CDC;
  • 要么当前用户没有权限访问 CDC 表。

CDC 源表在 Flink 里就是一张普通的表,DDL 例如:

sql 复制代码
CREATE TABLE orders (
    id         INT,
    order_date DATE,
    purchaser  INT,
    quantity   INT,
    product_id INT,
    PRIMARY KEY (id) NOT ENFORCED
) WITH (
    'connector'    = 'sqlserver-cdc',
    'hostname'     = 'localhost',
    'port'         = '1433',
    'username'     = 'sa',
    'password'     = 'Password!',
    'database-name'= 'inventory',
    'table-name'   = 'dbo.orders'
);

-- 直接查询快照 + CDC 变更
SELECT * FROM orders;

注意 table-name 的格式是:schema.table,例如 dbo.orders

五、核心 Connector 参数说明

5.1 基础连接参数

参数名 必填 说明
connector 固定为 sqlserver-cdc
hostname SQL Server 主机名 / IP
port 默认 1433
username 登录用户名
password 登录密码
database-name 要监控的数据库名
table-name 要监控的表名,如 dbo.orders
server-time-zone 会话时区,默认 UTC,如需要可设为 Asia/Shanghai

5.2 增量快照与 chunk 相关参数

SQLServer CDC 也支持增量快照机制,这意味着可以并行读取快照数据,然后切换到 CDC 流:

  • scan.incremental.snapshot.enabled:是否启用增量快照(默认 true

  • scan.incremental.snapshot.chunk.key-column:快照切片字段

    • 默认是主键第一列;
    • 可以指定非主键列,但有潜在一致性问题(后面专门讲)。
  • chunk-meta.group.size:chunk 元信息分组大小,默认 1000

  • chunk-key.even-distribution.factor.lower-bound / upper-bound

    • 用于判断 chunk key 分布是否"足够均匀",进而决定是否按范围均匀切块;
    • 分布因子计算公式大致为:

      distributionFactor=MAX(id)−MIN(id)+1rowCount\]\[ distributionFactor = \\frac{MAX(id) - MIN(id) + 1}{rowCount} \]\[distributionFactor=rowCountMAX(id)−MIN(id)+1

如果列值分布均匀,Flink 可以用简单的范围切分;否则需要多跑几次查询做动态切分。

5.3 Debezium 参数透传

通过 debezium.* 可以把参数传给 Debezium SQLServer Connector,例如:

sql 复制代码
'debezium.snapshot.mode' = 'initial_only'

注意:
scan.startup.mode 的实现依赖 Debezium 的 snapshot.mode不要 在 DDL 里同时配置两者,否则可能导致 scan.startup.mode 不生效。

5.4 其他常用参数

  • scan.incremental.close-idle-reader.enabled:快照结束后是否关闭空闲 reader(默认 false);

  • 对于 Checkpoint 行为还有 Flink 版本约束:

    • Flink ≥ 1.14,才支持 execution.checkpointing.checkpoints-after-tasks-finish.enabled
    • Flink ≥ 1.15,该配置默认就是 true

六、启动模式:scan.startup.mode

scan.startup.mode 控制 Flink SQLServer CDC 的启动行为,目前支持两种:

  • initial(默认)

    • 扫描表结构 + 全量快照数据;
    • 适用于:需要让下游拥有完整历史数据。
  • latest-offset

    • 只扫描表结构,不读历史数据;
    • 适用于:只关心"从现在开始"的变更。

例如:

sql 复制代码
CREATE TABLE orders (...) WITH (
  'connector'        = 'sqlserver-cdc',
  ...
  'scan.startup.mode'= 'latest-offset'
);

再强调一次:
scan.startup.mode 底层用的是 Debezium 的 snapshot.mode不要和 debezium.snapshot.mode 同时配置

七、一个重要限制:快照阶段无法做 checkpoint

与 Oracle CDC 类似,SQLServer CDC 在扫描快照阶段有一个限制:

在快照扫描期间缺少可恢复位点,源算子无法真正完成 Checkpoint。

Flink 的实际行为是:

  • checkpoint 会一直等到超时;
  • 超时后将该 checkpoint 标记为失败;
  • 如果没做额外配置,Flink 会认为作业失败,从而触发 failover。

对于大表快照,官方建议调高容忍度:

yaml 复制代码
execution.checkpointing.interval: 10min
execution.checkpointing.tolerable-failed-checkpoints: 100

restart-strategy: fixed-delay
restart-strategy.fixed-delay.attempts: 2147483647

含义:

  • 放宽 checkpoint 间隔;
  • 允许大量失败的 checkpoint;
  • 使用 fixed-delay 重启策略,并把重启次数设得非常大。

八、DataStream API 使用方式

SQLServer CDC 既可以通过 Flink SQL 使用,也可以在 DataStream 中直接作为 Source。

8.1 SourceFunction 单线程模式

最经典的写法,只能单并行读取:

java 复制代码
SourceFunction<String> sourceFunction = SqlServerSource.<String>builder()
    .hostname("localhost")
    .port(1433)
    .database("inventory")         // 监控的数据库
    .tableList("dbo.products")     // 监控的表
    .username("sa")
    .password("Password!")
    .deserializer(new JsonDebeziumDeserializationSchema()) // Debezium Record -> JSON String
    .build();

StreamExecutionEnvironment env =
        StreamExecutionEnvironment.getExecutionEnvironment();

env.addSource(sourceFunction)
   .print()
   .setParallelism(1); // 保证顺序

env.execute("SqlServer CDC Source Example");

文档提到:SQLServer CDC 源在变更流阶段只能单线程读取,也就是说真正的 CDC 流只能由一个任务消费,避免乱序。

8.2 增量 CDC Source(2.4.0 之后)

从 Flink CDC 2.4.0 开始提供了一个 增量 CDC Source,支持快照阶段并行:

java 复制代码
SqlServerIncrementalSource<String> sqlServerSource =
        new SqlServerSourceBuilder()
                .hostname("localhost")
                .port(1433)
                .databaseList("inventory")
                .tableList("dbo.products")
                .username("sa")
                .password("Password!")
                .deserializer(new JsonDebeziumDeserializationSchema())
                .startupOptions(StartupOptions.initial())
                .build();

StreamExecutionEnvironment env =
        StreamExecutionEnvironment.getExecutionEnvironment();

env.enableCheckpointing(3000);

env.fromSource(
        sqlServerSource,
        WatermarkStrategy.noWatermarks(),
        "SqlServerIncrementalSource")
   .setParallelism(2)   // 快照阶段并行度
   .print()
   .setParallelism(1);

env.execute("Print SqlServer Snapshot + Change Stream");

简单理解:

  • 快照阶段可以并行读取,提高首发同步速度;
  • 切换到变更流后仍然由单任务读取 CDC 事件,保证顺序。

九、无主键表 & chunk key 风险

从 3.4.0 开始,SQLServer CDC 支持无主键表,用法与 MySQL / Oracle CDC 一致:

  1. 必须配置:

    sql 复制代码
    'scan.incremental.snapshot.chunk.key-column' = 'your_column'
  2. 最好选择:

    • 非空字段;
    • 有索引;
    • 尽量不会被更新。

语义上:

  • 如果该列不会被更新 → 仍可以保持 Exactly-Once;
  • 如果该列会被更新 → 只能保证 At-Least-Once,需要下游按业务主键做幂等处理。

9.1 官方警告场景

  • 表结构:

    • 主键:id
    • chunk key:pid(非主键)
  • 切片:

    • Split 0:1 < pid <= 3
    • Split 1:3 < pid <= 5
  • 两个并行 task 正在分别读取 Split 0 和 Split 1;

  • 此时有一条记录:id=0pid2 → 4

  • 结果:

    • Split 0 缓存 [id=0, pid=2]
    • Split 1 缓存 [id=0, pid=4]

由于处理顺序不可控,最终下游看到的 pid 可能是 2,也可能是 4,产生不一致。

结论:

优先使用主键作为 chunk key;

无主键表或非主键 chunk key 时,要谨慎评估"一致性 vs 性能"。

十、元数据列与 CDC 指标

10.1 可用元数据列(VIRTUAL)

可以通过 VIRTUAL 元数据列,拿到各种"来源信息":

  • table_name:表名
  • schema_name:schema 名
  • database_name:数据库名
  • op_ts:变更发生时间(快照读出来的记录该值为 0)

示例:

sql 复制代码
CREATE TABLE products (
    table_name   STRING           METADATA FROM 'table_name'   VIRTUAL,
    schema_name  STRING           METADATA FROM 'schema_name'  VIRTUAL,
    db_name      STRING           METADATA FROM 'database_name'VIRTUAL,
    operation_ts TIMESTAMP_LTZ(3) METADATA FROM 'op_ts'        VIRTUAL,

    id           INT NOT NULL,
    name         STRING,
    description  STRING,
    weight       DECIMAL(10,3)
) WITH (
    'connector'    = 'sqlserver-cdc',
    'hostname'     = 'localhost',
    'port'         = '1433',
    'username'     = 'sa',
    'password'     = 'Password!',
    'database-name'= 'inventory',
    'table-name'   = 'dbo.products'
);

10.2 Source Metrics

SQLServer CDC 按 database.schema.table 维度暴露指标,例如:

  • isSnapshotting:当前是否在做快照;
  • isStreamReading:是否在读变更流;
  • numTablesSnapshotted / numTablesRemaining:多表快照进度;
  • numSnapshotSplitsProcessed / Remaining / Finished:快照分片处理进度;
  • snapshotStartTime / snapshotEndTime:快照开始/结束时间。

这些指标可以接入 Prometheus + Grafana,做一套 CDC 作业监控看板,观察:

  • 首发同步进度;
  • CDC 延迟;
  • 各表健康状态。

Flink 已经帮我们预先做好了 SQLServer 到 Flink SQL 的类型映射,这里简单列一下主流类型:

SQLServer 类型 Flink SQL 类型
char(n) CHAR(n)
varchar(n) / nvarchar(n) / nchar(n) VARCHAR(n)
text / ntext / xml STRING
decimal(p, s) / money / smallmoney DECIMAL(p, s)
numeric NUMERIC
float / real DOUBLE
bit BOOLEAN
int INT
tinyint / smallint SMALLINT
bigint BIGINT
date DATE
time(n) TIME(n)
datetime / smalldatetime / datetime2 TIMESTAMP(n)
datetimeoffset TIMESTAMP_LTZ(3)

有些"超长精度"的 decimal / money 在 Flink 侧会按 DECIMAL(p, s) 或 NUMERIC 处理,注意保持 p、s 一致,以免精度丢失。

对于传统的 SQL Server 核心系统,Flink SQLServer CDC Connector 提供了一条非常稳妥的实时化路径:

  • 不侵入业务代码;
  • 利用 SQL Server 原生 CDC 机制;
  • 在 Flink 侧统一使用 SQL / DataStream 做实时计算;
  • 可以很自然地把数据推送到 Kafka、Doris、StarRocks、Iceberg、ES 等下游。

落地实践建议:

  1. 从一两张关键表(订单、库存)试点,先验证 CDC 配置、Flink 作业稳定性;
  2. 对大表快照提前调好 checkpoint 策略,避免首发同步阶段频繁 failover;
  3. 充分利用增量快照能力,加快初次同步速度;
  4. 针对无主键表、chunk key、顺序一致性这些地方,提前设计好下游幂等策略;
  5. 接入 CDC Metrics 做"可观测",把 CDC 作业当基础设施来运维。
相关推荐
TG:@yunlaoda360 云老大4 小时前
谷歌云Flink 核心组成及生态发展:实时数据处理的下一代引擎
大数据·flink·googlecloud
JavaBoy_XJ4 小时前
电商系统中ES检索技术设计和运用
大数据·elasticsearch·搜索引擎
nini_boom11 小时前
**论文初稿撰写工具2025推荐,高效写作与智能辅助全解析*
大数据·python·信息可视化
小园子的小菜12 小时前
Elasticsearch高阶用法实战:从数据建模到集群管控的极致优化
大数据·elasticsearch·搜索引擎
源码之家13 小时前
机器学习:基于大数据二手房房价预测与分析系统 可视化 线性回归预测算法 Django框架 链家网站 二手房 计算机毕业设计✅
大数据·算法·机器学习·数据分析·spark·线性回归·推荐算法
Ctrl+S 之后13 小时前
新型多模态交互系统如何推动未来沉浸式数字体验全面进化的技术革新路线解析
flink
布吉岛没有岛_15 小时前
Hadoop学习_week1
大数据·hadoop
阿里云大数据AI技术17 小时前
云栖实录 | 洋钱罐基于 EMR Serverless 产品构建全球一体化数字金融平台
大数据·运维
正在走向自律20 小时前
大数据时代时序数据库选型指南:从技术架构到实战案例
大数据·架构·时序数据库