【架构】Lambda架构

2022-09-12 21:21:08 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

一、出现的背景

1.1 从传统数据库到NoSQL,再到Hadoop

很多人学习大数据都听说过以下发展进程,MySQL/Oracle/SQLServer → Hadoop/Hive/Spark。但还有一个时期,是大家容易忽略的——NoSQL。我们不能忽略掉它。

其实,NoSQL的发展和推广要比Hadoop更早,在没有Hadoop的大数据过渡期,随着数据量急剧膨胀,大家纷纷从传统的关系型数据库转变到NoSQL数据库,各种各样的NoSQL数据库应用而生。有了NoSQL数据库,可以轻易将机器的数量扩展到上千台。从RDB到NoSQL的转变,有一个重大的改变是数据模型的变化

也是受限于数据模型,NoSQL有很大的限制,如果要做复杂的数据分析、处理,很难实现。NoQQL能够将数据存下来、取出来,但OLAP很难在NoSQL开展,至今依然是这样的。所以不会有人用HBase、Cassandra、ES去做数据仓库。而是会用它们来做一些OLTP的工作。

而Hadoop的诞生,让对海量数据进行分析变得可行。也就是大数据的OLAP是从Hadoop开始的。至今,很多的计算引擎,依然采用的是Hadoop生态的模型,只不过使用的技术更优越了,速度也变快了。所以,有一种大数据的看法,会认为后续的很多产品,都是Hadoop的衍生物。一些人把这称为这是广义上的Hadoop。

大数据领域有两个先驱,一个是Google,一个是亚马逊。Google开发了分布式文件系统、MapReduce。亚马逊开发了Dynamo分布式key-value存储系统。然后下来就是开源社区,包括:Hadoop、HBase、MongoDB、Cassandra、RabbitMQ以及其他项目。

1.2 传统数据库技术问题

数据越来越多,关系型数据库在「大数据」的压力下崩溃了。而除了传统数据库,还有其他的一些技术,例如:消息队列、数据库分片等。我们来看看,即使用了这些补救策略,依然解决不了根本问题。而对传统关系型数据库的扩展是大数据的起点

为了说明问题,给大家举个例子:使用关系型数据库,统计某个网站的点击量。

1.2.1 数据库负载过高

  • 用户访问一次,就在数据库中保存一条数据。
  • 对于较小的用户规模来说,这种方式并没有什么问题。但用户量达到一定规模,会面临重大的挑战。
  • 用户量大了后,会频繁出现数据插入超时错误。数据库性能跟不上较大的负载,所以导致数据库写入超时。
  • 数据库根本无法扛住如此大的并发请求。

1.2.2 消息队列

  • 因为请求数量比较多,每次将用户访问数据写入库中,就需要建立与数据库的连接。
  • 而单机的数据库能够支持的并发连接数一定是有限的,能够支持的最大负载也是有限的。
  • 为了减少数据库的负载,我们可以尝试将数据以一批一批地方式来写入。
  • 所以,可以考虑用队列来解决问题,先将数据写入到队列中,然后一批一批的写入到数据库中。
  • 这样可以有效避免超时问题,并且可以避免数据丢失。

1.2.3 数据库分片

  • 消息队列有效地缓解了数据库的压力。但当用户量激增时,即使以批方式写入数据库,数据库的负载依然会很高。当刷入大量数据时,数据库的容量开始难堪重任。而且,单机的IO能力也是有限的。
  • 这也说明了,不管是高并发、还是海量数据,重大的瓶颈就是数据库。
  • 相信大家也知道,我们可以通过数据库的分表、分片技术来提升数据库的性能。可以让数据分布在不同的节点中,从而组成一个数据库集群。这也是当今业务系统开发的重要技术。

看起来很美好,但数据库分片无法解决所有问题。如果可以的,就不会有后续的NoSQL、以及Hadoop技术的发展。

  • 分片技术就是按照key的hash来选择分片,将数据均匀地分布在不同的分片中。
  • 但是,因为之前的服务器负载已经满了,所以需要重新分片,将所有的数据拆分为多个分片。
  • 这个过程需要一段时间,所以在分片的时候,需要暂停之前的插入数据库中的程序,确保分片顺利完成。否则在分片过程中就会丢失新增的数据。

所以,每当数据量一达到阈值,就需要重新分片,这就非常难受了。

