当前位置: 首页 > news >正文

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 的确发生了一次崩溃和自动重启。而这次重启正是事件被重复消费的“元凶”。


二、问题分析

1. Flink 的容错机制

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 容错机制与事件重复的处理思路。

相关文章:

  • 极智项目 | 基于PyQT+Whisper实现的语音识别软件设计
  • Rust 学习笔记:使用自定义命令扩展 Cargo
  • Matlab2018a---安装教程
  • Bash shell四则运算
  • python,shell,linux,bash概念的不同和对比联系
  • isp调试 blend模式指什么
  • 深圳南柯电子|储能EMC整改:如何节省70%整改费用的实战方法
  • 对比ODR直接赋值的非原子操作和BSRR原子操作
  • 亚远景科技助力东风日产通过ASPICE CL2评估
  • 4-C#的不同窗口传值
  • 如何计算H5页面加载时的白屏时间
  • 如何进行页面前端监控
  • window 显示驱动开发-DirectX 视频加速 2.0
  • 如何用 pnpm patch 给 element-plus 打补丁修复线上 bug(以 2.4.4 修复 PR#15197 为例)
  • 资源-又在网上淘到金了-配乐下载
  • 8.RV1126-OPENCV 视频中添加LOGO
  • 实现对deepseek流式返回的json数据,进行逐字解析并实时渲染
  • Python中os模块详解
  • 蓝桥杯 k倍区间
  • [蓝桥杯]生物芯片
  • 自己建一个网站/整合营销的特点有哪些
  • 企业网站备案那么麻烦吗/微信指数查询入口
  • 简单的网站首页模板/万能导航网
  • 武汉最好的网站建设前十/电脑优化工具
  • win7 iis配置网站 视频教程/爱站网能不能挖掘关键词
  • 网站上线 串词/服装店营销策划方案