老陈是青石镇银行的第六任行长,他每天面对的,是从四面八方涌来的汇款单——磨坊的粮款、商队的货款、田庄的租金,像一条永不停歇的河流,涌进银行的柜台窗口。老陈给所有客户的承诺只有一句话:“每笔钱到账时,不多一分,也不少一分。”可这句话让账房先生们吃尽了苦头:邮差送单可能遇到暴雨(机器宕机),账房先生可能誊错账(状态丢失),更邪门的是同一张汇款单有时会被重抄两遍送进来(数据重复)。为了让承诺成真,老陈想出了一套办法——每隔一个时辰,让全行柜台同时停笔,给每本账册拍一张“状态合影”(Checkpoint 快照);拍合影前,由邮差举着一面小红旗从源头出发,沿途柜台先清完旧账再拍照(Barrier 对齐);涉及跨钱庄转账时,还要先送“意向函”再送“确认函”(两阶段提交)。这套规矩,就是流处理世界里赫赫有名的 Exactly-Once 语义。今天,我们就跟着老陈的银行,把这项技术从头到尾拆个明白。
掌握 Exactly-Once 语义是流处理从业者的核心功底。理解 Checkpoint、Barrier 与两阶段提交,你才能在金融、风控等“一分钱都不能算错”的业务中,设计出故障不慌、数据不重不漏的实时系统。
目录
可视化演示
展示Flink流处理Exactly-Once保障三模式:三种语义层次对比、Chandy-Lamport barrier对齐Checkpoint机制、Sink两阶段提交端到端保障,递进式呈现核心原理。
本节配有交互动效(进入视口后自动加载,约 3KB)。
语义层次:三种消息传递保证 At-Most-Once可能丢消息(故障时跳过) Exactly-Once零丢失零重复(快照+2PC) EO = Checkpoint + 2PC Sink 故障恢复后状态精确一致无重复
端到端语义——三档邮寄服务的承诺
老陈的银行和邮局合作投递汇款单,邮局提供三档服务。平信最便宜,寄丢了就丢了,邮局概不负责;挂号信贵一些,邮局会登记在册,怀疑丢失时会补寄一次,可万一补寄时原件其实已经到了客户手里,收件人就会收到两张相同的汇款单;保价信最贵,邮局用唯一编号逐封登记,直到客户签字确认才算送达,保证恰好收到一次。
这三档服务,恰恰对应了流处理中的三种语义。每条数据记录从数据源(比如 Kafka)出发,经过一个又一个算子计算,最终写入结果系统。计算过程中,任何一台机器都可能宕机,数据可能在“读取→计算→写出”的任何一个环节被重复或遗漏。At-Most-Once(至多一次)故障后从旧位点重新读,可能跳过数据造成丢失;At-Least-Once(至少一次)故障后重放数据,可能把已算过的再算一遍造成重复;Exactly-Once(恰好一次)则通过 Checkpoint 加大状态、两阶段提交等手段,让故障后的结果与“从未发生故障”完全一致。
用数学语言描述,就是处理次数的三种取值:
客户不差钱时自然会选保价信。同样,在金融、广告、风控这些“一分钱都不能算错”的行业,必须选用 Exactly-Once 语义。而实现保价服务的全部秘密,就藏在这句承诺背后——账房先生们手里那本分毫不差的账本,就是下一节的“状态”。
- proc\_count(e)表示事件 e 在整个故障与恢复的周期中被实际处理的次数,是衡量语义的核心变量。
- 至多一次处理次数可以是 0 或 1,也就是某些数据可能会被漏掉、丢失。
- 至少一次处理次数可以是 2 次、3 次甚至更多,也就是数据可能被重复处理。
- 恰好一次处理次数不多不少正好 1 次,这是 Exactly-Once 的最终状态。
状态存储——账房先生的账本
账房先生老王负责全行最复杂的往来账目。他面前摊着几十本账册,有的是按客户名分册的(张三的存折、李四的往来账),有的是按业务类型记的总流水(今天总共收了多少汇款单)。这些账本是银行一切业务的地基。账本的存放也很有讲究:经常翻看的放在桌面(内存),不常用的锁进保险柜(本地磁盘),太占地方的还会抄一份送回总行金库(外部存储)。
在 Flink 中,“账本”就是状态 State——算子在处理过程中需要记住的全部信息。计数器算子要记住当前累计值,窗口算子要记录窗口里收到了哪些数据。Flink 把状态分成两大类:算子状态(Operator State)挂在算子整体上,最典型的例子是 Kafka Source 记录的消费位点(offset);键控状态(Keyed State)则按 key 各开一册,每条数据只更新自己所属 key 的那本账,例如按用户 ID 统计点击次数。这些账本由“状态后端”(State Backend)统一管理:内存后端快但不持久,文件系统后端把快照落到 HDFS,RocksDB 后端则把大状态写到本地磁盘,并支持超出内存的超大状态。
以键控状态为例,每一笔账的演进可以写成:
账本记得越准,恢复时越有底气。可光有账本还不够:银行每隔一个时辰要拍“全行状态合影”,把所有柜台的账本和经手进度统统固化下来。这就是下一节的 Checkpoint。
- 状态 s\_k^{(t)}键 k 在时刻 t 的状态值,例如用户“张三”当前的账户余额。
- 事件 e\_k^{(t)}t 时刻到达、且属于键 k 的事件,例如张三刚寄来的那张汇款单。
- 更新函数 f记账规则,把旧状态和新事件合成为新状态——收到 100 两就加 100,支出 50 两就减 50。
Checkpoint快照——全行状态合影
老陈定了一条铁规矩:每隔一个时辰,全行拍一张“状态合影”。拍合影时,每个柜台要把当前经手到哪张汇款单、账本余额是多少、抽屉里压着哪些没入账的单据,全部塞进一个信封,交专人骑快马送进总行金库。如果哪天银行遭了大火,恢复时就拿金库里最近一张合影,让所有人按照片上的样子重新开工。这张合影,就是 Flink 的 Checkpoint。
Checkpoint 是 Flink 容错的基石:它周期性地把全体算子的状态和 Source 的消费位点封存成一份一致性快照,写入持久化存储。作业失败后,从最近的快照恢复,Source 重置到对应位点,所有算子状态回拨到快照时刻。除了自动触发的 Checkpoint,Flink 还支持手动触发的保存点(Savepoint)——它使用同样的快照机制,但通常用于应用升级、集群迁移等运维场景,保存得更完整、更持久。
一个 Checkpoint 是对全行状态的一次完整定格:
有了合影,银行不再怕火了。但问题来了:各柜台业务进度不同,有的已处理到第 100 号单,有的还在第 80 号。如果直接按各自当前状态拍照,照片里“时间点”对不上,恢复后账目必然错乱。解决这个问题的,是拍照前那道严格纪律——Barrier 对齐。
- 快照 C\_n第 n 次 Checkpoint 产生的完整状态集合。
- 算子状态 S\_n^{(i)}第 i 个算子(柜台)在快照时刻的本地状态。
- 消费位点 offset\_nSource(邮差)在快照时刻读到的位置,即“下一个该读哪封信”。
- 分号分隔分号左侧是全部算子状态,右侧是 Source 位点;两者合在一起才能精确恢复。
Barrier对齐——举旗止账
为了保证合影里每个柜台都定格在“同一批汇款单”的位置上,老陈想出一招:每次拍合影前,源头邮差在发完一批新汇款单后,会紧跟着举起一面红白相间的小旗(Barrier)往下游走。每个柜台看到旗子,必须先把手头已收到的汇款单全部处理完,才开始拍照,然后把旗子传给下游柜台。如果一个柜台有多个入口——比如同时接收“汇款单”和“退汇单”两条邮路——它必须等所有入口的旗子都到了,才动手拍照。这个过程就是“对齐”。
在 Flink 中,Barrier 是 Source 周期性注入数据流的特殊标记,随普通数据一起流动。算子收到 barrier 后,先处理完 barrier 之前所有数据,再保存状态快照,然后把 barrier 传给下游。多输入算子必须等待所有输入流的同编号 barrier 全部到达,才能触发快照——这样快照里的状态才能精确对应到同一条“逻辑时间线”上。
对齐会产生等待开销。设一个算子有 d 个输入流,第 i 个输入的 barrier 到达时间为 t_i,则对齐等待时间为:
旗子一来,全行停笔、对齐、拍照,照片上的时间点才完全一致。这套“全体定格”的规矩并非老陈首创——它的理论根基,来自几十年前两位计算机科学家的著名论文:Chandy-Lamport 分布式快照算法。
- 到达时间 t\_i第 i 个输入流上 barrier 到达算子的时刻。
- 最慢到达取所有输入中 barrier 最晚到达的时刻。
- 最快到达取所有输入中 barrier 最早到达的时刻。
- 对齐等待 T\_align两者之差,直接决定 checkpoint 的耗时;反压越大,等待越长。
Chandy-Lamport——分布式合影的祖师爷
1985 年,两位计算机科学家 K. Mani Chandy 和 Leslie Lamport 研究了一个直击本质的问题:在一座由多个房间连通的大宅子里,如何让各房间的人拍出一张“同一瞬间”的合影?答案是从源头房传出“拍照令”:每个房间收到令后,先把自己的人定格、记录房间内的状态,再向相邻房间转发拍照令;同时,要把“拍照令到达前已经送出、但还没被对方收到的信”也登记下来(在途消息)。最终各房间的局部照片拼起来,就是一张全宅统一瞬间的合影。
这就是 Chandy-Lamport 算法——在异步、无全局时钟的分布式系统中生成一致性快照。Flink 的 Checkpoint 正是这一算法在流处理引擎中的工程化实现:数据流中的 barrier 就是“拍照令”,算法的通道状态记录则由 barrier 对齐来保证(barrier 之前的消息必然已经被算子处理并计入状态,不需要额外记录)。
Chandy-Lamport 快照由“进程本地状态”和“通道在途消息”两部分组成:
老陈的合影法正是照着祖师爷的论文抄作业:旗子(barrier)是拍照令,柜台上排队的汇款单是在途消息。有了一致快照,“不丢”有了保障;可“不重”还没解决——跨钱庄汇出的钱,必须走一套两阶段的确认仪式。
- 本地状态 S\_p进程 p 收到拍照令时记录的自身状态,对应 Flink 中每个算子保存的状态快照。
- 在途消息 M\_pq通道 p→q 中已发出但尚未到达的消息,对应 Flink 中 barrier 之间的缓冲数据。
- 并集合成把全部进程状态和全部在途消息合并,构成全局一致快照。
两阶段提交——跨钱庄的确认仪式
镇上最大的一笔汇款,往往要由老陈的钱庄和隔壁石桥钱庄共同完成。老陈设计了一套稳妥流程:第一步,先派伙计送一封“转账意向函”到石桥钱庄,钱庄回执“同意,款项已冻结待确认”;第二步,老陈把这笔意向登记进当天的全行合影,等合影成功交到金库后,再送出“正式确认函”,石桥钱庄收到后正式入账。要是中途出岔子,老陈就补送“作废函”,石桥钱庄解冻款项,像什么都没发生过。
这正是两阶段提交(Two-Phase Commit):第一阶段预提交(precommit),第二阶段正式提交(commit)或回滚(abort)。Flink 的 TwoPhaseCommitSinkFunction 为 Sink 实现了这一机制:Checkpoint 执行时,Sink 先预提交,把缓冲数据写入外部系统(如 Kafka 事务);等 Flink 确认所有算子都成功保存快照后,再统一提交事务,数据才对下游消费者可见。任何一步失败都会 abort,外部系统丢弃未确认数据。
用逻辑式表达就是:
“意向函 + 确认函”让跨钱庄的每笔钱都精准落地:要么完全到账,要么完全不到账。配合前面的 Checkpoint 合影,Source、处理过程、外部输出三段都做到了“不重不漏”,这就是端到端的 Exactly-Once。
- 预提交 precommit(tx)开启事务 tx 并进入“待确认”状态,外部系统冻结数据。
- 提交 commit(tx)Checkpoint 成功后正式确认,数据对外可见;失败则永远不会走到这一步。
- 回滚 abort(tx)Checkpoint 失败或超时后丢弃事务,数据保持不可见,绝不产生“半截子”状态。
非对齐Checkpoint——堵车时的取舍
有一天,镇子通往山外的邮路赶上了暴雨,运汇票的马车排起长队,好几个柜台前堆满了没处理的汇款单。到了拍合影的时辰,老陈发现:若按老规矩等每个柜台把旧单全部清完再对齐拍照,恐怕要拖到半夜。他当机立断:不等了!让各柜台就地放下手头的账册和单据,直接各自拍一张现状照;至于还在马车上的汇款单,就一并记进照片的备注栏。合影很快拍完了,只是照片里夹着许多“还在路上”的动态信息,恢复时需要重新盘点。
这就是非对齐 Checkpoint(Unaligned Checkpoint):当算子遭遇反压(Backpressure)时,不再等待所有输入 barrier 对齐,而是立刻执行快照,同时把信道缓冲中未处理的数据一并纳入快照。它的收益是大幅缩短 checkpoint 在反压场景下的完成时间,代价是快照体积更大、恢复时需要重放更多在途数据。因此它特别适合“高反压 + 大状态 + 事务性 Sink”的场景。
非对齐快照额外保存的,是每条通道缓冲区中的在途数据:
老陈的“应急合影”不是没有代价:恢复时可能把马车上的单子重新清点一遍,造成重复处理。但只要下游配合两阶段提交或幂等写入,最终账目依然精确。对齐与非对齐,本质上是一场“快照速度”与“恢复成本”的博弈——没有银弹,只有基于业务场景的取舍。
- 缓冲数据 B\_pq通道 p→q 缓冲区中排队等待处理的数据。
- 缓冲长度用绝对值的记号表示这段缓冲里有多少数据。
- 总开销把所有通道的缓冲数据量加总,就是非对齐快照相对对齐版本多出的存储成本。
AI 应用场景
金融交易反欺诈实时风控
阶段:数据接入与特征工程(实时特征计算)
解决的问题:支付风控需要对每笔交易实时计算特征,比如“用户最近 5 分钟交易频次”“设备在 1 小时内出现的城市数”。传统批处理只能做 T+1 事后分析,无法拦截实时欺诈;而流式处理一旦发生故障,数据重复会冤枉正常用户,数据丢失会漏放欺诈交易。
具体案例:某支付公司每秒接入数十万笔交易事件,链路为 Kafka → Flink 计算滑动窗口特征 → 特征送入风控模型评分。开启端到端 Exactly-Once 后,即使 Flink 作业在高峰期重启,交易特征也不会因重放翻倍,模型打分保持稳定,欺诈拦截率显著提升。
推荐系统样本一致性保障
阶段:数据清洗与训练样本构建
解决的问题:推荐系统依赖用户点击、曝光日志训练模型。流式清洗作业故障后,日志重复会导致训练样本重复、模型出现偏差;日志丢失会导致特征缺失、线上与离线不一致。
具体案例:某短视频平台的埋点日志经 Flink ETL 做解析、过滤、session 聚合后写回 Kafka,供离线训练和实时特征复用。启用 Exactly-Once 后,恢复重放不会产生重复样本,线上特征与离线训练样本严格对齐,模型迭代效率大幅提高。
Python 演示
"""简化模拟 Flink Exactly-Once:Checkpoint + Barrier + 两阶段提交""" class EventSource: """数据源:发出事件流,并在指定位置注入 barrier""" def __init__(self, events, barrier_after=(3, 6)): self.events = events self.barrier_after = set(barrier_after) self.offset = 0 self.barrier_no = 0 self.pending = False # 是否有一个 barrier 待发出 def next(self): if self.pending: self.pending = False self.barrier_no += 1 return ('barrier', self.barrier_no) if self.offset < len(self.events): e = self.events[self.offset] self.offset += 1 if self.offset in self.barrier_after: self.pending = True return ('event', e) return None class CounterOperator: """算子:维护键控状态,遇到 barrier 时保存快照""" def __init__(self): self.counts = {} # 状态账本 self.snapshot = None # 最近一次 checkpoint def handle(self, item): kind, payload = item if kind == 'event': self.counts[payload] = self.counts.get(payload, 0) + 1 elif kind == 'barrier': # barrier 到达:复制状态作为快照 self.snapshot = (payload, dict(self.counts)) def restore(self): if self.snapshot is not None: self.counts = dict(self.snapshot[1]) class TwoPhaseCommitSink: """sink:两阶段提交(预提交 -> 提交 / 回滚)""" def __init__(self): self.buffer = [] # 待写入数据缓冲 self.pending = {} # 已预提交未确认事务 self.visible = set() # 已提交可见数据 def precommit(self, tx_id): self.pending[tx_id] = list(self.buffer) self.buffer.clear() def commit(self, tx_id): if tx_id in self.pending: self.visible.update(self.pending.pop(tx_id)) def abort(self, tx_id): self.pending.pop(tx_id, None) def main(): src = EventSource(['a', 'b', 'c', 'a', 'd', 'b', 'e']) op = CounterOperator() sink = TwoPhaseCommitSink() while True: item = src.next() if item is None: break op.handle(item) if item[0] == 'event': sink.buffer.append(item[1]) else: tx = item[1] sink.precommit(tx) # 阶段一:预提交 sink.commit(tx) # 阶段二:提交 print('checkpoint-{}: state={}'.format(tx, dict(op.counts))) print(' visible={}'.format(sorted(sink.visible))) # 模拟故障:恢复到最近 checkpoint 后继续 print('\n=== 模拟故障,恢复最近 checkpoint ===') op.restore() print('恢复后状态: {}'.format(dict(op.counts))) print('下游已见数据: {}'.format(sorted(sink.visible))) if __name__ == '__main__': main()
if self.offset in self.barrier_after: self.pending = True每处理完第 3、第 6 个事件后标记 barrier 待发,模拟 Flink 周期性注入快照令。self.counts[payload] = self.counts.get(payload, 0) + 1更新键控状态,等价于账房先生向对应客户的账本上记一笔。self.snapshot = (payload, dict(self.counts))barrier 到达时深拷贝状态作为 Checkpoint 快照,模拟“全行状态合影”。self.pending[tx_id] = list(self.buffer)Sink 预提交:把缓冲区数据登记为“待确认事务”,对应转账意向函。sink.precommit(tx)阶段一:预提交,外部系统冻结数据,等待最终确认。sink.commit(tx)阶段二:正式提交,数据对外可见;checkpoint 失败则不会调用它。op.restore()故障恢复:从最近快照恢复算子状态,模拟银行从金库中取出最近一张合影重新开工。
常见误区
误区我一直以为 Flink 的 Exactly-Once 就是保证每条数据只被 CPU 执行一次,如果发生重放,那 Exactly-Once 不就是在骗人吗?
正解故障恢复时 Flink 必然会把部分记录重新处理一遍,这是无法避免的。Exactly-Once 的真正含义是“对外可见的最终结果”与无故障时完全一致:靠 Checkpoint 保证不丢,靠两阶段提交或幂等写保证不重。就像老陈的银行,一张汇款单可能被两个不同的伙计各处理了一次,但账本上最终只记一笔。
为何常见:“恰好一次”这个译名有强烈误导性,直觉上容易被理解成物理层面的单次执行。
误区对齐的时候要等所有输入 barrier 到齐,那 barrier 还没到之前积压的数据,是不是就被清掉扔了?那 Exactly-Once 不就变成丢数据了吗?
正解对齐时 barrier 之后到达的数据只是暂存在输入缓冲区,等快照完成后再继续处理,完全不会丢弃。就像拍合影时,新来的汇款单先被放到柜台旁的托盘里,拍完照接着办。
为何常见:“对齐”听起来像要把时间点之前的数据全部清空,容易让人误以为是丢弃操作。
误区两阶段提交我在数据库课上学过,就是 prepare 加 commit、锁资源那套,Flink 的 2PC 应该也一样吧?
正解Flink 的 TwoPhaseCommitSinkFunction 借鉴了 2PC 的思想,但配合的是 Checkpoint 机制而非数据库的锁协议:Sink 在 checkpoint 时预提交,全部算子快照成功后才正式提交,外部系统需要支持事务(如 Kafka 事务、JDBC 事务)。它解决的并非并发控制问题,而是“故障恢复后不重不漏”的落地问题。
为何常见:两者都叫“两阶段提交”,名词相同导致与经典数据库 2PC 混淆,实际上解决的完全是不同层面的问题。
误区非对齐 checkpoint 在反压的时候快这么多,那干脆所有作业都开启非对齐不就好了?反正好处这么大。
正解非对齐会显著增大快照体积(需要保存所有通道缓冲数据),恢复时要重放更多在途数据,而且要求下游必须配合幂等写入或事务。对齐 checkpoint 在正常负载下开销小、恢复精确,只有在持续反压导致 checkpoint 反复超时失败的场景下,才建议开启非对齐。
为何常见:只看到非对齐在反压下的速度优势,忽略了它在大状态、大缓冲场景下的存储与恢复成本。
面试题
Q1. Flink 中三种处理语义,哪一种保证“每条记录恰好被处理一次”?
- A.At-Most-Once(至多一次)
- B.At-Least-Once(至少一次)
- C.Exactly-Once(恰好一次)← 正确答案
- D.Once-Per-Window(每窗口一次)
答案:C。Exactly-Once 通过 Checkpoint 加两阶段提交保证最终状态与无故障一致。At-Most-Once 可能丢数据,At-Least-Once 可能重复处理,而 Once-Per-Window 并不是标准的处理语义。
Q2. Flink 的 Checkpoint 由哪种特殊消息触发?
- A.Watermark(水位线)
- B.Barrier(屏障标记)← 正确答案
- C.Savepoint(保存点)
- D.Offset(消费偏移)
答案:B。Barrier 是 Flink 周期性注入数据流的特殊标记,如同老陈派出的“小红旗”。Watermark 用于处理事件时间,Savepoint 是手动快照,Offset 则是消费位点,都不是触发 Checkpoint 的信号。
Q3. 某 Flink 作业故障恢复后,发现外部系统中部分数据被重复写入,最可能的原因是?
- A.Sink 缺少幂等或事务去重← 正确答案
- B.Source 消费速度过慢
- C.状态后端容量已满
- D.窗口长度设置过大
答案:A。Checkpoint 保证了算子状态不丢,但恢复时 Checkpoint 到故障点之间的数据会被重放。如果 Sink 不具备两阶段提交或幂等去重能力,这段重放数据就会重复落到外部系统,相当于只有“意向函”而没有“确认函”把关。
Q4. 当算子的两个输入流 Barrier 到达时间差变大时,下列描述正确的是?
- A.Checkpoint 对齐等待时间变长← 正确答案
- B.Checkpoint 一定会失败
- C.缓冲区数据一定会丢失
- D.状态后端会自动扩容
答案:A。对齐等待时间等于最慢输入与最快输入的 barrier 到达时刻之差。时间差变大,等待自然变长,但 checkpoint 可能依然成功,缓冲数据也不会丢,只是耗时增加。
Q5. 关于 Flink 两阶段提交 Sink,下列说法正确的有?
- A.包含预提交与提交阶段← 正确答案
- B.预提交后数据即可见
- C.确认失败可回滚事务← 正确答案
- D.需外部系统事务支持← 正确答案
答案:ACD。两阶段提交分预提交和最终提交两步。预提交后数据处于“待确认”状态,对外不可见,只有 commit 后才可见,因此 B 错误。确认失败会 abort 回滚,且外部系统必须有事务能力配合,所以 A、C、D 正确。
Q6. 保存点(Savepoint)与 Checkpoint 相比,主要区别是什么?
- A.Checkpoint 是手动触发的
- B.Savepoint 手动触发用于升级迁移← 正确答案
- C.两者完全没有任何区别
- D.Savepoint 不保存任何状态
答案:B。Checkpoint 由 Flink 自动周期触发,用于故障恢复;Savepoint 由用户手动触发,保存更完整的快照,常用于应用升级、集群迁移等运维场景。两者机制相似但触发方式和用途不同。
Q7. 非对齐 Checkpoint(Unaligned Checkpoint)的特点有?
- A.反压时快照速度更快← 正确答案
- B.需额外保存通道缓冲← 正确答案
- C.无法保证任何一致性
- D.适合高反压大状态场景← 正确答案
答案:ABD。非对齐 Checkpoint 不等 barrier 对齐,直接保存各算子状态和通道缓冲数据,所以快照更快(A),但快照体积更大(B)。它仍然可以配合幂等/事务 Sink 达到 Exactly-Once,并非完全放弃一致性,故 C 错误;它特别适合高反压、大状态场景(D)。
Q8. 下列哪种场景最能体现 At-Least-Once 语义?
- A.故障后重放已处理数据导致重复← 正确答案
- B.故障后跳过部分未处理数据
- C.每条数据仅处理一次并且写入结果
- D.数据按窗口聚合后统一输出
答案:A。At-Least-Once 的核心特征是“至少处理一次,可能重复”。故障后从旧位点重放,必然把已处理过的数据再算一遍,产生重复;而跳过未处理数据对应 At-Most-Once。
Q9. Chandy-Lamport 算法中,进程收到拍照令后除了记录本地状态,还需要记录什么?
- A.拍照令到达前已发出但在途的消息← 正确答案
- B.立刻停止一切消息的后续处理
- C.等待全局时钟到达某个整点
- D.只记录拍照令之后到达的消息
答案:A。Chandy-Lamport 的核心思想是“进程状态 + 通道在途消息”才能构成一致快照。在途消息是指令到达前已发出但未到的消息,对应 Flink 中 barrier 前尚未被算子处理的数据,这正是对账时最容易漏掉的部分。
Q10. 实现端到端 Exactly-Once 通常需要哪些机制配合?
- A.Checkpoint 保存状态与偏移← 正确答案
- B.Barrier 对齐保证一致快照← 正确答案
- C.Sink 事务或幂等写入← 正确答案
- D.完全禁止故障恢复机制
答案:ABC。端到端 Exactly-Once 需要“状态快照不丢”(A)、“快照时间点一致”(B)和“外部落地不重”(C)三者协同。D 荒谬——没有故障恢复机制,就谈不上容错语义。