1.2.4 容错

  • 数据量越来越大,数据库分片也越来越多,机器也越来越多。规模越大,出现故障的概率也会随之增大。
  • 如果一台服务器停机,该主机上的数据将不可用。而数据不可用时,就需要将这部分的数据放在一个特殊的队列中(例如:Dead Letter Queue),每隔一段时间刷新一次数据。
  • 或者给数据库做备份,每个分片都做一个从,某个机器出现故障时,还有其他的服务器可以继续使用。
  • 这会导致,大量的时间都是在处理数据的容错问题。运维团队不堪重负。

1.2.5 人为因素导致数据错误

  • 如果因为人为的原因,在生产上部署了一个错误的程序。这个错误会导致计算数据不对。
  • 但隔了一段时间,才发现这个问题。而发现问题时,数据已经是错的了。
  • 这时,我们需要采取一些补救措施。但想要发现哪些数据是错的很难。
  • 相信大家都不会觉得,这怎么可能,怎么会往生产中部署一些有错误的程序呢?但现实就是这样。我们在当前环境下,无法开发出一个100%尽善尽美的程序。不管我们怎么小心,错误还是会出现在生产环境中。
  • 系统越来越复杂,出错的概率也越来越大。

软件开发中出错是不可避免的,而大数据技术就是要来解决上述的这些问题。分片和副本带来了可扩展性和容错性,数据的不可变特性也很大程度避免了人为原因把好的数据搞乱。

1.3 没有万能的组件

  • 过去15年,诞生了很多可扩展的数据系统。例如:文件系统有HDFS(2006)、数据库有Cassandra(2008)等。这些系统可以用来处理大量的数据。
  • 例如:HDFS可以对大规模的数据进行批量计算,但批计算延迟很高。所以,如果要做一些低延迟的数据操作,是无法考虑直接使用HDFS的。
  • 而NoSQL(就像Cassandra)提供了更简单的数据模型(key、value模型)以实现高度可扩展。但要把复杂的关系型结构扩展到这种简单模型,非常复杂。而且NoSQL数据也是可变的,也更容易出现一些人为的错误。

所以,这些组件没有哪个组件是万能的。但如果搭配它们一起来使用,就可以取得不错的效果。绝大多数的大数据系统都是由多个组件组成。而Lambda架构就是将若干组件组合在一起。

二、Lambda架构要做到什么

2.1Lambda架构创始人

提到Lambda架构,就不得不提Nathan Marz(后面我们称他为Marz)。嗯,下面是它的Twitter截图。

他是Red Plant LABS的创始人、Apache Storm项目的创始人。Marz在Backtype(后来Twitter把Backtype收购了)开发了Apache Storm。他也是「Big Data: Principles and best practices of scalable realtime data systems」这本书的作者。这本书没有划水,基本上都是干货。大家也可以看到他是Manning出版社的图书。这本书主要也是围绕Lambda架构来讲解了,建议大家去看英文版本。

Marz也是Lambda Architecture的创始人。他在2011年发布了一篇「如何打破CAP定理」热度非常高。大家有兴趣也可以去看看,下面是链接:http://nathanmarz.com/blog/how-to-beat-the-cap-theorem.html。

2.2 Lambda架构应具备的特征

2.2.1 健壮和容错

分布式数据库要保证数据的一致性、避免重复数据、并发等。同时,还要保证数据总是正确的,这会给系统设计带来较大挑战。我们应该尽量避免这种复杂。同时要具备人为容错能力。也就是说需要避免因为人为的失误,而导致数据丢失、数据错误。需要为系统提供重跑数据的能力,能够在人为导致数据错误情况,重新获得正确的数据。同时,保证数据的不可变性,外部程序无法修改数据。

2.2.2 低延迟读和更新

系统能够不影响系统健壮性的同时实现低延迟读取和更新。大家看这些年的技术发展,相信有很多人都想将低延迟读取和更新融合到HDFS中。而其实十几年前就有这样的实践,例如:HBase。虽然,HBase是属于Hadoop生态,但就数据模型、存储引擎来说,HBase做了非常大的调整,只不过在底层,数据仍然是保存在HDFS中,其他的基本上跟HDFS差距非常大。所以,HBase也划入到了NoSQL的阵营。

