Flink DynamoDB Connector 用 Streams 做 CDC,用 BatchWriteItem 高吞吐写回

1、先搞清楚:DataStream 连接器 vs SQL 连接器(版本现状)

很多人看到 "Flink 2.2 文档里有 DynamoDB SQL Connector" 会以为可以直接 CREATE TABLE ... WITH ('connector'='dynamodb') 开干,但需要注意:

  • Flink 文档里 DynamoDB SQL Connector 在 Flink 2.2 版本标注为:还没有可用的 connector 依赖 (也就是:文档有,但 jar 还没发出来/不可用)。 (Apache Nightlies)
  • 目前更成熟、可直接用的是 DataStream ConnectorDynamoDbStreamsSource + DynamoDbSink。 (Apache Nightlies)

如果你现在要落地"实时 CDC + 写回",建议优先走 DataStream 方案;SQL 方案可以先关注进展,等依赖正式可用再迁移。

2、整体链路长什么样

一个典型的实时链路(非常常见,也很好用):

AWS 官方也有 "DynamoDB Streams + Flink" 的使用说明,核心就是"消费 Streams 记录并实时处理"。 (AWS 文檔)

3、Source:DynamoDbStreamsSource 读取 CDC

3.1 必备前置:Streams 与 StreamViewType

DynamoDbStreamsSource 读的是 DynamoDB Streams;而 Streams 事件里到底带不带 NEW_IMAGE/OLD_IMAGE,取决于你在 DynamoDB 表上配置的 StreamViewType。Flink 文档也强调:实际事件内容由 StreamViewType 决定。 (Apache Nightlies)

建议你在建表/改表时就想清楚:

  • 只做主键级路由:KEYS_ONLY
  • 做增量同步/回写:通常要 NEW_IMAGENEW_AND_OLD_IMAGES

3.2 起始位点:LATEST vs TRIM_HORIZON(非常关键)

Flink 里通过 DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_POSITION 设定起始位置: (Apache Nightlies)

  • LATEST:从最新开始读
  • TRIM_HORIZON:从最早可读开始读,但 DynamoDB Streams 数据会在 24 小时后被裁剪 (意味着你想"从头补历史",最多也就补 24 小时)。 (Apache Nightlies)

3.3 代码骨架:从 Stream ARN 启动 Source + 水位线

java 复制代码
// 1) 配置 Source
Configuration sourceConfig = new Configuration();
sourceConfig.set(
    DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_POSITION,
    DynamodbStreamsSourceConfigConstants.InitialPosition.TRIM_HORIZON // 默认 LATEST
);

// 2) 构建 DynamoDbStreamsSource
DynamoDbStreamsSource<MyChangeEvent> source =
    DynamoDbStreamsSource.<MyChangeEvent>builder()
        .setStreamArn("arn:aws:dynamodb:us-east-1:1231231230:table/test/stream/2024-04-11T07:14:19.380")
        .setSourceConfig(sourceConfig)
        .setDeserializationSchema(new MyDdbStreamsDeser())
        .build();

// 3) 加入执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

DataStream<MyChangeEvent> stream =
    env.fromSource(
            source,
            WatermarkStrategy.<MyChangeEvent>forMonotonousTimestamps()
                .withIdleness(Duration.ofSeconds(1)),
            "DynamoDB Streams source"
        )
        .uid("custom-uid");

上面这套写法与官方示例一致:Stream ARN 指定流、Configuration 提供参数、WatermarkStrategy 可用 approximateCreationDateTime 做事件时间。 (Apache Nightlies)

3.4 反序列化:实现 DynamoDbStreamsDeserializationSchema

你需要实现 DynamoDbStreamsDeserializationSchema<T>,把 AWS Streams 的 Record 转成你自己的事件类型。Flink 文档明确:deserialize 接收的就是 DynamoDB Streams 的 Record。 (Apache Nightlies)

下面给一个"够用、好改"的事件结构(更偏工程落地):

java 复制代码
public class MyChangeEvent {
    public String eventName; // INSERT / MODIFY / REMOVE
    public Instant eventTime;
    public Map<String, AttributeValue> keys;
    public Map<String, AttributeValue> newImage;
    public Map<String, AttributeValue> oldImage;
}

对应的反序列化大致这样:

java 复制代码
public class MyDdbStreamsDeser implements DynamoDbStreamsDeserializationSchema<MyChangeEvent> {

    @Override
    public void deserialize(Record record, Collector<MyChangeEvent> out) {
        var dd = record.dynamodb();
        MyChangeEvent e = new MyChangeEvent();
        e.eventName = record.eventNameAsString();
        e.eventTime = dd.approximateCreationDateTime(); // 事件时间
        e.keys = dd.keys();
        e.newImage = dd.newImage();
        e.oldImage = dd.oldImage();
        out.collect(e);
    }

