深入理解Lambda架构

荐语

本文要介绍的是 2015 年 O’Reilly 出版的书籍 Big Data: Principles and best practices of scalable realtime data systems,书中主要介绍一种常见的大数据架构 —— Lambda 架构。Lambda 架构通过批处理和流处理相结合的方法,有效处理了大数据场景下的工作负载。相比以关系型数据库为核心的传统数据系统,它在可扩展性、容错性等方面有了更进一步的提升。

虽然 Lambda 架构是十年前被提出的数据架构,但 Lambda 架构的以不可变数据作为核心、历史和实时数据分层处理的架构思想仍值得我们学习。

传统数据系统的局限

考虑有这么一个系统,根据用户的浏览记录,统计用户访问网页的次数:

在上述系统中,每次用户浏览一个 url,应用就会向数据库更新一行数据。随着用户量的不断上升,并发量急剧上涨,数据库最先达到瓶颈,频繁出现更新超时的现象。针对这种情况,我们可以引入消息队列,将单次写改成批量写,以此降低数据库的并发数

但这并不是长久之计,随着用户量的再次上涨,单实例的数据库还是会成为单点瓶颈。为此,可以对数据库进行水平扩展,分库分表,将原本集中在单个数据库实例上的请求,负载均衡到多个实例之上。

分库分表虽能消除数据库单点瓶颈,但也会带来其他的一些问题:

  • 应用逻辑更复杂。应用层需要根据数据的 key 找到其所在的数据库实例,以便进行数据更新。
  • 维护成本更高。为了避免因数据库实例宕机导致的服务不可用,需要通过增加数据副本、缓冲队列等手段提升可用性。另外,随着数据量的增大,可能因数据库实例间负载不均衡,而产生对数据进行重分区的操作。这些都会导致维护成本的急剧增加。
  • 容错性问题。一旦开发者在更新数据库时出现人为错误,比如在增加 pageviews 字段时多乘了 2,那么数据将难以恢复,因为很难溯源。

上述例子中,从简单的 CURD,到增加消息队列、分库分表、副本、重分区等,系统也越来越复杂。导致这一切的原因是,传统数据库系统并非为分布式而生,它无法很好地适用于大数据场景

大数据技术的局限

那么,大数据技术就能解决这些问题吗?答案是,

像 Hadoop、MapReduce、HBase 等这类大数据技术,虽然能够天然就处理海量数据,但它们都有各种局限,比如,MapReduce 是批处理系统,计算时延高;HBase 键值查询快,但无法满足复杂的 SQL 查询场景。

为了找到解决方法,我们还需从大数据系统的本质入手。

大数据系统的本质

作者对数据系统进行了如下定义:

A data system answers questions based on information that was acquired in the past up to the present.

也即,数据系统的本质是依据全量数据来回答用户提出的问题,从技术的角度讲,就是处理各类查询请求。因此,可以用一个公式来定义数据系统:

query = function(all data)

对于数据系统来说,在上述的定义之外,还应具备以下的属性:

  • 健壮性和容错性。在大型分布式系统中,网络故障、服务器宕机等是必然发生的,大数据系统一方面应能应对这些异常,另一方面还应能为用户屏蔽其中的复杂性,避免高昂的维护成本。除此之外,大数据系统还须能容忍人为错误
  • 低时延。很多应用都要求低时延,通常在毫秒级别。
  • 可扩展性。要能够支持通过扩展更多的机器来应对不断增加的数据量,保证查询性能不受影响。
  • 可调试性。问题出现时,系统要提供必要的信息来辅助问题定位,关键是能够跟踪系统每一行数据的变更详情
  • 支持即席查询。用户能够灵活选择自定义的查询条件进行查询。

从前文可知,以传统关系型数据库为核心的数据系统并不完全具备这些属性,为此,Lambda 架构诞生了。

Lambda 架构的解决思路

我们可将数据分成两类,历史数据和实时数据(最近几个小时),用户对两类数据在查询时延、精度等方面的要求各不相同。所以,我们完全可以采取不同的技术方案对它们分别处理。这就是 Lambda 架构的核心思想之一,分层

