幂等写入操作:
所谓的幂等写入就是指一个操作可以重复执行很多次,但只导致结果一次的更改。例如对HashMap的插入操作,如果是相同的键值对,那么后面的重复数据就每有作用了。这种方式限制在于外部接受sink端必须支持这样的幂等写入,如Redis和MySQL。
但是,即便是这样的幂等写入,也存在一些隐患,例如我们当遇到故障恢复,回滚数据的时候,保存点到发生故障之间已经写入了一遍数据,且回滚的时候不能消除它。如果此刻有一个外部应用在读取写入的数据,短时间内可能会突然跳回之前的某个值,不过当数据恢复到超超过发生故障点的时候,最终的结果还是一致的。
举个例子
当sink到redis时
输入: key1,value1……key1,value2
最终结果: key1,value2
如果重复写入 key 相同的两次数据时,上一次的值被后面覆盖的值,是发生了变化的
事务写入
幂等写入的最大问题就是上面提到的,已经泼出去的水,嫁出去的姑娘——收不回来了。
那么事务写入就解决了这个问题,我们知道事务中的4个基本特性:ACID。所有操作必须完成,否则将会撤回所有操作。
基本思想
事务写入的基本思想就是用一个事务来与检查点绑定,在开始保存状态的同时开启一个事务,接下来所有的数据写入都在这个事务中进行,等到检查点保存完毕,将事务提交。如果中间出现故障,那么状态回退到上一个检查点,当前事务没有关闭(保存完),因此准备写入外部的数据也会被撤销。
事务写入又分为两种
预写日志(WAL)
1、先把结果数据作为日志状态保存起来,
2、在进行检查点保存时,也将这些结果数据持久化存储。
3、当收到检查点完成的通知后(JobManger会发送),将所有的结果一次性sink出去
预写日志的缺陷:
1、还需要持久化到磁盘,类似与批处理,延迟性高
2、预写日志会写入失败,因此我们需要接受类似ACK的确认之后,才代表真的sink完成。
3、如果在接受ACK阶段出现故障,那么恢复的时候flink依然会认为没有sink成功,会再次发送结果数据,就会造成数据重复。
两阶段提交(2PC)
基本思想
分成两段提交,先做预提交,等检查点完成之后再正式提交,这种提交方式是真正基于事务的,需要外部系统支持事务。
1、当收到检查点的分界线时,Sink任务启动一个事务,
2、接下来所有的数据都通过这个事务写入到外部系统,但是此时事务没有提交,所以数据不可用。
3、当sink任务接受到JobManger通知检查点完成时,正式提交数据,此时外部系统才可以读取到数据。
因此两阶段提交也真正意义上做到了exactly once。
但是,两阶段的提交需要的前提条件有很多:
✔ 外部系统必须支持事务,或者sink任务必须能模拟外部系统的事务,
✔ 检查点的间隔期间内,必须能够开启事务且接受数据的写入
✔ Sink任务能够再进程失败后恢复事务
✔ 提交事务必须是幂等操作,就是重复的提交是无效的
✔ 收到检查点的通知钱,事务必须是等待提交的状态。故障恢复的情况下,如果外部系统关闭了,那么未提交的数据将会丢失。
In conclusion:
具体在选取上,还是要根据业务的需求来权衡采取哪种方式