要让数据系统能够支持各种各样的应用场景,我们是否能够依赖一个组件就能完成所有呢?如果真的可以,那Lambda架构让组件相互协作的方式将会被打破。但目前,其实我们并没有看到在大数据领域中有这样的一款万精油组件。其实,在OLTP系统中,也没有这样的组件。就像我们会用消息队列来进行流量削峰,我们会使用高速缓存在减轻数据库的负载,我们会使用全文检索引擎来加速模糊、关联查询等等。且看IT这么多年的发展,我根本不相信会有这样的组件出现。

说到这里,需要来聊一个比较另类的存储系统——Kudu。虽然Kudu也是一个LSM结构的存储引擎,但它的数据模型看起来像极了关系型模型。但如果用过Kudu就会知道,Kudu本身能够支持的操作非常少,只是它有表的概念、有主键、列的概念。但如果要进行一些类似于关系型的复杂操作,我们得借助于Impala或者是Spark这样的引擎。

Kudu官方也介绍,它是一款介于HDFS、NoSQL之间的存储系统。Kudu也确实实现了能够以低延迟写入、更新以及主键查询。而在OLAP上。但如果用Kudu存储海量的数据、以及进行复杂一点的分析,我们会发现,在OLAP上Kudu还是弱了很多,因为它的结构非常复杂,要兼顾事务、并发、低延迟读写、数据分析,所以,它确实也是一个中间型的产品。维护的成本比较高,资源消耗也会比较大。大规模的数据探索,Kudu有非常大的局限性。而且写放大问题很难解决。这些问题,当在海量数据面前,全部都会暴露出来。

2.2.3 可扩展性

在数据负载增加时,可以通过向系统增加资源来保证性能。可扩展性几乎是数据系统中的必备选项。当然,我们不能排除所有的公司都是这样的。例如:一个公司业务急剧下滑,数据突然急剧减少。这种情况是存在的,但我坚信,如果一个公司出现这样的情况,公司会想办法来降低数据系统的开发、维护成本。而将更多地资源投入到业务中,技术人员的价值也会逐渐衰落。我相信,没有哪个架构师愿意看到这样的局面,更没有哪个架构师会基于这样的场景来去设计系统。

Lambda架构能够保障每个层都可以进行水平扩展,也就是添加更多的机器来实现扩展。水平扩展,是系统扩展的常见方式。而我们基于大数据组件开发的应用,基本上也都是能够水平扩展的。没有人会将一个用于调试的单机系统部署到生产环境中。

当添加新功能或者对系统功能进行调整时,可以对系统进行扩展,并且能够以最小的成本来开发。例如:能够快速地进行数据迁移、格式转换等。统一是目前数据系统建设的重要原则。为减小开发成本,我们在设计之初尽可能地减少架构的复杂度、减少引入不同的存储体系、和计算体系。每当我们添加一个组件、每当我们添加一个模块,都会提升开发成本,开发成本提升了,维护成本也上来了。

2.2.4 即席查询

要为挖掘数据提供即席查询的能力。即席查询对开发人员、分析人员或者是维护人员都很重要。不要指望在企业中能够让ETL开发工程师提供所有的数据、和计算结果。一个尊重数据的公司,一个谋求发展的公司,不会指望ETL开发工程师能够带来太多的业务创新,以及去发现、解决业务问题。我们需要给这些用户去探索数据的空间。那么AdHoc是必不可少的。

AdHoc的引擎有Presto、Impala等。但AdHoc也不一定就是MPP引擎的独家授权。为了能够更好的管控资源,稳定的性能。Spark、Flink也可以提供有一定延迟的AdHoc。这没有什么问题。毕竟,如果定位用户为分析人员、开发人员、或者维护人员,大多数的场景也都不是毫秒级、秒级的。

2.2.5 最小的维护成本

选择一个功能实现比较简单,复杂度不高的组件。系统越复杂,出现问题的概率也就越大。维护成本我们需要考虑下公司的运维人员,当我们研发完毕后,要将数据系统的维护移交给运维人员。让他们来完成一些操作。不要指望运维人员的技术能力有多强,我们设计的数据系统就应该能够降低运维门槛。维护成本越低,系统出现故障的概率、人为的错误操作带来的风险,也都会下降。

而Lambda架构可以将复杂性从核心组件推到系统的各个部分。

2.2.6 可调试性

