从现在起,我们需要重新审视下自己是怎样开发程序,以及是怎样理解所开发的程序的。
或许在实际的开发工作中,我们会出现以下一些日常情况:当我们开发Web后端时,认为无非就是增删查改,于是拿起Spring Boot就做了;
当我们开发流计算应用时,认为无非就是消息过来就处理,于是拿起Storm就干了;当我们开发批处理任务时,认为无非就是将数据读出来后进行计算,然后输出结果就好了。是的,针对每一种任务,我们都知道怎么去完成这个任务,然后针对具体任务解决具体问题。
然后有一天,产品经理跑过来跟你说,功能需要增加;运维人员跑过来跟你说数据库要调整;刚来的新人需要你来指导,你需要跟他讲解系统的整体结构。这个时候你会发现,原来设计的系统已经根本理不清或者动不了了:到处是耦合的增删查改逻辑,到处是相互依赖的输入/输出,到处是乱七八糟的数据格式。想在原来的业务流程中插入一个业务模块,一定要对系统“大动干戈”;想要调整数据库,一定要修改程序代码;想要指导新人,一定要告诉他数据在哪里修改。
突然,你觉得好烦。似乎一切都开始变得失控,每动一处都是“伤筋动骨”。造成这些问题的祸根其实从一开始就埋下了。因为在设计系统的时候,你就没有一个整体的指导性原则。当我们开发流计算系统时,亦是如此。流计算系统的本质是对数据的实时处理和分析,所以我们首先应该理解数据系统的本质是什么。
数据系统用于根据过去所获取和累积的知识,回答当前提出的问题。这里面有两层信息。其一是积累知识。当有新的消息流入系统时,我们需要将它记录下来,这些消息会成为我们知识的一部分。其二是回答问题,我们在回答当前的问题时,依据历史积累的知识来回答这个问题。
数据系统一方面需要积累知识,另一方面可以回答问题。必须强调的是,积累知识和回答问题是两个独立的过程。积累知识可以是在任何时间、从任何地方收录数据,收录之后内部还有可能需要进一步归纳和整理。回答问题则是在任何时候,根据知识库所知道的一切来回答任何人提出的问题。
记住“积累知识”和“回答问题”这两个过程是独立的非常重要。为这告诉我们,在设计有关数据系统的方案时,千万不要将“查询”和“更新”这两个过程耦合起来,否则知识积累的过程和回答问题的过程紧密关联,会让我们的系统在将来可能的需求变更或功能增强时变动起来非常困难。
Lambda架构就是这样一个先积累知识后回答问题的数据系统。
Lambda架构将数据系统抽象为一个作用在数据全集上的函数。用公式表示就是:
query = function(all data)
这个公式还只能算是一个粗略概括,不能体现Lambda架构的核心观点,因为基本上所有的数据系统都可以用这个公式大体表示。Lambda架构与众不同的地方是,它专门为解决大数据量场景下实时查询的问题而生,它将数据系统更精细地刻画为
query = function(all data) = function(batch data) + function(streaming data)
从上面的公式可以看出,当数据量太大而不能实时全量计算时,Lambda架构将数据处理过程分成两部分。一部分是基于批处理的预计算,另一部分是基于流处理的实时增量计算。将这两部分计算结合起来,最终得到计算结果。
这里需要说明一下Lambda架构之所以取名为Lambda的原因,这有助于我们理解Lambda的思想。Lambda架构将数据系统视为在不可变数据集上的纯函数计算,这与函数式编程的核心思想是不谋而合的。我们经常听说的Lambda表达式正是函数式编程的具体表现形式,如在Java、Python等编程语言就有Lambda表达式的存在。对函数式编程和Lambda表达式感兴趣的读者可自行查阅相关资料。
Lambda架构最初由Storm流计算框架的作者Nathan Marz为构建大数据场景下低延时计算和查询的通用架构模式而提出。如图7-1所示,Lambda架构总体上分为3层:批处理层(batch layer)、快速处理层(speed layer)和服务层(serving layer)。其中,批处理层和快速处理层分别处理历史全量数据和新输入系统的增量数据,而服务层用于将批处理层和快速处理层的结果合并起来,以提供最终用户或应用程序的查询服务。
在Lambda架构中,各层的具体功能如下。
1.批处理层
批处理层用于存储主数据集和预计算各种批处理视图。当数据进入批处理层时,数据被存储下来,并作为数据系统的主要数据集。由于全量的数据很大,计算比较耗时,所以批处理层的主要作用是对预定的查询进行预计算,并将计算结果保存下来。如果做得更精细些,批处理层可以基于计算结果生成各种视图,并构建相应的索引,以供后续快速检索和查询。
2.快速处理层
在最标准的Lambda架构中,快速处理层的作用是实时计算在批处理层两次调度执行期间新到的增量数据,并将计算结果保存下来。在这种标准架构下,理论上快速处理层的输出结果与批处理层的输出结果在业务意义上应该是完全相同的。换言之,如果我们分别用两张数据库的表来存储批处理层和快速处理层的计算结果,那么这两张数据库表的表结构应该是相同的。只是因为分析的时间段不同,这两张表的数据记录不一样而已。但Lambda架构并非“定式”,在很多场景下,我们可以根据自己的需求对快速处理层做出改动。
例如,既然前一次的批处理层计算结果已经存储在数据库中了,那为什么快速处理层就不可以直接使用这次的批处理层计算结果呢?事实上我们经常这样做,例如,用批处理层学习统计模型或机器学习模型,将模型结果保存到数据库,然后快速处理层从数据库中定期更新模型,并根据模型做出实时预测。
3.服务层
服务层用于将批处理层和快速处理层各自计算所得结果合并起来,从而能够实时提供用户或应用程序在全量数据集上的查询结果。服务层对外提供的查询接口是只读的,这对实现高性能、无状态、高可靠的查询服务非常有用。所以,服务层在技术实现上结构相对简单,但它与具体的业务查询会结合得更加紧密。
Lambda架构是一种架构设计思想,针对每一层的技术组件选型并没有严格限定,所以我们可以根据实际情况选择相应的技术方案。批处理层的数据存储方案可以选择HDFS、S3等大数据存储方案。批处理层的任务执行框架则可以选择MapReduce、Hive、Spark等大数据计算框架。批处理层的计算结果(如数据库表或者视图)由于需要被服务层或快速处理层高速访问,所以可以存放在诸如MySQL、HBase等能够快速响应查询请求的数据库中。快速处理层是各种实时流计算框架的用武之地,如Flink、Spark Streaming和Storm等。快速处理层对性能的要求更加严苛,其计算结果可以写入像Redis这样具有超高性能表现的内存数据库中。当接收到查询请求时,服务层可以分别从存储批处理层和快速处理层计算结果的数据库中取出相应的计算结果并做出合并,作为最终的查询输出。
本章最初的出发点是讨论当我们在实时流计算系统中不能够直接实现某些实时计算目标时应该如何处理,我们提出的解决方案是Lambda架构。那么具体应该怎样将Lambda架构引入我们的实时流计算系统中呢?
在以实时流计算为主体计算流程的体系中,并非要由服务层来提供最后全量数据的查询输出,而是由分散在流计算框架各个节点处的计算逻辑单元直接使用批处理层的计算结果,如图7-2所示。
以风控系统中的特征提取和风险评分为例。特征提取系统需要并发提取数十个特征,在这些特征中,有些特征的计算耗时很短,可以立刻实时计算出结果,如计数、求和等;而另外一些特征的计算非常耗时,不能够实时得到结果,如二度关系图的计算。所以,按照Lambda架构将计算的实时部分和离线部分分离的思想,我们把不能够实时计算的特征分离成实时计算和离线计算两部分,其中,实时计算是在离线计算结果的基础上进行的增量计算。与特征提取系统一样,风险评分系统也会有类似的问题,模型参数需要根据在线数据每天进行一次更新,这时也需要将模型和评分过程分离为离线计算和实时计算两部分。其中,离线计算用于训练更新模型参数,实时计算用于进行在线风险评分。