参考: 《Stream processing with Apache Flink》、《Streaming Systems》
流计算引擎一般提供的是系统内部的状态一致性。这其中不包括输入和输出的一致性,因为输入端和输出端通常并不属于流引擎,而会对接其他成熟的系统比如Kafka或数据库。输入端的一致性取决于它是否能始终以不变的顺序重放数据(比如从文件中读取)。输出结果的一致性跟数据库的一致性类似,主要取决于sink的输出是否具备幂等性(idempotency)或支持事务(transaction)。当然,在应用中,我们关注的一定是端到端(end-to-end)的一致性,也就是包括输入、系统内部、和输出的整体一致性。那么,流计算引擎要如何提供它这部分的保证呢?
分布式系统中常用这么几种语义(semantics)来描述系统在经历了故障恢复后,内部各个组件之间状态的一致性。严格程度从高到低为:Exactly Once(准确一次), At least Once(至少一次), At most once(最多一次)。
怎么界定每个部分的状态是一致的?我们来看几个例子:
数据源S发送给算子A数据,由A进行累加,也就是sum。A中的状态就是累加的和,S的状态就是当前已发送的流(因为A的当前结果和S之前发送的所有数据都有关)。一开始,S发出了1,2,3。A的状态依次更新为1,3,6。当S发出4的时候,A出现了故障。现在S和A的状态分别是S:1,2,3,4,A:6。接下来可能有这么几种情况:
我们再来看一看就算重复处理数据,状态依然一致的情况。也就是所谓的幂等(idempotent)操作(执行多次与执行一次的效果相同)。比如update就是一个幂等操作,将一个值update为1,这个操作不管执行几次,结果都是相同的。
回到语义上,如果不管故障是否发生,一个数据一定只会被处理一次(exactly once),就好像故障没有发生一样。那么不管是不是幂等操作,我们都能保证状态的一致性,这也是最严格的一致性保证。如果一个数据可能被处理不止一次(at least once),则仅能在幂等操作或能够分辨重复数据的前提下保证一致性。如果一个数据最多被处理一次(at most once),那就等于没有提供任何保证。毕竟就算所有数据都丢失,也符合这个“最多一次”的保证。
需要注意的是,exactly-once和at-least-once的保证都有一个前提:能够可靠地、按原来的顺序重放没有被处理的数据。比如上面的例子中,S和A一起倒退回上一个一致的状态,然后重放数据。一些常作为数据源的框架,比如Kafka,就有这个能力。如果数据源不提供这样的便利,我们也可以在系统内部实现它:我们可以将这些数据写入日志中,以便重放时查询。也可以存入缓冲区,当下游返回了ack,就将对应的数据从缓冲区中删掉(比如S收到A发出的“4已处理”的消息后,将“4”从缓冲区中删掉)。当需要重放时,只重发缓冲区中剩下的数据。