本文记录一下我比较感兴趣的实时流处理方面的知识,从计算Flink,到存储Pravega,再到消息中间件Kafka,理论知识四大方面学习。如有错误请在评论区指正。实时更新~
理论知识
有状态的流计算
无状态的意思是流处理的时候只需要针对某一条消息进行处理,结果只受到这条消息的影响,比如在每一条消息后面追加字符“a”;
有状态指的是在消息处理的时候需要保存前后多条消息的相关信息,结果受到多条消息的影响,比如count,average操作。所以有状态操作更加强大但是实现起来更加困难,特别是当它也支持“恰好一次”的时候。
流消息的三种语义
- At most Once
接收者最多收到一次消息
- At Least Once
发送者给接收者发送消息,如果一直收不到接收者的确诊消息,发送者会一直重发。
- Exactly Once
对于一条消息,接收者确保只收到一次。每个输入的事件最终只会影响结果1次,即使机器或者软件出现故障,既没有重复数据,也不会丢数据。
不管处理时候有没有错误发生,计算的结果都应该是一样的。
所以在计算的时候如果发生了错误,系统重新计算,结果一定要和没有错误发生的结果是一样的。
先讲讲其他流处理架构是如何做fault tolerance的.
1.Storm的Record acknowledgement
每一个被storm的operator处理的数据都会向其上一个operator发送一份应答消息,通知其已被下游处理。storm的源operator保存了所有已发送的消息的每一个下游算子的应答消息,当它收到来自sink的应答时,它就知道该消息已经被完整处理,可以移除了。
如果没有收到应答,storm就会重发该消息。显而易见,这是一种at least once的逻辑。另外,这种方式面临着严重的幂等性问题,例如对一个count算子,如果count的下游算子出错,source重发该消息,那么防止该消息被count两遍的逻辑需要程序员自己去实现
2.Spark streaming的micro batch
通过控制每批计算数据的大小来控制延迟与吞吐量的制约,如果想要低延迟,就用小一点的batch,如果想要大吞吐量,就不得不忍受更高的延迟
我们重点来看Flink与Kafka的exactly-once语义支持
Flink 对Exactly Once的支持
实现Exactly-Once的关键在于能够准确知道和快速记录下来当前的operator的状态
Flink定期会通过记录checkpoint,即一下信息的的一致性快照。
- 应用程序的当前状态
- 输入流的位置
Flink可以配置一个固定的时间点,定期产生checkpoint,然后把checkpoint的数据写入持久存储系统。如S3或HDFS。将checkpoint数据写入持久存储是异步发生的,这意味着Flink应用程序在checkpoint过程中可以继续处理数据。
发生机器或软件故障,重新启动后,Flink应用程序将从最新的checkpoint点恢复处理; Flink会恢复应用程序状态,将输入流回滚到上次checkpoint保存的位置,然后重新开始运行。这意味着Flink可以像从未发生过故障一样计算结果。
但以上的exactly-once语义都是基于flink内部实现的
那如果要实现端到端的Exactly-Once语义,即Flink写入外部系统也需要能够满足Exactly-Once语义。这些外部系统必须要提供提交或者回滚的方法,然后通过Flink的checkpoint来维护一致性。
因此就要引入两阶段提交协议这个在分布式系统中协调提交和回滚的方法了。
然后下面讲一下Kafka的exactly-once
如果Kafka不支持exactly-once操作,那么可能出现下面的错误:
1.重复写入
2.计算状态多次更新
3.重复消费
那Kafka是怎么保证自己的exactly-once呢?
1.把计算结果写入输出的topic中——生产者提交数据到broker
2.broker把更新操作写入更新日志changelog中——broker进行消息处理
3.消费者消费数据,提交偏移量,把消息的消息偏移量写入相应的topic中
对于重复写入问题:
Kafka是怎么实现消息传输的幂等操作呢?每一条消息除了消息的Key和消息的值,还会增加两个字段:
分别是ProducerID和一个全局唯一的序列号,这个序列号由broker生成。
那么如下图所示,闪电表示消息发送后的ack确认失败,消息重传,Kafka会根据
对于第二点:broker的更新操作
需要实现把"消息读入->消息处理->结果写出"作为事务操作,也就是说这个操作需要满足ACID。
那Kafka是如何在保证系统性能的基础上实现事务的呢?
左下角表示事务日志,系统会存在一个事务锁,在某一个事务开始之前需要获得这个锁。
首先告诉系统开始一个事务,然后接着发送消息到brker相应的topic和partition,但是这些消息暂时是不能被消费方消费的,只有在所有操作成功完成之后再提交这个事务,这时候才标记这些消息是可以消费。
所有过程都会涉及到相应的log,只有在完成了这个事务之后,消费者才能消费这个事务所提交的消息。这么做保证了消息发送和业务逻辑要么全做,要么不做。不做意味着要支持事务的回滚,而事务日志是保存在相应的topic中的。
第三点:消费端如何保证
1.Kafka消费者只会读取那些被成功提交了的数据。所谓成功提交指的是消息的partition的leader和其所有的follower成功记录。这一方面也保证了消息的高可用。
2.消费端再消费数据的时候会提交偏移量到zk,然后broker根据zk的变化情况把相应数据标记为"已消费"。
正常情况下就不会出现重复消费的情况。那如果向zk提交偏移量之后服务宕机了,即broker里的数据没有标记为已消费,那么服务重启之后也会发生重复消费啊!
可以给消息分配一个全局ID,然后建立一个本地消息表,把提交zk偏移量和写入本地消息表放在一个事务进行,然后消费的时候再去查表看有没有消费过。
流处理系统架构
该部分是《数据密集型应用系统设计》关语流处理的部分
首先,批处理和流处理有何不同?
批处理的假设是:输入有界,如MapReduce的排序操作必须是先读取整个输入然后才能开始生成输出。
而流处理所处理的数据是无限的,批处理需要人为地把数据划分为固定时间段的数据块,如每天结束时候来处理一天的数据,或者在每个小时结束的时候处理这一个小时的数据。这种方法毫无疑问效率是很低的,那么流处理的思想就是为了减少这种延迟,每当有事件就可以开始处理。
一般来说"流"是指随着时间推移而持续可用的数据。所以可以把流当作一种数据管理机制一种无界,持续增量的处理方式。
接下来着重解决的问题是,如何通过网络来表示,存储和传输流?
流和数据库又有什么关系?
最后