当出现问题时,能够提供更多的调试信息。能够跟踪系统中的每个值,确切地知道是什么导致问题。可调试性也是需要Lambda架构中保证的,而且能够使用数据重跑的方式重新计算数据。

可调试性在系统的设计中非常关键,但系统出现一些问题时,我们需要重新追数、重新跑数。或者我们想要查找到是在什么时间、什么数据、什么操作下出现的问题。并且能够追溯到每个细节。这就要求在我们设计应用程序(不限于ETL、实时应用等)时,我们需要考虑可调试。我们需要给系统做较多的配套工具,通过工具能够快速发现问题、解决问题。

三、完全增量架构根本不可行

下面这张图,是一个最简单、也是最高层次的架构抽象:应用基于数据库不断地进行读写操作。

不管是什么系统,应用都是在增量地维护数据库状态。我们日常开发的系统都是基于完全增量来开发的,与具体的实现技术无关,也不管数据模型如何,不管是关系型数据库,或者是非关系型数据库。几十年来,一直都是这样的模式。只不过,大部分人没有感知。还有一些童鞋,一直都觉得自己是在实现增删改查、抽数取数对数中,没有关注到这些。

3.1 高成本的Online Compaction

举个例子,就是写放大问题。例如,缓存存满需要溢写到磁盘,而当溢写到磁盘中的文件达到一定数量时,需要合并这些文件,不然如果要检索这些文件,需要开启过多文件句柄,耗费磁盘IO。与此同时,还需要重建索引,我们无法将继续将数据写入到之前的文件中,直到Compaction完成。Compaction过程会增加磁盘IO、CPU负载。同时,一般为了节省出来一些空间,还需要进行压缩。常见的一般有Snappy、Gzip这样的压缩方式。Compaction过程,因为需要拷贝数据再进行合并,所以也要求磁盘有更多的空间。

如果是数据存储框架当然不想让写放大影响数据系统的可用性。所以,它们肯定会想办法将负载均摊到集群中,也是想尽量降低对节点数据操作的影响。这种操作是有运维工程师来完成的。我之前也听过一个上市公司在介绍他们的HBase一系列的运维活动,相当复杂。

Online Compaction是完全增量架构所固有的。而最近的,Hudi、IceBerg、DeltaLake这类组件。他们将Online Compaction放到了计算引擎中(Spark、Flink)。

3.2 实现最终一致性很复杂

CAP定理,在存在网络分区情况下,根本不可能同时实现高可用性和一致性。

  • 这个网络分区(network partition)可以直接理解为一个IDC中不同的交换机、不同机架,它们处于不同的网络区域。如果我们能够确保数据系统是一直可用的,那么不可能保证数据是一致的。如果要保证数据总是一致的,那么总会导致某一时刻,数据系统不是高可用的。
  • 要保证数据系统始终是高可用的而不是完全一致,处理起来往往非常复杂。而如果是保障最终一致性,可能会出现数据丢失。
  • 丢失的原因这样来理解,为了保障数据的高可用,在存在分区的时候,一个分区出现故障,还有另一个分区可用。因为实现的是最终一致性,所以,挂掉的节点存在的数据,可能还没来得及刷到分区副本。而实现最终一致性其实并不简单,例如:要实现一个累加操作,分区1保存的是11,分区2保存的是10,分区1脱机后,切换到分区2,而当需要将结果合并时,如何合并呢?这时要修复数据非常复杂。所以,要实现完整的合并,仅存储计数结构是有问题的。

所以,在增量、高可用系统要实现最终一致性是很容易出错的。

3.3 人为错误低容忍度

完全增量系统一旦出现人为失误操作导致的问题容忍度是很低的。既然增量系统,所以数据是不断变化的,所以数据库的状态需要不断保存。如果期间出现了错误,状态就会被改变成为错误的状态。从而导致,后续的所有数据都是错误的。毕竟,增量嘛!Lambda架构在数据准确性、延迟以及吞吐量上表现明显要比完全增量架构要好得多。

四、Lambda架构

在大数据技术领域中,没有单一工具能够解决所有的数据问题。我们必须要使用一系列技术来构建大数据系统。Lambda架构的主要思想是将大数据系统分层。而每一层都能实现一定的功能。Lambda一共分为三层:

  • Speed Layer(加速层)
  • Serving Layer(服务层)
  • Batch Layer(批处理层)

