0x00从实时数仓的历史谈起实时数仓的历史,有三个显著的分水岭。第一个分水岭是从无到有,随着以Storm为代表的实时计算框架出现,大数据从此摆脱了MapReduce单一的计算方式,有了当天算当天数据的能力。第二个分水岭是是从有到全,以Lambad和Kappa为代表的架构,能够将实时与离线架构结合在一起,一套产品可以实现多种数据更新策略。第三个分水岭是从全到简,以Flink为代表的支持窗口计算的流式框架出现,使离线和实时的逻辑能够统一起来,一套代码实现两种更新策略,避免了因为开发方式不统一导致的数据不一致问题。未来会有第四个分水岭,就是从架构走向工具,以Hologres为代表的HSAP引擎,用服务分析一体化的设计理念,极有可能彻底统一掉分析型数据库和业务数据库,再配合Flink,真正实现数仓的彻底实时化。未来不再存在离线和实时的区分,Flink+Hologres,用一套结构、一套产品,就完事了。过去,面对实时,数仓的逻辑是:性能不够,架构来补;现在,面对实时,数仓的逻辑是:既要、还要、全都要。过去,我们使用OLAP引擎,是为了更快的查询数据;现在,我们使用OLAP引擎,是为了当线上业务库,替代Mysql用。当我们觉得摩尔定律要失效时,它却仍然在发展。当我们觉得数仓架构定型时,它却依然在高速演化。只有了解历史,才能看清未来,我想重新了解一下Lambda/Kappa/Flink的历史,熟悉架构的演进,以此来探讨实时数仓建设的一些问题。我们先从Lambda的历史讲起。
0x01Lambda对低成本规模化的数据服务,以及尽可能低延迟的数据应用,是推动Lambda架构诞生的原动力。早期的Storm框架,能够帮助解决低延迟的问题,但这个框架并不完美,其中一个痛点,便是Storm无法支持基于时间窗口的逻辑处理,这导致实时流无法计算跨周期的逻辑,用户不得不寻找一些额外的方法来实现。为了解决痛点,Storm的作者NathanMarz,提出了一种混合计算的方式,通过批量MapReduce提供离线结果,同时通过Storm提供最新的数据,这种离线与实时混合的框架,就是如今广为流传的“Lambda架构”。亚马逊上有一本《BigData:Principlesandbestpracticesofscalablerealtimedatasystems》,就是NathanMarz用来详细阐述Lambda架构的,而最重要的图,就是下面这张:Lambda架构通过两条分支来整合实时计算和离线计算,一条叫SpeedLayer,用于提供实时数据,另一条叫作BatchLayerServingLayer,用于处理离线的数据。为什么这么设计呢?在深入理解Lambda之前,我们首先了解一下数据系统的本质,主要有两个:一个是数据,另一个是查询。数据的本质,主要体现在三个方面:when:数据的时间特性,对于分布式系统而言,这个特别重要,因为时间决定了数据发生的先后顺序,也决定了数据计算的先后顺序;what:数据的内容特性,数据本身是不可变的,分布式系统对数据的操作,可以简化为两点:读取和新增,也就是Create和Read,而Update和Delete可以用Create来替代;where:数据的存储特性,基于分布式系统数据不可变的特性,存储数据只需要进行添加即可,系统的逻辑设计会更加简单,容错性也可以大幅度提升。查询的本质,特指一个公式:Query=Function(AllData)。表面的意思是,不论数据是存在Mysql这种RDBMS的,还是MPP类型的,或者是OLAP、OLTP,甚至是文件系统,都能够支持查询。更深入一点,意味着数据的查询,要支持Monoid特性,例如整数的加法就要支持:(a+b)+c=a+(b+c)。概念本身比较抽象,这里只需要了解,如果函数满足Monoid特性,那么计算时就可以支持分散到多台计算机,进行并行计算。了解了数据系统的本质,我们再看实时架构,我们就发现,如何在平衡性能和资源消耗的基础上,满足Query=Function(AllData)的特性,就是Lambda架构设计的精髓。离线数据能够支持庞大数据量的计算,耗时长,但处理的数据量大,适合历史数据的处理。并且,因为我们不能避免系统或者人为发生的错误,那么离线方案对于结果的兜底,要远优于实时方案,任何问题都可以通过逻辑的修正,来得到最终正确的结果。所以查询应该是:batchview=function(alldata)而实时数据要支持实时的部分,为了实现低延迟的特性,势必要减少计算的数据量,那么只针对增量的做处理,作为对离线处理的补充,就是一种最优的方案。这里查询就变成了:realtimeview=function(realtimeview,newdata)而最终的结果,因为要支持历史+实时数据的查询,所以需要合并前面的两个结果集。如果我们的查询函数满足Monoid性质的话,只需要简单合并两个流的数据,就能得到最终的结果。所以:query=function(batchview,realtimeview)总结下来,Lambda架构就是如下的三个等式:batchview=function(alldata)realtimeview=function(realtimeview,newdata)query=function(batchview,realtimeview)这么设计的优点,NathanMarz也进行了总结,最重要的有三条,即:高容错:对于分布式系统而言,机器宕机是很普遍的情况,因此架构的设计需要是非常健壮的,即便是机器跪了,任务也要能正常执行。除此之外,由于人的操作也很容易出现问题,因此系统的复杂性要控制在一定的程度内。复杂度越高,出错概率越大。低延迟:实时计算对于延迟的要求很高,对应的系统读操作和写操作应该是低延迟的,对系统数据的查询响应也应该是低延迟的。可扩展:系统不仅要能够适应多种业务形态,比如电商、金融等场景,当数据量或负载突然增加时,系统也应该通过增加更多的机器来维持架构性能。因此,可扩展性,应该是scaleout(增加机器的个数),而不是scaleup(增强机器的性能)。最后,Lambda的架构,就设计成了我们经常看到的样子:尽管结果看起来是“正确的废话”,但思考的过程,却是非常的透彻和深入。这本书很不错,但了解的人不多,感兴趣的可以了解一下。
0x02KappaLambad框架尽管考虑的很深入了,但仍然存在两个问题:第一个是数据复杂度高,由于有多套数据源,因此口径对齐就成为了一个大问题;同时,产品在建设时,交互上要考虑实时和离线两套逻辑,面对逻辑多一点的业务,计算也会变得非常复杂;第二个是搭建成本高:不仅框架需要维护多套,开发人员需要熟悉多种框架,而且相互之间运维成本很高。离线和实时维护两套逻辑,太容易导致数据结果不一致,这个点非常痛!LinkedIn的JayKreps对此很不满意,于是提出了“Kappa架构”,作为Lambda方案的简化版,删除了批处理系统的逻辑,认为数据只需要流式处理就可以。这个方案的设计其实有一些暴力,让我们看下主体思路是怎样的:以Kafka作为数据的存储系统;当需要计算增量数据时,只需要订阅相应的broker,读取增量即可;当需要计算全量数据时,启动一个流式计算的实例,从头进行计算;当新的实例完成后,停止旧的实例,并删除旧的结果。架构上应该是这样样子:逻辑上是这个样子:有一句话很适合Kappa架构,即“如无必要,勿增实体”,也就是只有在有必要的时候才会对历史数据进行重复计算。由于实时计算跟离线计算是同一套代码,因此规避了两套逻辑带来的结果不一致问题。但相应的,Kappa的性能其实就成为了一个问题,需要对实例的数量进行控制。感兴趣的,在这里进行扩展阅读: