Flink 重启后事件被重复消费的原因与解决方案

在使用 Flink 构建流处理系统的过程中,我们通常会依赖其高可用性和状态一致性来确保系统稳定运行。然而,最近我遇到了一次事件被重复消费的问题, 客户的反馈表现是实时报表的数据一直不动, 怎么都没有变化, 查看消费的事件表明了是因为事件被重复消费了,虽然最终查明原因不复杂,但背后的机制却值得深入探讨。本文记录这个问题的背景、分析过程、成因推理与解决方案,希望能对遇到类似问题的开发者有所帮助。

一、问题背景

在一个任务流中,task-manager 发出了一个唯一 ID 的事件,该事件在 Flink Job 中被下游消费处理。然而,我们在日志系统中却看到该事件被处理了 4 次

  • 事件发送时间2025-04-08T00:25:27.731416Z

  • 第一次消费2025-04-08T00:36:57.305814Z

  • 第二次消费2025-04-08T00:36:57.318966Z

  • 第三次消费2025-04-08T00:54:22.906435Z

  • 第四次消费2025-04-08T01:24:51.371913Z

经过排查,我们发现 Flink Job 在 2025-04-08T01:00:03 的确发生了一次崩溃和自动重启。而这次重启正是事件被重复消费的"元凶"。


二、问题分析

Flink 通过 Checkpoint + State 恢复 实现容错。每当 Job 崩溃或手动恢复时,Flink 会尝试从最近的成功 checkpoint 中恢复状态。这包括:

  • 算子的内部状态(如窗口、聚合中间值)

  • Source 的读取位置(如 Kafka 的 offset)

但关键点在于:Flink 的 Source 并不是实时提交 offset,而是伴随 checkpoint 一起提交的。

2. Exactly-Once vs At-Least-Once

Flink 的语义支持如下两种模式:

  • At-Least-Once(至少一次):默认模式,恢复后可能重复处理;

  • Exactly-Once(精确一次):通过精确控制状态 + Source offset + Sink 幂等机制来保障事件不会被多处理。

若我们在 Sink 阶段没有做幂等保障,又未使用支持 Exactly-Once 的 Source/Sink 组合,那么重复处理是不可避免的。


三、事件重复的具体原因

回顾我们的案例:

Flink Job 在 01:00:03 down 掉并重启,恢复自上一次 checkpoint。由于事件 ID 对应的事件在 checkpoint 后才进入 Flink,因此未被标记为已处理。重启后,从旧的 offset 重新读取,自然就再次处理了相同事件

结合事件消费时间和 Down 时间戳,能推测出:

  • 第 3 次消费是在 job down 之前;

  • 第 4 次消费发生在重启之后,重复来自恢复逻辑;

  • 第 1、2 次消费相隔 13ms,可能是 Sink 写入失败/超时触发了 retry 或 parallel instance 之间调度导致的二次执行。


四、如何避免这类问题?

1. 启用 Exactly-Once Checkpoint 模式

复制代码
StreamExecutionEnvironment env = 
StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(5000, CheckpointingMode.EXACTLY_ONCE);

并确保:

  • Kafka Source 使用 FlinkKafkaConsumer / FlinkKafkaSource 并开启 checkpoint 支持;

  • Sink 也支持两阶段提交或幂等语义(如 Kafka、JDBC with XA 支持、幂等 Redis 写入等);

2. Source 和 Sink 配合使用事务或幂等逻辑

例如:

  • Kafka Source 配合 Kafka Sink(开启事务写入);

  • Kafka Source 配合 Redis Sink,在 Sink 阶段做 SETNX 或幂等校验;

  • JDBC Sink 使用 Flink 官方 JdbcSink.sink(),开启批处理、自动提交、幂等更新逻辑等。

3. 引入事件去重机制

在业务上为每个事件设计唯一 ID(如 UUID 或业务主键),Sink 阶段查询是否已处理过,避免重复处理。

比如 Redis 实现:

复制代码
if (redis.setnx(eventId, 1)) {
    redis.expire(eventId, 24 * 3600); // 设置过期,避免内存堆积
    // 真正处理逻辑
} else {
    // 已处理,跳过
}

4. 提升 Checkpoint 成功率和频率

  • 如果 checkpoint 间隔过长,宕机后会丢失更多"已处理但未保存"的事件;

  • 建议将间隔设置在 5-10s,具体依据系统负载调整;

  • 保证 checkpoint 成功率,尤其避免某些状态太大、Sink 卡顿影响 checkpoint 成功;


五、总结

Flink 是一个强大但"状态驱动"的系统,一切幂等、容错、精确语义的背后,都依赖 checkpoint 的精确控制和 Source/Sink 的协同。

这次事件提醒我们:

Flink 重启后并不是"理所当然"地继续执行,而是带着上一次 checkpoint 的记忆"穿越回来"继续工作。

如果中间没有状态标记,事件自然可能被重复读取和处理。

因此我们在实际使用 Flink 时,应从以下几个方面着手提高系统的鲁棒性:

  1. 明确语义需求(Exactly-Once vs At-Least-Once);

  2. 配置恰当的 checkpoint 策略;

  3. 选择合适的 Source/Sink 并配合幂等/去重机制;

  4. 关注 Flink 的 job 生命周期,监控重启、失败与 checkpoint 状态。


六、后记

本次事件虽然只是一条消息的重复处理,但在某些业务系统中(如支付、扣款、异步通知)将会造成严重后果。事件幂等性Flink 的状态一致性,应该是流处理工程师每日关注的基本功。

希望这篇文章能帮你更好地理解 Flink 容错机制与事件重复的处理思路。

相关推荐
字节数据平台15 分钟前
火山引擎发布Data Agent新能力,推动用户洞察进入“智能3.0时代”
大数据·人工智能
TDengine (老段)23 分钟前
TDengine 字符串函数 CHAR_LENGTH 用户手册
大数据·数据库·时序数据库·tdengine·涛思数据
TDengine (老段)26 分钟前
TDengine 数学函数 CRC32 用户手册
java·大数据·数据库·sql·时序数据库·tdengine·1024程序员节
数智顾问39 分钟前
(111页PPT)大型集团IT治理体系规划详细解决方案(附下载方式)
大数据·人工智能
geneculture1 小时前
官学商大跨界 · 产学研大综合:融智学新范式应用体系
大数据·人工智能·物联网·数据挖掘·哲学与科学统一性·信息融智学
青云交7 小时前
Java 大视界 -- 基于 Java 的大数据实时流处理在能源行业设备状态监测与故障预测中的应用
flink·lstm·设备状态监测·故障预测·实时流处理·java 大数据·能源行业
唐兴通个人8 小时前
人工智能Deepseek医药AI培训师培训讲师唐兴通讲课课程纲要
大数据·人工智能
梦里不知身是客118 小时前
spark读取table中的数据【hive】
大数据·hive·spark
赞奇科技Xsuperzone10 小时前
DGX Spark 实战解析:模型选择与效率优化全指南
大数据·人工智能·gpt·spark·nvidia
努力成为一个程序猿.10 小时前
Flink集群部署以及作业提交模式详解
大数据·flink