状态的一致性
当在分布式系统中引入状态时,自然也引入了一致性问题。
一致性实际上是"正确性级别"
的另一种说法,也就是说在成功处理故障并恢复之后得到的结果,与没有发生任何故障时得到的结果相比,前者到底有多正确?举例来说,假设要对最近一小时登录的用户计数。在系统经历故障之后,计数结果是多少?如果有偏差,是有漏掉的计数还是重复计数?
一致性级别
在流处理
中,一致性可以分为3个级别:
at-most-once(最多一次):
程序中不使用
这其实是没有正确性保障的委婉说法——故障发生之后,计数结果可能丢失
。at-least-once(至少一次):
这表示计数结果可能大于正确值,但绝不会小于正确值。也就是说,计数程序在发生故障后可能多算
,但是绝不会少算
。exactly-once(严格一次):
这指的是系统保证在发生故障后得到的计数结果与正确值一致.既不多算也不少算
曾经,at-least-once非常流行。第一代流处理器(如Storm和Samza)刚问世时只保证at-least-once,原因有二:
保证exactly-once的系统实现起来更复杂。这在基础架构层(决定什么代表正确,以及exactly-once的范围是什么)和实现层都很有挑战性
流处理系统的早期用户愿意接受框架的局限性,并在应用层想办法弥补(例如使应用程序具有幂等性,或者用批量计算层再做一遍计算)。
最先保证exactly-once的系统(Storm Trident和Spark Streaming)在性能和表现力这两个方面付出了很大的代价。为了保证exactly-once,这些系统无法单独地对每条记录运用应用逻辑,而是同时处理多条(一批)记录,保证对每一批的处理要么全部成功,要么全部失败。这就导致在得到结果前,必须等待一批记录处理结束。因此,用户经常不得不使用两个流处理框架(一个用来保证exactly-once,另一个用来对每个元素做低延迟处理),结果使基础设施更加复杂。曾经,用户不得不在保证exactly-once与获得低延迟和效率之间权衡利弊。Flink避免了这种权衡。
Flink的一个重大价值在于,它既
保证了exactly-once
,又
具有低延迟和高吞吐
的处理能力。
从根本上说,Flink通过使自身满足所有需求来避免权衡,它是业界的一次意义重大的技术飞跃。尽管这在外行看来很神奇,但是一旦了解,就会恍然大悟。
端到端的状态一致性
目前我们看到的一致性保证都是由流处理器实现的,也就是说都是在 Flink 流处理器内部保证的;而在真实应用中,流处理应用除了流处理器以外还包含了数据源(例如 Kafka)和输出到持久化系统。
端到端的一致性保证,意味着结果的正确性贯穿了整个流处理应用的始终;每一个组件都保证了它自己的一致性,整个端到端的一致性级别取决于所有组件中一致性最弱的组件。
具体划分如下:
source端
需要外部源可重设数据的读取位置.目前我们使用的Kafka Source具有这种特性: 读取数据的时候可以指定offset
flink内部
依赖checkpoint机制sink端
需要保证从故障恢复时,数据不会重复写入外部系统. 有2种实现形式:
幂等(Idempotent)写入
所谓幂等操作,是说一个操作,可以重复执行很多次,但只导致一次结果更改,也就是说,后面再重复执行就不起作用了。事务性(Transactional)写入
需要构建事务来写入外部系统,构建的事务对应着 checkpoint,等到 checkpoint 真正完成的时候,才把所有对应的结果写入 sink 系统中。对于事务性写入,具体又有两种实现方式:预写日志(WAL)和两阶段提交(2PC)
不可重置 | 可重置 | |
---|---|---|
任意(Any) | At-most-once | At-least-once |
幂等 | At-most-once | Exactly-once(故障恢复时可能出现不一致) |
预写日志(WAL) | At-most-once | At-least-once |
两阶段提交(2PC) | At-most-once | Exactly-once |
幂等:可重置,可能造成暂时的不一致,故障恢复的时候,比如往es写数据,比如10个批次写入,其中五条成功了,另外五条失败了,然后就需要重新读取失败的数(10变成了2个5),但是最终数据依旧是10条,所以在恢复的时候会造成暂时的不一致。
预写日志:是先把数据写入到预写日志
中,然后再往系统中写。这里涉及到两个操作,若其中一个操作出现错误,就需要进行重复写操作,导致数据重复。
两阶段提交:第一阶段,开启事务,第二阶段,关闭事务,并不会造成数据丢失与重复。