现在大数据,云计算已经成为互联网的标配,但是现在主流的大数据处理依旧是使用batch模式,batch模式就是将数据按某种规则分成块,然后对整个块跑计算逻辑,缺点是延迟太高(至少是分钟),常用的工具就是Hadoop。在日益变化的需求面前,高延迟越来越不能忍受,因此Streaming模式应运而生,他最大的特点就是低延迟,最快能到毫秒级别,常用的Streaming工具主要是storm,spark等,但是这些工具都有各自的优缺点,功能上不能完全取代batch,这篇文章就是想深入分析什么样的Streaming系统能彻底替代batch,并最终打败batch。
尽管现在市场上对streaming越来越关注,但是相对于batch来说,大部分streaming系统还不是很成熟,所以streaming系统正处于积极开发和进步中。
作为一个在分布式steaming系统上工作过5年的从业者(MillWeel, Cloud DataFlow), 我非常荣幸给大家分享streaming系统能解决什么问题,以及batch系统和streaming系统在语义上的区别,这非常重要,会帮助我们更好地使用它。内容众多,我会分成两篇文章来讲述:
下面是一篇很长的文章,不要分心。(译者:原文是nerdy,主要指呆滞但是专注的技术宅)
本节讲述一些非常重要的背景知识,帮助理解剩下的部分。
在我们深入之前,我想先解决:什么是streaming。现在‘Streaming’有很多不一样的意义,让我们误解streaming到底能解决什么问题,所以我一定要先 精确 定义它。
问题的本质是:很多术语不是告诉我们他们是什么,而是告诉我们他们是怎么实现的,比如Unbounded data processing无穷数据处理和Approximate resulte近似结果处理被认为就是streaming,而实际上只是streaming常常用来解决这类问题,其实batch也能解决。没有精确定义的streaming被人们狭隘的认为streaming只是拥有某些常被称做‘streaming’的特性,比如近似结果。一个设计合理的streaming系统是可以提供 correct正确 ,consistent一致 并且 repeatable可重复计算 的结果,就像现在所有批处理引擎,我觉得 streaming定义 是:一种能处理无穷数据的数据处理引擎 。(为了这个定义更完整,我必须强调它包含了我们常说的streaming和micro-batch)。
下面是一些经常和‘streaming’联系在一起的几个术语,我分别为他们给出了更精确,更清楚的定义,我建议整个工业界都应该采纳它们:
到此,我已经定义了什么是流处理引擎:一种用来处理无穷数据的引擎,对于其他术语,我不会用streaming来替代他们。我们已经在Cloud Dataflow中使用这些术语了,我建议其他人也可以采用它们。
(译者:在下面的文章中,我会直接用streaming和batch来代替流处理引擎和批处理引擎,这样可以少敲几个字,敲字很辛苦)
下面让我们看看streaming到底能做什么和不能做什么,特别是设计合理的streaming能做什么。长久以来,streaming被工业界狭隘地认为是提供低延迟,近似结果,批处理是用来提供正确结果的,比如 Lambda Architecture (译者注:下文我会用Lambda架构表示,我理解为啥叫它Lambda)。
如果你熟悉Lambda架构,你应该知道这个架构最基本的思想就是使用批处理引擎和流处理引擎,执行一样的计算,流处理引擎提供低延迟不精确结果(可能是使用近似算法,或者流处理就不准备提供正确性),之后批处理引擎提供正确的结果,覆盖流处理引擎的结果。起初,这个架构,由 Nathna Marz, Storm创始人 提出,真的获得不少成功,因为这个想法在当时看来真是不错,那时候流处理引擎还不令人满意,批处理引擎也不能像我们预期的那样发展得很好,Lambda架构却提供一种短期解决方案,并且很多项目真的用了。Lambda架构也存在缺点:维护成本高,你需要维护两套独立的系统,而且需要在最后合并他们的结果。
作为一个在 强一致 流处理引擎工作多年的从业人员,我不同意Lambda架构的出发点,而且我特别喜欢 Jay Kreps’ Questioning the Lambda Architecture 这篇文章,它很有远见地质疑了双引擎的必要性,并且给出很强的理由。Kreps使用repeatability可重放的消息队列系统 kafka 去解决repeatability可重放问题,然后他提出一种新的架构:Kappa架构:基本思想就是只使用一套合理设计的引擎。虽然我不认为这个概念需要一个新的名字,但是我非常赞同他的思想。
(译者:可重放是指你可以随时回到一个时间点顺序读取任何信息,kafka能做到这点,但现实是kafka也有开销,比如你的model需要一年的历史数据,你会让kafka存下一年的数据?基本上不会,你应该把数据存在开销更低的系统,比如hadoop,但是streaming系统可以读取回这些历史数据)
现在我们应该更近一步,我认为:设计合理的streaming是可以提供比batch更多的功能,到那时我们不再需要batch。(译者:当前所有streaming比batch占用更多的资源,从商业上说batch一定会持续存在直到streaming能更加高效利用资源)Flink就是利用这个想法创造了一个完全的streaming系统,并且支持batch模式,我非常喜欢它。
随着streaming系统越来越成熟,它将提供一种无穷流处理的框架,并且让Lambda架构消失在历史中。我相信它已经在发生。如果我想彻底打败batch,我们需要完成两件事:
本质上,正确性取决于consistent一致的存储。steaming需要一种类似checkpointing持久化存储,正如Kreps在它这篇文字所写,这种存储在机器挂掉的情况也能保证一致性。当几年前Spark streaming首次出现时,它在streaming世界里就是一致性的代名词。一切都在向前发展,仍有很多streaming系统不提供强一致性,我实在是不理解为啥at-most-once仍然存在,但是实际情况它还在。(译者:如果at-most-once指的是系统保证这个消息最多被处理一次,其他方式是:at-least-once至少一次和exactly-once只有一次,作者想表达的是最多一次就是系统会丢数据,除非你不关心正确性,否则这种方式就不该存在。但实际是上实现三种方式的开销不一样,随着系统越来越成熟,可能三种开销就不会那么大,到那时候估计就没人愿意使用最多一次的方式了。)
重要的事情再说一次:强一致性必须要exactly-once处理,否则无正确性可言,而且只有这样才能追上并且最终超越batch系统。除非你毫不在乎结果的正确性,否则我真诚地建议你放弃任何不提供强一致性的streaming系统,不要浪费时间在他们身上。
如果你真的想学习如何给streaming系统设计强一致性,我推荐你可以读读 MillWheel 和 Spark Streaming 的论文,这两篇论文都花费了很长的时间讲述一致性。本文时间有限,就不在此详述了。 (译者:还没读,看完会给大家分享下)
当我们处理无穷无序数据时,时间工具是一切的基础。当前,越来越多的需求要求我们处理无穷无序数据,而现有batch系统(包括大多数streaming系统)都缺乏必要的工具去解决这个困难。我会用本文剩下的部分和下一篇文章着重解释它。(译者:无序是难点,大部分分布式系统都不可能提供顺序保证,这里时间工具是指系统提供api,让我们自己控制超时以及如何按时间分块,下面会有详述。)
我们首先会了解时间问题中重要的概念,而且深入分析什么是无穷无序数据的时间差,之后我会用剩下的部分讲解batch和streaming系统处理有限和无穷数据的常用方法。
(译者:下面我会直接用event time 和processing time)
很多需求都不关注event time,这样的生活会简单很多,但是还是有不少需求是关心的,比如为带时序的用户行为建立特征,大多数付费应用和很多异常检查。(译者:广告的attribution就是带时序的行为,你只能在看过广告后点击)
完美情况下,Event time和processing time应该永远是相等的,事件发生后立即被处理。现实是残酷的,两者的时间差不仅不是0,而是一个与输入,执行引擎和硬件相关的函数。下面几个是经常影响时间差:
* 有限的共享资源:比如网络阻塞,网络分区,不独占条件下的共享CPU
* 软件:分布式系统逻辑,竞争
* 数据本身的特征:包括key的分布,吞吐量差异,无序(比如:很多人在坐飞机时关闭飞行模式使用手机,等手机网络恢复后手机会把事件发给手机)
下图就是一个真实的event time和processing time差异图:
黑色点线代表两个事件完全一致。在这个例子中,系统延迟在中间到最低点。横向距离差代表两个时间的时间差,基本上这个时间差都是由延迟导致。
这两个时间没有固定的相关性(译者:不能简单的用一个函数去计算),所以如果你真的关心event time,你就不能用processing time去分析你的数据。不幸的是大多数现有系统都是用processing time去处理无穷数据,他们一般都会将输入按processing time用一些临时的分界线拆分小块去处理。
如果你们系统关心正确性,那就千万不能用processing time去分块,否则一部分消息会因此被分到错误的块,无正确性而言。我们会在下面看到更多这样的例子。
即使我们用event time作为分界线,其实也不能实现正确性。如果event time和processing time之间没有一个可预测的关系,你怎么能确定你已经收到所有消息?(比如:你要统计5分钟的数据,你怎么能保证收到所有5分钟的数据)现在大部分数据处理系统都依赖某种“完整性”,但是这么做让他们处理无穷数据遇到严重的困难。(译者:完整性一般都是用超时来实现,等一段时间发现没有了就放弃)
我们应该设计工具能够让我们生活在这种不确定性中(译者:不确定性是指时间差不能预测)。当新数据到底时,我们可以获取或者修改老数据,所有系统都应该自然而然去优化“完整性”,而不是认为它至少可有可无的语义。(译者:优化完整性的意思是系统能够提供api控制超时。)
在我们深入如何实现类似Cloud Dataflow数据模型前,我们先了解一个更有用的话题:常见数据处理方式。
现在我们可以开始谈一些重要的数据处理模式了:batch和streaming(我把micro-batch归到streaming中,因为两者差异在这里不是很重要)。
处理有限数据很简单,大多数都已经熟悉。在下图中,左边是一个完整数据集,中间是数据处理引擎(一般是batch,当然一些设计合理的streaming也可以),比如 MapReduce ,右边则是处理的结果:
更让我们感兴趣的是无穷数据,我们会先分析传统的batch,然后分析常见streaming或者micro-batch。
虽然batch从字面上看不像用来处理无穷数据,但是从它出生就已经被业界使用了。大家都能想到,只要我们能把无穷数据分块,我们就能用batch引擎出处理。
最常见的方法就是把数据拆分成固定大小的块,然后依次处理各个块数据。特别是日志数据,我们可以自然而然把数据拆分成以时间为单位的树状结构,这个很直观,因为基本上就是按event time分块。
实际上,大部分系统仍然要处理完整性问题:如果一些事件由于网络延迟到达,怎么办?如果消息是全球的,但是存在一个公共地方,怎么办?如果事件来自手机,怎么办?我们可能需要用一些特别的办法解决它们:比如延迟事件处理直到所有数据都到达,或者重新计算整个数据集只要有新的数据到达。
** Sessions 序列 **这个比简单分块复杂,我们需要把事件拆的更细。Sessions一般是指把事件按活跃间隔拆分。如果在batch中计算sessions,你会出现一个Session横跨多个块,比如下图红色部分。当然,当你增加每个batch的时间间隔,这种横跨多个batch的概率会降低,但是它会带来更高的延迟。另外一个方法是增加额外的逻辑出处理这种情况,代价是逻辑复杂。
两者都不是完美的方法,更好的方法是用streaming的方式处理session,我们下面会讲。
streaming从开始就是设计用来处理无穷数据,这跟大多数batch引擎不太一样。正如我们上面说的,大多数分布式系统的数据不只是无穷,还是一些其他让人讨厌的特性:
因此出现了一批处理这类数据的方法,我大致把他们分为4类:
我们简单分析下他们
这类处理逻辑完全跟时间没关心,只是更数据本身有关,batch和streaming在处理这种逻辑时没有本质区别,所以大部分streaming都支持。当然batch引擎也支持,你可以用任意方法把数据分块,再依次处理。下面是一些实际例子:
Filtering过滤
最基础的逻辑,比如你在处理web日志,你想要某些域名的数据,你可以在每个事件到底的时候丢掉那些不想要的,跟无穷,无序,时间差一点关系都没有。
Inner-joins
当join两类数据时,你只关系两者都到达的情况,如果一方先到,你可以把它存起来,等第二个到底之后取回前一个,然后计算。当然你想回收一些单个事件占用的资源,那就是跟时间有关了。(译者:超时回收)
当然如果要支持某些outer-join,就存在数据完整性问题:当你看到一方时,你怎么知道另一方是否还会来?你肯定不知道,除非你设计超时,超时又跟时间相关,本质上又是另一种形式的分块,我们会详细分析。
第二大类就是近似算法,比如近似Top-N,streaming K-kmeans等等。近似算法的好处是开销低,可以处理无穷数据。坏处是这类算法数量有限,而且实现复杂,近似的本质限制他们的使用,不可能所有场景都能用近似。这些算法大多数都有一些时间特征,比如decay,当然他们大多用processing time。另一个很重要的特点就是他们都提供可控的错误率。如果错误率可以根据数据到达的顺序预测出来,这些错误率就可以忽略不计,即便是无穷数据,这点很重要,你最好能记住这点。
近似算法让人很兴奋,但本质上是另一种形式的时间无关算法。
剩下两个方法都是讲如何将数据按时间分块。我想先讲明白啥是windowing,它就是把无穷或者有限数据按分界线拆分成有限的块。下图是三种不同的分块策略:
我们会看看用processing time和event time去分窗口到达有什么不同,当然从processing time开始,因为它更常用。
** Processing time分块 **
系统只需要保存来的数据直到一段时间完成。比如:5分钟分块,系统可以保存5分钟的数据,然后认为所有数据都到了,发给下游处理就行。这种方式有下面几个很好的特性:
当然processing time也有缺点: 如果数据含有event time,并且你想用processing time来分块解决,那么这些数据必须是有序的。 不幸的是分布式上游大部分无法保证有序。
一个简单的例子:手机app想了解用户使用情况。如果一个手机断网一段时间,这段时间的数据不会即时发到服务器端直到手机再次连网。我们无法用processing time分块获得任何有用的信息。
另一个例子:有些分布式上游在正常情况下可以保证event time顺序,但是不代表上游一直能保持一样的时间差。比如一个全球系统,由于网络问题,可能某个州的网络导致很高的延迟,如果你是processing time分块,分块的数据已经不能代表当时的情况了,可能是新旧数据的混合。
我们希望这两个例子都是用event time分块,保证逻辑的正确性。
** Event time分块 **
Event time分块能让我们观察到上游数据在事件发生时的情况,这是最正确的方法。但是,大多数数据处理系统都没能从语义很好的支持它,虽然我们知道任何强一致性的系统(Hadoop或者Spark Streaming)经过一些修改是能解决的。(译者:作者说从语义上支持就是系统能保证按Processing time分块,我常用的工具都没有这样的语义)
下图显示了一个按event time分成一小时的块:
白色的线代表事件的processing time不同于event time。毋容置疑,event time分块一定能保证event time正确性。
另一个好处是你可以创建动态大小的块,比如session,但是不存在session跨越多个固定的块。
当然,任何事情都是有代价的,包括event time分块。它有两个很明显的缺点,因为块必须比实际长度存活更久:
这篇包含太多信息了。如果你读到这里,你应该受到表扬,我们已经完成了一半了,让我们回顾下我们讲了什么,并且知道下一篇讲什么。让人高兴的是这篇无聊些,但是下一篇一定有趣。