Marz提出来一系列的公式来演绎Lambda架构。Marz用一个公式来概括大数据系统:Query = function(all data)。但实现这个操作特别难,因为数据量达到PB级,每次查询需要处理如此大量的数据,几乎是难以完成的。而目前数仓跑批是如何实现的呢?答案是:预计算。提前将一些所需的查询计算好,然后快速查询结果。基于此,公式转变为:

  • Query = function(batch view)
  • Batch View = function(all data)

这种查询方式比较典型的就是T 1的离线数仓查询方式。但它也存在问题:

  • 所有预计算都是由ETL、和BI开发提前计算好的。如果我们需要探索数据,这种方式就没法解决了。
  • 因为跑批时,需要计算大量数据,无法在短时间内完成查询。

4.1 Batch层

  • Batch Layer可以理解为离线数仓,是最容易理解、以及最熟悉的。它旨在存在不可变的、不断增长的主数据集。然后在主数据集上计算大量数据。常见的技术就是Hadoop技术了。Batch层会不停地跑批计算,常见的是以一天来单位,定期地执行。
  • 批处理层的开发相对来说要简单很多,开发起来就像写单机程序一样。
  • Batch层实现数据的精确计算。

4.2 Serving层

  • Serving层是用于提供数据服务的。我们需要给Batch层提供快速可查询能力。所以,Serving层旨在将批处理层的数据加载到一个分布式数据库中,而且Serving层要提供随机查询的能力。每当Batch层有新数据时,会自动将Batch数据传输到Serving层。
  • 说到这里,大家可能会想到。嗯,好像之前我们也做过类似的事情,我们会将Hive的数据导出到MySQL中,然后通过MySQL建立索引,来实现快速查询。但可以肯定的是,这种是小批量数据的玩法,大批量数据几乎是没有可行性的。有的公司,会通过MPP计算引擎,直接查询离线数仓的不可变数据。例如:使用Impala,或者是Presto等。
  • Serving层的特点是要求数据库是能够支持批量更新、以及随机读取的。Serving层是不被要求具备随机写的。随机写的代价是非常大的。所以,其实我们在Hive上层查询提供的方案,严格来说,它们也存在一些缺陷。因为:
    • MySQL在容错和扩展上根本不足以应对大规模数据,没有可行性。
    • MPP计算引擎随机读取性能会受很大影响。要想以很低的延迟随机读取效率很低。另外数仓跑出来的数据是无法被批量更新的。当然生产环境我们也不能接受跑批的数据被外部更新。

Batch层 Serving层基本上解决了所有问题,但我们之前提到了,它的延迟很高。因为Batch layer需要处理大规模数据,全部跑完有的需要若干个小时。

4.3 Speed层

Speed层要解决的问题就是数据延迟高的问题。延迟主要在哪儿呢,就是在跑批开始到跑批结束这期间的数据。Lambda架构提到的是,通过一个低延迟的实时系统来补足这个时间段内的数据。它的意义也很明确,就是要尽快地将新的数据对外供数。

加速层和批处理层很大程度上,处理的数据规模不同。加速层只能看到的是最近的数据。每当数据有更新的时候,它仅更新实时视图。不会像批处理一样重新计算。所以,加速层做的是增量计算。

Marz用公式通俗易懂地解释了加速层:

  • realtime view = function(realtime view, new data)

意思是,加速层是基于已经存在的实时视图,和新数据进行增量计算。

Lambda架构还有一个重要特点,每当批处理层的数据进入到服务层后,不再需要实时视图中的数据了,也就是可以丢弃掉。Lambda这三层中,加速层是最复杂的。Lambda架构也旨在隔离复杂性。一旦出现问题,还可以放弃复杂的加速层。可以在几个小时内数据恢复正常。因为有Batch层兜底,所以加速层的数据可以得到修复。毕竟,如果要求加速层的数据100%正确,其实并不是很容易。

五、总结

最后,引用以下Marz对Lambda架构的说明:

  • Query = function(Batch View, Realtime View)
  • Batch View = function(all data)
  • Realtime View = function(realtime view, new data)

加速层要使用支持随机读取和随机写入的数据库,所以,加速层所使用的存储要比在服务层使用的数据库复杂得多。

参考资料:公众号(Flink)-《说说大数据要知道的Lambda架构》

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/152813.html原文链接:https://javaforall.cn

0 人点赞