资讯动态

Flink状态一致性实战:从Checkpoint原理到端到端Exactly-Once

发布时间:2026/10/18 10:52:12 来源:尧图企业网站定制
1. 从一次线上事故说起状态丢了到底是谁的锅大概一年前我们团队维护的一个实时指标计算服务出了事故。现象很典型某天凌晨 Flink 作业从 checkpoint 恢复之后产出的一批分维度累计指标突然偏了而且不是简单地少了一段窗口数据是那种对不上的、脏的偏。排查了几个小时最后定位到的问题根源不在 Flink 本身而是作业里某个外部服务的数据在恢复窗口内发生了重复写入恰好下游那个接口不是幂等的于是累计值就凭空多了一截。这个事故给我的触动非常大。很多人一听到状态一致性第一反应是Flink 的 Exactly-Once 不是已经解决了吗但真实场景是Flink 的 Exactly-Once 只保证它自己内部的状态一致不保证你整个分布式链路的一致。你以为你开了 checkpoint设置了 EXACTLY_ONCE 语义就万事大吉了其实还差得远。这篇文章我不想从Flink 状态一致性是什么这种教科书式的问题开始。我更想以一次实际项目的视角把状态一致性这个命题拆开揉碎它到底在解决什么问题、Flink 的机制原理是什么、为什么端到端一致性才是真正的难点、以及我在实际项目中踩过哪些坑、怎么排查、怎么设计才靠谱。这篇内容适合正在用 Flink 做实时计算、或者准备在项目里引入 Flink 的工程师也适合那些虽然用了 Flink 但一直对 checkpiont 和状态机制半知半解的同学。2. 为什么状态会丢分布式环境下的一致性问题本质2.1 状态存在的两个层面内存计算与外部世界要理解一致性先得理解 Flink 里的状态到底是什么。状态在 Flink 里分为两大类一类是算子状态Operator State比如 Kafka Source 记录的消费位点另一类是键控状态Keyed State比如按用户 ID 聚合的累计下单金额、按传感器 ID 维护的最近一分钟温度窗口。这些状态本质上是跑在计算节点内存/本地磁盘里的可变数据。关键问题来了Flink 是分布式系统一个作业有多个并行子任务每个子任务管理自己的那份状态。当一个节点挂掉、或者网络分区导致某个子任务失联时这份状态如果只存在于该节点的内存里那就真的丢了。就算没丢恢复的时候你也不知道它是哪个时间点的状态。更麻烦的是流处理是无界数据时间一直在往前走你不可能把整个输入流重新完整地保存下来重放一遍。于是问题就变成了如何让分布在各节点上的状态在任何一个时间点上都有一份一致的、可恢复的快照。这个问题的难点不在于拍快照这个动作本身而在于拍快照不能打断数据流的处理。你当然可以简单粗暴地把计算停住等所有节点把状态落盘之后再继续跑但这样带来的停机时间在实时场景里完全不可接受。所以 Flink 的设计目标从一开始就很明确要一个异步的、不阻塞主处理流程的、分布式的、一致性的快照机制。2.2 一个朴素但有效的类比全楼统一抄水表Chandy-Lamport 分布式快照算法是 Flink 实现一致性快照的理论基础。这个算法理解起来其实不难我用一个生活类比来拆解。想象一栋楼里有三层住户每层由一个抄表员负责水表读数楼长要求在某一个时间点把整栋楼的水表读数抄一遍作为当月用水的统一结算依据。问题在于抄表员不可能在同一物理瞬间把所有水表都看完水还在流、数字还在转。理想状态是每层抄表员在接到楼长指令的那一刻之后的读数变化不做记录只记录那一刻的读数。但三层之间还有管道连通楼上流下来的水可能还在途中这就会导致同一时刻的定义不统一——你记录的时间点和我记录的时间点并不是严格对齐的。Chandy-Lamport 算法解决这个问题的核心就是屏障barrier。屏障可以理解成一种特殊的、携带时间戳的抄表指令标记。它跟随数据流从 Source 向下游流动。每个算子收到屏障时就知道在这个位置之前的输入数据都已经处理完了现在我该把状态快照落盘了。更重要的是屏障在流经每个算子时算子会把它当前的状态快照下来然后继续处理屏障之后的数据。这样一来每个算子各自保存的快照虽然在物理时间上不是同一个瞬间但在数据流逻辑时间上是完全对齐的。2.3 Flink 的分布式快照到底怎么拍对齐机制拆解具体到 Flink 的实现快照过程分几步Source 算子周期性地向所有下游广播 barrier。每个并行 Source 子任务都会注入一个带有 checkpointId 的 barrier你可以粗暴地把它理解为第 N 次快照的分界点。每个算子实例等待所有输入通道的 barrier 到达前提是该算子有多个上游输入。只有等到所有上游通道的同一个 checkpointId 的 barrier 都到了才说明这个时间点之前的所有数据我都收齐了然后把自己的状态快照异步写入状态后端。快照完成后算子把 barrier 继续向下游转发同时继续处理 barrier 之后的数据。等待所有输入通道的 barrier这个动作有个专门的名字叫 barrier 对齐barrier alignment。它是 Exactly-Once 语义的关键但也正是它带来第一个性能隐患如果某个输入通道的 barrier 迟迟不来比如该通道对应的上游处理慢其他通道的数据就得在缓存里等着这本质上就是一种背压。后来 Flink 提供了不对齐模式Unaligned Checkpoints牺牲一点恢复粒度换取对齐等待的时间用场景比较微妙后面我会细讲。关于快照的两个核心要点我补充一下快照是异步的——算子先把状态复制到本地状态后端通常是 RocksDB再异步上传到持久化存储比如 HDFS不会阻塞数据处理主链路。但异步不意味着无开销序列化和 IO 消耗是实打实的尤其在大状态场景下。快照的一致性边界是屏障经过点不是时间点——理解这一点特别重要。它意味着状态快照里可能包含逻辑上还没到达该算子的数据的痕迹这在语义上是对的但在直觉上很容易让人困惑。3. 三种一致性级别Flink 给的不是全有或全无3.1 从缓存里丢数据到精确一次语义的含义Flink 提供了三种处理语义很多文档把它们翻译成至少一次精确一次但翻译造成的误解特别大。我建议直接记英文和它的真实含义一致性级别故障恢复后的可能结果本质描述AT_MOST_ONCE数据可能丢计算状态不完整故障时未处理完的数据直接丢弃不重放状态可能落后AT_LEAST_ONCE数据不会丢但可能重复故障后从最近快照恢复快照之后的输入重新消费天然产生重复EXACTLY_ONCE每条数据对状态的影响只发生一次通过 barrier 对齐 状态快照 故障后恢复到精确快照点屏蔽了重复影响注意 AT_LEAST_ONCE 和 EXACTLY_ONCE 在 Flink 里故障恢复时的外部行为差异只体现在状态是否受到重复数据的影响上。两者恢复时都会重放快照之后的数据但 AT_LEAST_ONCE 下这些重复数据会再次更新状态而 EXACTLY_ONCE 通过对齐机制保证了重放数据的范围是从快照点之后干净地开始的状态不会被二次影响。3.2 Kafka 场景下三种级别的真实差异我用最常见的 Kafka Source 来举例。Flink 的 Kafka Source 自己维护着消费位点offset作为算子状态。故障恢复时Source 从最近一次 checkpoint 保存的 offset 继续消费。AT_MOST_ONCE 下Flink 不等 checkpoint 成功就提交 offset或者干脆不自动提交恢复时可能直接从更晚的位点开始于是这段数据就丢了。AT_LEAST_ONCE 下offset 是在 checkpoint 完成之后才提交的。恢复时Source 从最近成功的 checkpoint 存下的 offset开始重新消费那么这个 offset 之后到故障发生之间的数据就会被再次处理一遍如果你的聚合逻辑是求和、计数这类操作结果就会翻倍。EXACTLY_ONCE 下虽然这些数据也会被重新消费、重新进入算子但由于算子恢复了快照时的状态并且在恢复过程中通过屏障对齐保证了重放的数据不会二次应用到已经包含在快照里的状态上所以最终状态和故障从未发生完全等价。换句话说重复数据的物理存在不可消除但它的逻辑影响被机制性地屏蔽了。这里要特别提醒一个容易踩的坑EXACTLY_ONCE 只保证 Flink 内部状态的一致性级别。数据重复进入外部系统比如写入 Kafka 或 MySQL的问题EXACTLY_ONCE 不管。外部系统的那一跳需要你自己用幂等写或事务写去兜底。3.3 状态与 checkpoint 的关系什么时候真的落盘很多人问过我Flink 的算子状态是不是实时地存在内存里的checkpoint 是不是就是定期把这个状态拷贝一份答案也不完全是。在 Flink 里实时计算用的状态主要存在两种地方内存Heap或者 RocksDB。内存状态访问快但容量受限且故障时全丢完全依赖 checkpoint 恢复。RocksDB 状态后端则把状态以 KV 形式落到本地磁盘容量大得多checkpoint 时直接基于 RocksDB 的 SST 文件做增量或全量快照。checkpoint 是周期性触发的默认间隔你可以自己调。这也是恢复粒度和成本的取舍点checkpoint 间隔越短恢复时重放的窗口越小、状态越接近故障时刻但快照的频率越高对集群 IO 和 CPU 的压力也越大。经验值上实时数仓场景我一般不建议把 checkpoint 间隔调到 10 秒以下除非状态本身很小否则频繁快照反而会拖慢整体吞吐。4. 端到端一致性真正的分布式难题藏在系统之间的那根管道里4.1 Flink 的精确一次为什么不是银弹这是我在实际项目里最想强调的一点状态一致性从来不是Flink 一个组件的事而是整条链路的事。一条典型的实时链路长这样KafkaSource→ Flink计算→ Kafka / MySQL / RedisSink。Flink 的 EXACTLY_ONCE 保证的是中间这一段也就是 Flink 内部的键控状态和算子状态的精确一次。但你的下游是外部系统Flink 恢复并重放数据时下游系统并不知道这次重放是恢复动作它只会再一次收到这些数据再一次执行写入逻辑。如果你的 Sink 写的是insert into 订单表 values(...)那恭喜你重复数据直接 insert 了两遍。业界把这个问题叫作端到端一致性End-to-End Exactly-Once。它由三部分构成Source 的可重放性数据源必须支持从某个位点重新读取数据Kafka 支持Socket 这类流式源就不支持。Flink 内部的状态一致性上文讲过的 checkpoint barrier 机制。Sink 的幂等性或事务性Flink 重放的重复数据传到外部系统后必须被外部系统识别为重复并丢弃或者被合并为一次写入。这三者缺一不可。前两点做到很容易第三点才是真正的难点。4.2 幂等写入最简单的端到端方案但有很多前提幂等写是指同一份数据无论写入多少次最终效果都一样。实践中常用做法是给数据设计主键或唯一键写入时用upsert语义存在就更新不存在就插入。例如写入 MySQL 时用INSERT INTO ... ON DUPLICATE KEY UPDATE写入 Redis 时用HSET天然按 key 覆盖写入 HBase 时用Put同一 rowkey 重复写不产生新行写入 Kafka 的下游消费者自己按主键去重。幂等写的优点是实现简单、性能开销小缺点是对业务数据的约束很强。你的数据必须天然带一个业务主键比如订单号、用户 ID、设备 ID。如果做的是纯增量累加没有天然键可以覆盖幂等写就不太可行。比如我要算每一个商品 SKU 的累计销售额如果重放了一整天 24 小时的数据每个 SKU 的累计值被翻倍累加那任何按 key 覆盖的幂等写都救不了你因为你连哪一部分是旧的、哪一部分是重复的都分不清。另外幂等写还有一个隐蔽的坑部分场景下最终值必须基于完整历史。如果你的指标是用户累计消费次数消费行为数据恰好重放了两次你用 upsert 按用户 ID 覆盖最后落在库里的值是重放后的值依然错。所以幂等写只能和具体业务指标结合评估不能无脑套。4.3 事务写入与两阶段提交Flink 是怎么和外部世界讨价还价的这是端到端一致性里机制最复杂、也最容易理解偏的一块。Flink 的事务写入建立在两阶段提交协议2PC之上思路是这样的预提交阶段Pre-commitFlink 在 checkpoint 成功之前先把这一批次的数据暂存起来不真正对外可见。Flink 的 StreamingFileSink 在这一阶段会把数据写进一个临时文件/临时目录标记为pre-commit状态。提交阶段Commit当 checkpoint 成功且所有算子都完成快照后Flink 再向 Sink 发起真正提交。此时这批数据才对下游可见。中止阶段Abort如果 checkpoint 失败预提交的数据被回滚/丢弃外部系统不会看到这半截数据。这套机制要求 Sink 本身必须支持事务或者至少支持写入 → 提交/回滚两段操作。比如 Kafka 的 Sink 可以配合 Flink 的 KafkaProducer 使用事务性 producer使一批数据要么全部写入成功、要么全部不可见写入 MySQL 则可以用一个事务把这一批数据包在BEGIN ... COMMIT里。但 2PC 在分布式环境里是有名的性能杀手和故障放大器提交阶段是阻塞点如果 checkpoint 成功了但提交动作还没完成时 JobManager 挂了那这批数据就卡在预提交成功、最终提交未知的状态。Flink 重启后会去轮询事务状态并决定是提交还是回滚。这个恢复逻辑很成熟但也有极少数场景下由于外部系统的超时判断不一致导致事务最终被提交了两次。性能开销开启事务性 Sink 之后每个 checkpoint 周期内的事务会降低消息吞吐特别是高频小批次写入场景实测吞吐可能下降 20%-30%。我用过一个场景数据峰值 10 万条/秒事务性 Kafka Sink 打开后吞吐掉了接近三成最后靠增大 checkpoint 间隔 批量攒批才压回可接受范围。4.4 端到端一致性矩阵一张表看明白组件职责把整条链路的一致性责任划分开会更清楚链路阶段组件一致性责任典型实现数据源Kafka / Pulsar / 数据库 Binlog支持按位点重放Kafka 按 offset 重新消费计算引擎Flink 内部barrier 对齐 状态快照Checkpoint EXACTLY_ONCE状态存储Heap / RocksDB快照的可靠持久化增量/全量 checkpoint 到 HDFS外部存储 SinkKafka / MySQL / HBase / Redis幂等写或事务写2PC、主键 upsert这张表的重点是一致性是一层层叠加出来的。你只做 Flink 内部那层外部照样重复你只做 Sink 幂等但 Source 不可重放恢复时数据照样丢。任何一层缺位端到端一致性都不成立。5. 一致性、性能与可用性三者不可兼得的工程权衡5.1 checkpoint 频率 vs 恢复粒度没有免费的午餐一致性是有代价的。最直接的成本体现在 checkpoint 频率和恢复时间目标RTO上。checkpoint 越频繁恢复时重放的数据越少业务可见的数据空白窗口越短但快照本身对集群的压力越大。我实际见过一个作业状态 200GB 左右checkpoint 间隔默认设的 5 分钟。某个时刻节点宕机恢复过程是从 HDFS 拉快照约 3 分钟→ 重放最近 5 分钟 Kafka 数据取决于消费速度→ 追上当前位点。整个恢复窗口有近 8 分钟的状态停留在过去的时间。对于某些对实时性敏感的下游来说这个数字可能不可接受。所以调 checkpoint 频率要有评估意识状态大小、checkpoint 存储带宽、下游允许的延迟和数据重复容忍度。我给过一个粗略的经验公式恢复总时长 ≈ 快照下载时间 重放数据时间 追上实时位点时间如果快照下载时间是瓶颈单纯调高 checkpoint 频率没有意义得从压缩快照、裁剪状态、或者启用 RocksDB 增量 checkpoint 入手。增量 checkpoint 只上传本次变动的 SST 文件对 200GB 级的大状态能明显压缩快照耗时。5.2 barrier 对齐 vs 背压吞吐与一致性的第一场交易barrier 对齐机制在多上游输入的算子处有一个隐藏成本假设某算子有 3 个输入通道1 号通道的 barrier 先到此时算子必须先暂停处理 1 号通道后续的数据等待 2、3 号通道的 barrier。如果 2、3 号通道因为上游反压等原因迟迟不来1 号通道的数据就会在输入缓存里堆积形成局部背压进而拖慢整个数据流的处理速度。实测里这种情况在数据倾斜 多流 join的场景中非常明显。我处理过的一个 join 作业左右两条流的数据量差了一个数量级大流总是先到 barrier小流半天才赶上结果整个作业的吞吐被这个对齐动作拖到了不到设计值的一半。Flink 针对这个问题的解法是Unaligned Checkpoint不对齐 Checkpoint。它允许算子不再等待所有输入通道的 barrier 对齐而是把当前输入缓冲区内还未处理的数据连同 barrier 一起保存进快照里恢复时直接从那块缓冲区接着算。代价是快照体积会变大包含缓冲区数据恢复逻辑更复杂且需要你和你的下游能容忍恢复后有一段基于旧缓冲区的处理。什么时候该用 Unaligned 呢我的选择标准是状态很大RocksDB 级别且 checkpoint 耗时长但又不能容忍重放太久的情况下。注意它不是为了提升常规吞吐而是为了缩短对齐等待导致的故障恢复时间增长。默认还是关着除非你明确知道自己在做什么。5.3 并行度调整与状态重分布一次改并行度引发的血案这是实际运维中特别容易忽略的一个点Flink 作业的并行度调整会触发状态重分布而状态重分布本身是一致性最脆弱的时刻。比如一个 keyed state 的作业原来并行度 8你觉得吞吐不够改成 16。修改并行度后Flink 需要把原有 8 个子任务上的状态重新按 key 哈希到 16 个子任务上。这个过程是通过恢复时重新分配完成的前提是恢复时用的快照仍然存在。如果你改并行度的时候没有保留对应的 savepoint/checkpoint或者状态后端不支持重新分配部分算子类型有这个限制作业恢复时就会直接报状态不匹配错误。另一个坑是改了并行度之后checkpoint 的 barrier 对齐行为也变了。更多并行子任务意味着更多人参与快照任何一块慢都会拖累整体 checkpoint 完成时间。我在一个项目中把并行度从 8 调到 32 后状态总量没变但 checkpoint 完成时间从 2 秒涨到了近 8 秒原因就是对齐等待时间变长了。这又回到权衡问题并行度不是越大越好它和一致性成本是耦合的。5.4 savepoint 与 checkpoint 的分工别把它们当成同一个东西实操中经常有人把 savepoint 和 checkpoint 混为一谈。简单区分checkpoint是 Flink 为了故障恢复自动执行的周期性强用完即走Flink 会按配置保留最近的若干份。savepoint是你手动触发的、带业务语义的版本存档常用于版本升级、并行度调整、代码逻辑变更前的备份以及我要改完代码之后能从某个时间点重新开始这类场景。做 Flink 作业升级时我的标准流程是改代码之前先打一份 savepoint → 停机 → 改代码/改并行度 → 从 savepoint 恢复。这个过程保证了升级前后状态的一致性。如果不这么做直接重启从最新 checkpoint 恢复但代码逻辑变了比如 key 的粒度变了恢复后状态语义会对不上。savepoint 本身也存在一致性风险如果你的作业 pipeline 里有不支持快照的算子比如某些自定义 Sourcesavepoint 会失败。所以打 savepoint 最好在业务低峰且上下游允许一定延迟时进行。6. 从原理到落地配置、排查与实战决策6.1 一个能直接抄的配置模板下面这个配置是我在实时数仓场景里常用的一个起点你可以按需调整。这里以 DataStream API 为例StreamExecutionEnvironment env StreamExecutionEnvironment.getExecutionEnvironment(); // 开启 checkpoint间隔 60 秒 env.enableCheckpointing(60_000L, CheckpointingMode.EXACTLY_ONCE); // checkpoint 的详细配置 CheckpointConfig checkpointConfig env.getCheckpointConfig(); checkpointConfig.setCheckpointStorage(new FileSystemCheckpointStorage(hdfs://nameservice/flink-checkpoints)); checkpointConfig.setMinPauseBetweenCheckpoints(30_000L); // 上一次 checkpoint 结束后至少等 30 秒再触发下一次 checkpointConfig.setCheckpointTimeout(10 * 60_000L); // 单个 checkpoint 10 分钟超时 checkpointConfig.setMaxConcurrentCheckpoints(1); // 同一时刻只允许一个 checkpoint 进行中 checkpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); // 作业 cancel 后保留 checkpoint便于恢复 // Kafka Source开启 checkpoint 后位点自动由 Flink 管理无需额外配置 // 但要注意 consumer group 不要和外部共享几个参数我要解释一下为什么这样设。setMinPauseBetweenCheckpoints(30_000L)特别重要。如果不设置Flink 会在上一个 checkpoint 还没完全结束时就开始下一个两个快照动作可能重叠给存储和上游带来重复压力。设为 30 秒可以保证 checkpoint 之间有足够的喘息时间也避免失败后立刻重试导致持续抖动。setMaxConcurrentCheckpoints(1)是强制串行化 checkpoint 的保险丝。如果你设置了外部化保留而且恢复时总是从最近一次成功的 checkpoint 恢复这个参数就不用改。setCheckpointTimeout(10 * 60_000L)设太短的话大状态作业会频繁 checkpoint 超时失败设太长的话快照卡住时你无法快速感知。10 分钟是我在 200GB 状态级别下的常用起点状态更小的项目可以缩到 5 分钟。6.2 恢复时到底发生了什么状态机的完整链路故障恢复不是简单拉一份快照回来。完整链路是JobManager 感知到 TaskManager 失联将作业置为 FAILED 状态触发重启策略。JobManager 从持久化存储中选取最近一次完整成功的 checkpoint 元数据拿到所有算子状态的位置。重新调度算子子任务每个子任务从对应状态的存储位置异步恢复状态。 如果你用的 RocksDB 增量 checkpoint恢复时需要把多个增量段合并耗时会比全量长。这也是为什么增量 checkpoint 适合状态变化量小但总量大的场景——恢复时段的合并成本你需要提前测。Source 算子恢复消费位点开始从恢复点重新消费 Kafka 数据。每个算子从快照状态开始继续处理重放的数据直到消费位点追上故障发生前的实时位点系统才算真正恢复。如果作业 pipeline 里有外部系统参与比如事务性 Sink恢复时还会涉及未完成事务的最终状态确认常见的结果有两种事务提交coordinator 确认已提交并保留记录或事务回滚。这个确认过程如果和外部系统通信超时Sink 会把这批数据阻塞住导致恢复后的作业在 Sink 环节卡住不动。排查时如果发现恢复完成后吞吐迟迟上不去别只盯着 Source 和 CPU先看一眼 Sink 的事务状态是否健康。6.3 一致性问题的排查手册我在实战中依次看什么作业出现数据重复或状态对不上时我的排查步骤依次是第一步确认 Flink 内部一致性配置生效。用flink run -d启动作业时看 Web UI 里 Checkpoint 页面是否持续产生成功的 checkpoint恢复时用的CheckpointingMode到底是什么。有时候代码里设置了 EXACTLY_ONCE但某个算子内部自己又调用了disableCheckpointing()状态路径就断了。第二步确认外部 Sink 是否有幂等/事务能力。把从 Flink 到外部系统的写入语义列清楚MySQL 用 upsert 吗Kafka 的 Sink 开事务了吗Redis 的写入能按 key 覆盖吗如果都不行那大概率就是这里重复的。第三步确认 Source 的重放边界。Kafka Source 的消费位点是存在 Flink 的状态里的但如果你自实现了 Source或者作业里同时存在多个消费同一 topic 的组位点管理就乱了。自实现 Source 的作业常常在恢复后发生位点和快照状态脱节的诡异问题。第四步分辨重复到底发生在哪一跳。一个有效的定位方法在 Sink 入口打印或记录本次写入的数据是否属于恢复重放窗口。你可以给 Source 数据打一个单调递增的序号恢复前和恢复后的数据在序号上出现断层或重叠你就能精确定位是哪个环节导致的重复。对于一个因为状态恢复而出的脏数据最常见的根因其实不是 Flink 状态机本身跑错了而是重放窗口内的数据在下游被二次应用。理解这个根因比调整一堆参数管用得多。6.4 选型决策什么场景用什么方案我不是拍脑袋定的不同业务对一致性的容忍度天差地别我的选型逻辑大致如下表业务场景对重复/丢失的容忍度推荐组合原因实时大屏指标流量、PV/UV可容忍轻微重复AT_LEAST_ONCE 定时刷新入库这类场景重数据影响有限优先保证吞吐实时数仓 ODS/DWD 层中EXACTLY_ONCE Kafka 事务性 Sink保证后续分层计算不累积误差订单/支付/账户变更极低EXACTLY_ONCE 业务主键幂等写或 2PC外部业务系统必须看到精确一次的效果日志清洗/非关键监控可容忍丢失AT_MOST_ONCE性能优先丢几条日志无所谓这张表不是绝对标准但说明了一个原则一致性是业务需求的投影不是技术上的越强越好。过度追求 EXACTLY_ONCE引入大量事务和幂等逻辑有时候会牺牲掉你更在乎的吞吐或延迟。我做过一个流量指标作业最初按教科书配了 EXACTLY_ONCE Kafka 事务 Sink结果因为事务提交频繁导致整体吞吐下降明显后面评估业务对轻微重复不敏感降级成 AT_LEAST_ONCE 幂等刷新吞吐恢复指标质量也完全够用。6.5 大状态场景的两个实战经验RocksDB 与状态裁剪最后给两个我花了不少时间才总结出来的实战经验。第一个RocksDB 状态后端 增量 checkpoint 的坑。RocksDB 做状态后端时checkpoint 有两种模式全量把整个 DB 的 SST 文件都上传和增量只上传本次 checkpoint 和上次 checkpoint 之间的差异文件。增量模式启动前一定要确认 RocksDB 的 state backend 和 savepoint 的兼容性Flink 从 savepoint 恢复时如果 savepoint 是用全量模式打的而作业现在配了增量模式可能会因为文件格式差异恢复失败。我的习惯是版本升级前用 savepoint但恢复时临时切回全量模式等确认恢复成功再切回增量。第二个状态裁剪State TTL是一致性的好朋友也是敌人。给 Keyed State 设置了 TTL比如订单状态只保留 24 小时可以控制状态无限膨胀保证 checkpoint 体积稳定。但 TTL 的清理机制是惰性的不是精确到秒的过期而是访问时才检查是否过期或者依赖后台线程异步清理。这意味着如果你的业务对状态的时间窗口边界非常敏感例如必须精确按 24 小时过期TTL 可能会在边界处产生不可预期的一致性问题。所以对强一致的业务状态我倾向于不用 TTL而是通过业务层面的窗口设计来清理状态。7. 写在最后一致性不是配出来的是设计出来的做了几年 Flink 相关的项目我最大的感受是状态一致性这个概念本身并不复杂复杂的是把一致性放到一个真实的分布式链路里然后让所有组件联合起来满足同一个约定。Flink 的机制做得再漂亮也只是解决了一个环节的问题Source 能不能重放、Sink 能不能幂等或事务、状态是不是大得无法快速快照……任何一个环节掉链子整体的一致性目标都会落空。所以如果你正在设计一个新的实时作业我的建议是别一上来就只盯着开 EXACTLY_ONCE先把整条链路的每个组件想清楚——每个组件在故障时能做什么不能做什么恢复后数据会重复多少重复的后果是什么下游能不能吸收这些重复。把这些问题的答案写成一段注释放在作业代码的头部比一年后靠回忆排查事故要有效得多。状态一致性是一个系统的属性不是某个组件的属性。想通了这一点很多看似诡异的事故其实都能在事发之前就避免掉。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