    @Override
    public TypeInformation<MyChangeEvent> getProducedType() {
        return TypeInformation.of(MyChangeEvent.class);
    }
}

3.5 顺序性与分片:你能指望的"有序"边界在哪里

这块很容易被误解,Flink 文档讲得很清楚:

  • 同一个主键(primary key)内是有序的:因为同一主键会写入同一 shard lineage
  • shard split(父 shard 分裂成子 shard)时,只要先把父 shard 读完,再读子 shard,就能保持顺序
  • DynamoDbStreamsSource 会保证 shard 分配遵循父子关系:父 shard 读完,子 shard 才会进入分配逻辑 (Apache Nightlies)

结论很实用:

  • 你要做"同一主键内严格顺序处理",可以放心
  • 你要做"全局严格顺序",那不属于 Streams 的能力边界(要换设计)

3.6 Shard 分配策略

默认用 UniformShardAssigner:把 shard 尽量均匀分给并行子任务,新 shard 会给当前 shard 最少的 subtask。你也可以实现自定义 assigner。 (Apache Nightlies)

3.7 可靠性相关配置:重试与 shard discovery

Flink 文档列出了关键配置项: (Apache Nightlies)

  • 重试策略(AWS SDK v2):

    • DYNAMODB_STREAMS_RETRY_COUNT
    • DYNAMODB_STREAMS_EXPONENTIAL_BACKOFF_MIN_DELAY
    • DYNAMODB_STREAMS_EXPONENTIAL_BACKOFF_MAX_DELAY
  • shard discovery:

    • 默认每 60 秒发现新 shard,可用 SHARD_DISCOVERY_INTERVAL 调小
    • 若 shard graph 不一致,会自动检测并重试,重试次数由 DESCRIBE_STREAM_INCONSISTENCY_RESOLUTION_RETRY_COUNT 控制

4、Sink:DynamoDbSink 用 BatchWriteItem 高吞吐写入

4.1 Maven 依赖与版本

以 Flink 1.17 文档为例,DataStream DynamoDB Sink 的依赖形如: (Apache Nightlies)

xml 复制代码
<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-connector-dynamodb</artifactId>
  <version>4.2.0-1.17</version>
</dependency>

不同 Flink 版本会对应不同 connector 版本号,建议你以目标 Flink 版本的官方文档为准。

4.2 代码骨架:builder + 参数调优位

java 复制代码
Properties sinkProperties = new Properties();
sinkProperties.put(AWSConfigConstants.AWS_REGION, "eu-west-1");
// 建议生产别写死 AK/SK:可以走环境变量 / IAM Role
sinkProperties.put(AWSConfigConstants.AWS_ACCESS_KEY_ID, "aws_access_key_id");
sinkProperties.put(AWSConfigConstants.AWS_SECRET_ACCESS_KEY, "aws_secret_access_key");

ElementConverter<MyOut, DynamoDbWriteRequest> converter = new MyElementConverter();

DynamoDbSink<MyOut> sink =
    DynamoDbSink.<MyOut>builder()
        .setDynamoDbProperties(sinkProperties)            // 必填
        .setTableName("my-dynamodb-table")                // 必填
        .setElementConverter(converter)                   // 必填
        .setOverwriteByPartitionKeys(List.of("pk"))       // 可选:批内去重
        .setFailOnError(false)                            // 可选:错误是否致命
        .setMaxBatchSize(25)                              // 可选:默认 25
        .setMaxInFlightRequests(50)                       // 可选:并发
        .setMaxBufferedRequests(10_000)                   // 可选:缓冲
        .setMaxTimeInBufferMS(5000)                       // 可选:最大滞留时间
        .build();

stream.sinkTo(sink);

这套参数在官方文档中都有对应解释。 (Apache Nightlies)

4.3 牢记 BatchWriteItem 的硬上限(否则你会"莫名其妙写不进去")

DynamoDB 的 BatchWriteItem 有明确限制:一次请求最多 25 个 put/delete 操作,整个请求体最多 16MB 。 (AWS 文檔)

所以:

  • setMaxBatchSize(25) 不是拍脑袋,是 API 天花板
  • 如果单条记录很大,即使没到 25 条也可能触发大小限制(需要做字段裁剪、拆分表设计或降维)

4.4 ElementConverter:把 DataStream 元素变成 DynamoDbWriteRequest

Sink 的关键就是 ElementConverter<InputType, DynamoDbWriteRequest>:负责把你的对象转为 DynamoDB 的 put/delete 请求。 (Apache Nightlies)

一个常见的"写入 PutRequest"骨架(示意):

java 复制代码
public class MyElementConverter implements ElementConverter<MyOut, DynamoDbWriteRequest> {
    @Override
    public DynamoDbWriteRequest apply(MyOut v) {
        Map<String, AttributeValue> item = new HashMap<>();
        item.put("pk", AttributeValue.builder().s(v.getPk()).build());
        item.put("sk", AttributeValue.builder().s(v.getSk()).build());
        item.put("cnt", AttributeValue.builder().n(String.valueOf(v.getCnt())).build());

        return DynamoDbWriteRequest.builder()
            .putRequest(PutRequest.builder().item(item).build())
            .build();
    }
}

