这是我参与「第四届青训营 」笔记创作活动的第6天
ExactlyOne
为什么要求语义保证
大数据中数据总是在多台服务器上运行并且传递的,如果不对语义进行约束,则会出现漏处理,重复处理,脏处理等等情况
还是拿最经典的银行做例子,张三给李四转钱,张三发了500,但是李四还没来得及接收,服务器崩了,服务器重启之后,张三少了500,李四却没有收到,整个系统平白缺失了500,失衡了,这明显就是会出大问题。这就是语义保证所意欲解决的事情
不同数据处理保证的语义
- At-Most-Once:出故障的时候啥都不做,确保绝大多数数据被处理过一次就行
- At-Least-Once:保证每条数据至少被处理一次,允许同一条数据被多次处理,类比上面例子就是,服务器重启后,程序发现张三-李四的事务并没有处理完,就又处理了一遍,那么张三就总共被扣除了1000,李四则得到了500
- At-Exactly-Once:最严格的语义,保证每条数据都被执行有且仅有一次,简单来说就是利用事务的原子性,如果没有完全执行成功,就回退到上一个状态,类比以上例子,服务器挂了重启,会发现张三并没有真正失去500,因为事务终止并回退了版本
实现 Exactly One
最初版-全程锁
就是当 source operator(资源算子) 需要进行状态快照的时候,广播自身的需求,然后阻塞住,等待下游所有算子全部运行并响应它,确保资源被完全消费后,将状态快照保存到远端服务器
这里最重要的是当进行快照时,资源算子会停止自己的业务处理,专心于快照事务处理,整个流程会被阻塞住,会导致大量无用的cpu开销以及时间问题,整个业务处理效率会直线下降
Chandy-Lamport
这种算法就是从起点另起一个线程,将自己的数据异步快照 (这里指的是 chick point) 完成后,依照业务处理流程的顺序将chick point传递下去,每到一个算子,那个算子就在完成自身事务时再次下发chick point,直到下游结束,将chick point回传到起点,将状态快照保存到远端服务器
这里的优势是 source operator 完成快照后,会异步将快照下发,而不会因此而阻塞住自己的业务功能,从而减少了效率损耗。
有些时候一个source 会被多个不同的业务算子消费,也就是同一个数据会被不同的业务并发的访问,这时,source operator完成快照则需要同时为业务A和业务B两者广播快照要求,chick point 同时在 A,B 中传递,如果A完成了,B还没有完成,则需要阻塞A等待B,这种场景被称为 Barrier Alignment(阻塞对齐?),通过对已完成的业务设置 barrier(障碍),等待未完成的业务,实现 Alignment(对齐)。
两个阶段提交协议
这个是flink中的 exactly once 实现解决方案。主要思想就是使用了事务的原子性思想,利用缓存去完成了事务状态的暂存和事务的回滚。
这里也是 source operator 异步广播,将快照要求广播到所有引用了该资源的事务中,然后所有事务尝试执行,如果执行成功则向 source 发送成功信息(这里成功是将事务放到缓存中,并没有真正写入数据库/磁盘),失败则向 source 发送失败信息。
source 如果 callback 得到的全是成功信息,则会再次广播,要求事务 commit(提交,从缓存写到数据库/磁盘)
source 如果 callback 存在一个失败信息,则会广播要求所有事务roleback(回滚,说明缓存得到的数据无效)
总结
其实我觉得得从某些角度来看,flink 与 chandy-lamport 的 exactly one 的解决思路都挺相似的,但是单纯从我的叙述角度来看,二者的区别就像是面向过程编程和面向对象编程一样
当我们学习 Candy-Lamprot 时,我们会不由自主的去跟着他的执行顺序去思考,你会想这顺着业务流顺序去看,还会操心其中的失败
但是 flink 的解决方案则完成了封装,我们只需要知道这个业务流程要么成功提交,要么失败回滚,业务内部的逻辑不需要我们自己去考虑,我们只需要思考:我要进行快照->我需要为那些业务提供什么数据->业务要为我返回什么数据->我根据返还数据判断提交/回滚->再次广播