Lambda 架构分 3 个层次,每个层次职责分明、使用不同的技术方案,以此来满足大数据系统的各项诉求。

  • 批处理层(Batch Layer):负责离线处理历史数据,包含主数据集(Master Dataset)。所有的数据都会往主数据集写入。批处理层不会立即处理新数据,而是会在非业务繁忙时段,对全量数据进行批量处理,生成批视图(Batch View)。
  • 服务层(Serving Layer):负责存储批处理层生成的批视图,并对外提供批视图的查询接口。通常都会带有索引,以便用户能够快速查询数据。
  • 速度层(Speed Layer):负责实时处理新数据,生成实时视图(Realtime View),提供实时视图的查询接口,。

批处理层(Batch Layer)

不可变的数据

传统数据系统中,数据是可变的,比如前文所提到的网页访问例子中,pageviews 字段会随着网页访问行为而递增。当出现问题,特别是人为错误时,通常因难以溯源而无法纠错,导致容错性较低。

为此,Lambda 架构中批处理层采取的策略是不可变的数据,通过存储原始的数据实现:

上述例子中,采用可变数据的存储方式后,用户的每一次访问行为都对应一行数据,并通过时间戳来区分。pageviews 不再作为字段存储在数据库里,而是在数据处理时进行计算。

这种不可变的数据存储能够带来以下一些好处:

  • 更灵活的查询,可以对任何一个时间段的数据进行计算。
  • 更高的容错性。即使在人为错误发生后,我们仍能够基于原始数据进行重新计算,得到正确的结果。
  • 更简单的存储。存储只涉及追加新的数据,不涉及数据更改、索引更新等复杂操作。

不可变数据存储的缺点也很明显,相比可变存储,数据是持续追加的,因此需要占用更大的存储空间。当然,我们也可以制定一些数据过期删除策略,定时清理冗余、低价值的数据,降低存储成本。

数据存储

主数据集包含了所有数据,存储它的数据库应具备以下的能力:

  • 能够支持追加写数据。
  • 能够保证数据是不可变的。
  • 数据存储量要足够大。
  • 能够支持大批量地处理历史数据。

由此,我们很容易想到用分布式文件系统(Distributed File Systems,DFS)来存储主数据集:

  • DFS 实现追加写非常简单,只须将文件添加到主数据集所包含的文件夹中即可。
  • DFS 通过权限控制可以轻松确保数据不可变。
  • DFS 通常有很好的可扩展性,可以轻松增加机器来应对不断增长的数据量。
  • DFS 的优点之一是具备很高的读写吞吐量,比如 HDFS,结合 MapReduce 计算引擎,能够实现大批量的离线数据处理。

全量计算 VS 增量计算

批处理的数据计算方式可以分成全量计算(Recomputation algorithms)和增量计算(Incremental algorithms)。全量计算每次都会对全量数据进行重新计算,得到新的批视图;而增量计算只会对新的数据进行计算,并将计算出来的视图与已有视图进行合并,形成新的批视图。

相比全量计算,增量计算每次计算量更小、有更好的性能,但其代价是更低的容错性,因此,更推荐使用全量计算

另外,当算法依赖历史数据时,增量计算显然难以得到正确答案。考虑一个生日推断算法的例子,系统根据不同时刻抓取的用户信息 [userid, age, timestamp] 来推断用户的生日,比如在 2023/2/21 抓取某用户的年龄为 28,在 2024/3/4 再次抓取他的年龄为 29,那么该用户的生日就在 2023/2/21 和 2024/3/4 之间。显然,每次用于计算的信息越多,生日推断的结果就越准。

另外,我们也可以选择一个折中方案,部分重计算(Partial Recomputation algorithms),也即选取必要的历史数据进行重新计算,以生日推断算法为例,假设新数据中只包含用户 A 的数据,那么,我们可以从主数据集中选取用户 A 的历史数据与新数据合并,再进行计算,这样计算的数据量将会大大减少。

服务层(Serving Layer)

服务层属于批处理层的延伸,负责存储批视图,并对外提供查询接口。通常,服务层所使用的数据库须要具备如下属性:

  • 支持批量写。当新的批视图计算完成后,服务层需要支持批量写来完全替换掉旧视图。
  • 可扩展性好。随着主数据集的增长,批视图的存储空间可能也会随之增大,因此也要具备良好的可扩展性。
  • 支持随机读。服务层必须支持随机读,以及索引查询能力,这样才能做到快速的查询响应。
  • 容错性好。服务层通常也是分布式数据库,因此容错性是基础能力。

在服务层中,我们通常采用以空间换时间的策略,适当地增加数据冗余,以此换取更优的查询性能。