如果你不想写 converter,Flink 也提供了一些默认实现思路:例如从 POJO/Row/TypeInformation 推导 schema,或者用 @DynamoDbBean 相关的 converter。 (Apache Nightlies)

4.5 幂等与去重:overwriteByPartitionKeys 怎么用才不坑

setOverwriteByPartitionKeys(partitionKeys) 的语义是:在"每个 batch 内 "对相同分区键的写请求做去重(典型是"后来的覆盖前面的"),避免一个 batch 内同一主键被重复写导致浪费。 (Apache Nightlies)

它不是"全局去重",也不是"事务 exactly-once"。如果你要端到端更强语义,建议:

  • 输出表写入设计为"幂等覆盖"(同 pk 写入覆盖)
  • 或引入业务版本号/事件时间戳,保证"最后写入生效"
  • 或在 Flink 侧做去重/聚合后再写

5、本地/测试环境:Localstack 或自定义 Endpoint

做集成测试时,很常见要写到 Localstack,或写到 VPC Endpoint。Flink 文档给了明确做法:设置 AWSConfigConstants.AWS_ENDPOINT,并同时设置 AWS_REGION(region 用于对 endpoint URL 做签名)。 (Apache Nightlies)

java 复制代码
Properties cfg = new Properties();
cfg.put(AWSConfigConstants.AWS_REGION, "eu-west-1");
cfg.put(AWSConfigConstants.AWS_ACCESS_KEY_ID, "test");
cfg.put(AWSConfigConstants.AWS_SECRET_ACCESS_KEY, "test");
cfg.put(AWSConfigConstants.AWS_ENDPOINT, "http://localhost:4566"); // Localstack

这招对 Source 和 Sink 都适用,尤其适合做端到端回归测试。

如果你之前用过老的消费方式(比如 FlinkDynamoDBStreamsConsumer 一类),Flink 社区也发布过迁移说明:可以迁移到新的 DynamoDbStreamsSource,接口更统一、可读性更好。 (Apache Flink)

7、一份"上线前自检清单"(很值)

  1. Streams 起始位点

    想追历史就用 TRIM_HORIZON,但别忘了 Streams 只保 24 小时 。 (Apache Nightlies)

  2. StreamViewType 配对你的业务

    不带 new/old image,你反序列化拿不到字段。

  3. 顺序性边界

    只保证"同一主键内有序",跨主键不要幻想全局顺序。 (Apache Nightlies)

  4. Sink 的 BatchWriteItem 限制

    单次最多 25 条、最多 16MB。吞吐上去以后,很多"写失败/变慢"本质都是触顶。 (AWS 文檔)

  5. 压力控制

    通过 maxInFlightRequests/maxBufferedRequests/maxTimeInBufferMS 找到吞吐与延迟的平衡点(别只看 QPS,先把延迟曲线画出来)。

  6. 测试环境

    用 Localstack 时记得同时设 endpoint + region。 (Apache Nightlies)

Flink 2.2 的文档里确实有 DynamoDB SQL Connector 的建表示例和参数列表,但依赖部分明确写了"还没有 connector 可用"。 (Apache Nightlies)

如果你想先把 SQL DDL 写好做"未来迁移预案",可以保留类似:

sql 复制代码
CREATE TABLE DynamoDbTable (
  user_id BIGINT,
  item_id BIGINT,
  category_id BIGINT,
  behavior STRING
) WITH (
  'connector' = 'dynamodb',
  'table-name' = 'user_behavior',
  'aws.region' = 'us-east-2'
);

但落地执行层面,现阶段仍建议 DataStream 方案先跑起来。

相关推荐
早日退休!!!2 小时前
内存泄露(Memory Leak)核心原理与工程实践报告
大数据·网络
gc_22992 小时前
学习python调用dmpython库获取达梦数据库模式信息的基本方式
python·dmpython
reasonsummer2 小时前
【教学类-130-01】20260118对称汉字剪纸28个
python
victory04312 小时前
minimind SFT失败原因排查和解决办法
人工智能·python·深度学习
发哥来了2 小时前
主流AI视频生成工具商用化能力评测:五大关键维度对比分析
大数据·人工智能·音视频
曲幽2 小时前
Django入门指南:Python Web开发的“瑞士军刀”
python·django·flask·fastapi·web·pythonweb
m0_748249542 小时前
Java 语言提供了八种基本类型【文123】
java·开发语言·python
無森~2 小时前
MapReduce
大数据·mapreduce
移幻漂流2 小时前
Kotlin 如何解决 Java 的核心痛点:现代语言特性的深度剖析
java·python·kotlin