当前位置: 首页 > 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 容错机制与事件重复的处理思路。

http://www.xdnf.cn/news/787663.html

相关文章:

  • Deep Search之R1-Searcher系列
  • QT实现动画翻转效果
  • Docker 镜像深度剖析:构建、管理与优化
  • 多模态知识图谱可视化构建(neo4j+python+flask+vue环境搭建与示例)
  • 秋招准备-数据结构
  • 前端面试题之Class详解
  • @Resource和@Autowire
  • 《前端面试题:CSS预处理器(Sass、Less等)》
  • 代码训练LeetCode(19)轮转数组
  • 【学习记录】深入解析 AI 交互中的五大核心概念:Prompt、Agent、MCP、Function Calling 与 Tools
  • 全球常用地理信息、遥感数据处理软件介绍(单机版、在线云平台)
  • LeetCode 高频 SQL 50 题(基础版) 之 【高级查询和连接】· 下
  • 【Typst】5.文档结构元素与函数
  • 突破视觉认知边界VisionReasoner:用强化学习统一视觉感知与推理的全能框架
  • 防火墙在OSI模型中的层级工作(2025)
  • 动态规划十大经典题型状态转移、模版等整理(包括leetcode、洛谷题号)
  • Dify-5:Web 前端架构
  • Spring 统一异常处理怎么做?
  • QT 5.9.2+VTK8.0实现等高线绘制
  • Ubuntu中SSH服务器安装使用
  • VMWare安装常见问题
  • MATLAB仿真生成无线通信网络拓扑推理数据集
  • HTTP连接管理——短连接,长连接,HTTP 流水线
  • 【notepad++】如何设置notepad++背景颜色?
  • 在Oxygen编辑器中使用DeepSeek
  • typescript的Interface和Type
  • DPO 算法
  • Gradle依赖管理全面指南:从基础到高级实践
  • Spring Boot整合Druid与Dynamic-Datasource多数据源配置:从错误到完美解决
  • 力扣HOT100之多维动态规划:1143. 最长公共子序列