一致性实际上是“正确性级别”的另一种说法,也就是说在成功处理故障并恢复之后得到的结果,与没有发生任何故障时得到的结果相比,前者到底有多正确。例如,要对最近一小时登录的用户计数。在系统经理故障之后,计数结果是多少?如果有偏差,是有漏掉的计数还是重复计数?

在流处理中,一致性可以分为三个级别:

  1. at-most-once(最多一次): 这其实是没有正确性保障的委婉说法——故障发生之后,技术结果可能丢失。
  2. at-least-once(至少一次): 这表示计数结果可能大于正确值,但绝对不会小于正确值。也就是说,计数程序在发生故障后可能多算,但是绝不会少算。
  3. exactly-once(精确一次): 这指的是系统保证在发生故障后得到的计数结果与正确值一致。既不多算也不少算。

Storm Trident和Spark Streaming是最先保证exactly-once的系统,但是它们却在性能和表现力两个方面付出了很大的代价。为了保证exactly-once,这些系统无法单独地对每条记录运用应用逻辑,而是同时处理多条(一批)记录,保证对每一批的处理要么全部成功,要么全部失败。这就导致在得到结果前,必须等待一批记录处理结束。因此,用户经常不得不使用两个流处理框架(一个用来保证exactly-once,另一个用来对每个元素做低延迟处理),结果使基础设施更加复杂。曾经,用户不得不在保证exactly-once与获得低延迟和效率之间权衡利弊。Flink避免了这种权衡。

Flink的一个重大价值在于,它既保证了exactly-once,又具有低延迟和高吞吐的处理能力。

端到端的状态一致性

在实际流处理应用中,除了流处理引擎也至少还有一个数据来源组件和一个数据终点组件。端到端的保障指的是整个数据处理管道上结果都是正确的。

在每个组件都提供自身的保障情况下,整个处理管道上端到端的保障会受制于保障最弱的那个组件。

具体划分如下:

  • source端:需要外部源可重设数据的读取位置。如kafka读取数据的时候可以指定offset
  • flink内部:依赖于checkpoint机制
  • sink端:需要保证从故障恢复时,数据不会重复写入外部系统。实现方式有两种:
  • 幂等(Idempotent)写入:指一个操作可以重复多次执行,但只导致一次结果更改;
  • 事务性(Transactional)写入:需要构建事务来写入外部系统,构建的事务对应着checkpoint,等到checkpoint真正完成的时候,才把所有对应的结果写入sink系统中。具体实现方式有两种:
  • 预写日志(WAL)
  • 两阶段提交(2PC)