流处理比起之前的批处理而言,需要考虑的东西更多。批处理有个前提,那就是输入必定是固定的大小,而流处理处理的数据是不会暂停的,与线上服务需要处理的数据也不一样,线上服务需要等待使用者发送请求再回复请求。流(stream)这个概念应用的相当广泛,例如TCP协议,Unix里的pipeline,而流处理的流特指的是‘event stream’,什么是event呢?它指的是一个携带着时间以及信息的不可变,self-contained的对象,event可以是一个文本,或者其他什么的二进制文件。相关的event可以包含进一个topic或者stream。说完了概念,那我们再看看两种主要的流处理框架。
消息系统
我们可以理解为一个producer直接发送包含event的消息给consumer。最简单的消息系统就是Unix的pipeline和TCP链接。学术上来说,这个就是publish/subscribe模型。听起来很简单,但是所有可用的消息系统都会面临着两个问题,而且必须给出解答。第一个,如果producer产生消息的速度比consumer快怎么办?,第二个,节点突然挂掉,怎么办?对于第一个问题,大体上来说,有三个选项,1.丢掉这个消息2.缓存到队列中3.控制流量。TCP选择的是第三个,阻塞生产者,减少缓存buffer。更常见的是第二个,但是缓冲到队列,就会面临着这个节点挂掉或者disk空间不足怎么办的问题。又是一个trade-off。
消息的传递有两种方式,一个是直接发送消息,像UDP一样,不过着隐含着一个前提,那就是producer和consumer必须不能离线,否则就会丢失消息。第二个传递消息的方式就是通过中介messagebroker,采用这种方式就不需要考虑producer和consumer的网络问题,也可以把消息的存储在broker,相应的trade-off就是放弃了producer和consumer的同步,选择了异步方式。
除了消息的传递,我们还需要考虑多个消费者消费消息的情况。两种模式,一种是Load balancing,消息发送给consumer,然后由consumer共享给具有同一个topic的consumer。第二种是Fan-out,消息直接发送个所有的consumer。当然这两个模式不是绝对分割开的。
那我们该如何知道consumer真的消费了消息了呢?最常见的方法那就是consumer发送一个acknowledgments给生产者或者broker确认接受了消息,否则就要redelivery。
基于log的消息系统
第一类系统对于消息的保存可能不是那么在意,第二类的系统就选择了log保存信息,这个时候broker称为log-based message broker。Log一般意义而言,都是append-only,consumer消费消息根据offset去读取数据,offset的保存交由consumer,选择最新的offset读取数据。当然,对于log的存储,我们更需要考虑如果超出disk,要怎么分担数据。
除了消息处理框架后,我们还需要来看看处理完的数据和database的交互。想象一下,如果stream和database要保持同步的话,那么对于数据库的索引这些结构, 如何保证同步呢,我们依然会面临着索引和数据库存储不同步的问题。
数据库和流处理的交互除了导出数据到数据库,还必须考虑流处理获得数据库的更新。有两种不同的获得数据变化的思考,一个叫做change data capture(CDC),它是一种确保所有数据变化都反映到对应系统的机制,类似于single-leader,数据库是leader,其它交互部分都是follower。传统数据库的实现方式是触发器,但是有着严重的性能影响,于是又想到了log,利用特殊的log获得数据库的变化,log真的是一种万能药啊。第二种叫做Event Sourcing,这个基础是不可变的events记录,只能增加,而CDC则是时时反映数据库的变化。
那么让我们再次回到流处理本身,流处理在现实生活中可以用来处理复杂的event,对流本身进行分析,维护materialized view,对event进行搜索。不过在考虑这些之前,我们还是注意到时间这个魔术师,一般而言时间可以分为事件发生的时间(event time和处理时间(processing time),在event处理时,都需要考虑到着两种时间。当你知道了这两个时间,那么就要考虑时间窗口的概念,可以分为四种:1.Tumbling window,固定长度,所有event都只会属于一个时间窗口。2.Hopping window,也是固定长度,但是允许时间可以有小量偏差。3.Slidingwindow,选择了固定大小event,但时间窗口不再固定。4.Sessionwindow,这个把来自同一个user的event都聚合在一起处理。
前面批处理提到了join,在流处理也会面临着join操作。我们可以根据stream和table的交互简单的分为三种,1.stream-stream join,2.Stream-table join.3.table-table join。
流处理大致上就描述完了,除了上面这些问题,还需要考虑fault-tolerance,这个我们可以选择checkpoint,atomiccommit或者就是选择Idempotence(也就是通过唯一ID)。
不同于批处理在理论模型的简单,流处理面临着更为重要的数据一致性的问题,到目前为止的都还只是浅尝辄止,构建流处理的系统更需要工程师的认真考虑。
领取专属 10元无门槛券
私享最新 技术干货