比如,假设用户的报表需要按月、按天、按小时、按分钟等维度的视图,最简单的方法是只保存一份分钟级视图,若用户需要查询小时级粒度的数据,临时汇聚。但因为这需要额外的聚合运算,无法满足低时延的诉求。更好的方法是,在服务层保存多种粒度的视图。

速度层(Speed Layer)

批处理层可以很好地处理离线历史数据,但很多场景下,数据是实时产生的,比如 IoT。此时,单靠批处理层显然无法无法满足实时数据的低时延处理。因此,可以把速度层看成是对批处理层在实时性上的补充,它负责实时处理源源不断的新数据,并生成实时视图(Realtime View)供用户查询。

与批处理层相比,速度层具备如下的特点:

  • 能够处理实时数据。与批处理层处理全量数据不同,速度层只处理实时数据。在技术上选型上,通常使用流式计算引擎,能够不断处理数据,并实时更新实时视图。
  • 采用增量计算。速度层的首要目的是为用户提供实时数据的低时延查询能力,作为代价,计算结果可能存在精度不足、甚至错误的问题,不过这些错误最终都会被批处理层纠正

速度层除了要进行实时数据的增量处理外,还需保存实时视图,提供类似服务层的能力,也即对外提供实时视图的查询接口。

更好的 Lambda 架构实现

书中所介绍的 Lambda 架构中,服务层只是作为批处理层的延伸,存储批视图。但在介绍速度层时我们提到,速度层也要对外提供实时视图的查询接口。因此,更好的实现方式是,把批视图和实时视图都存储在服务层,让服务层成为唯一对外提供接口的地方

最后

Big Data 书中除了介绍 Lambda 架构的原理,还对如何利用 Hadoop、Cassandra 等大数据技术构建 Lambda 架构进行了详细的说明,并且给出了关键源码,可以通过阅读原书了解更多,本文不再详细介绍。

Lambda 架构将数据处理拆分成批处理层和速度层,前者采用全量计算,保证计算结果的正确性,从而让系统具备了很好的容错性;后者采用增量计算,保证了系统具备了低时延的实时数据查询能力,而数据正确性问题又能在批处理层得到纠正。两者最终的计算结果通过服务层对外提供查询接口,三个层次之间相辅相成。

如果用公式来定义 Lambda 架构,会是这样的:

query = function(Batch View, Realtime View)

Batch View = function(all data)

Realtime View = function(Realtime View, new data)

在技术选型上,批处理层的存储侧通常是分布式文件系统,比如 HDFS 等,而计算侧通常是离线计算引擎,比如 MapReduce、Spark、Hive 等;速度层通常是流式计算引擎,比如 Flink、Spark Stream 等;服务层通常是 NoSQL 数据库,比如 HBase、Cassandra 等。

Lambda 架构从诞生至今,历经了十多年的发展,它的一些缺点也了暴露出来:

  • 实时与批处理结果不一致,容易引起的数据口径问题。速度层和批处理层走了两套不同的计算逻辑,可能存在这样的情况,用户查询某个数据,昨天查询的结果(实时视图)与今天查询的结果(批视图)不一致,让人疑惑。
  • 批处理在计算窗口内无法完成。批处理通常发生在晚上业务空闲时段,然而随着数据量的增大,必然会出现在原有的计算窗口内无法完成全量计算,从而影响用户第二天的数据报表生成。
  • 开发和维护成本大。批处理层和速度层采用两套不同的技术方案,开发和维护成本都比较大。
  • 存储成本大。Lambda 架构为实现不可变的数据需要存储原始数据,数据的不断追加会带来很大的的存储成本。

针对 Lambda 架构的这些局限,Kappa 架构等大数据架构也应运而生。但 Lambda 架构因其优秀的稳定性和容错性,仍被应用在众多场景中。正如 软件架构, 一切尽在权衡 里提到的,这世上没有十全十美的架构,最重要的是根据实际业务情况,选择合适的架构

文章配图

可以在 用Keynote画出手绘风格的配图 中找到文章的绘图方法。

参考

[1] Big Data: Principles and best practices of scalable realtime data systems, Nathan Marz

[2] 一文读懂Kappa和Lambda架构, 王建峰

[3] 深入理解大数据架构之——Lambda架构, 博客园

更多文章请关注微信公众号:元闰子的邀请

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值