The Log: What every software engineer should know about real-time data's unifying abstraction 中文翻译
文章简介:中文翻译 The Log: What every software engineer should know about real-time data’s unifying abstraction
正文
六年前我在一个特别有趣的时间加入了 LinkedIn。 我们刚刚开始遇到单体集中式数据库的极限,需要开始向分布式系统过渡。 这是一个有趣的经历:我们构建,部署并运行分布式图形数据库,分布式搜索后端,Hadoop 安装以及第一代和第二代键值存储一直到今天。
我在这一切中学到的最有用的东西之一就是我们正在构建的许多东西都有一个非常简单的概念:日志。 有时称为预写日志或提交日志或事务日志,日志几乎与计算机一样长,并且是许多分布式数据系统和实时应用程序体系结构的核心。
在不了解日志的情况下,您无法完全理解数据库,NoSQL 存储,键值存储,复制,paxos,hadoop,版本控制或几乎任何软件系统; 然而,大多数软件工程师并不熟悉它们。 我想改变这一点。 在这篇文章中,我将向您介绍有关日志的所有信息,包括日志以及如何使用日志进行数据集成,实时处理和系统构建。
Part One: What is Log
A log is perhaps the simplest possible storage abstraction. It is an append-only, totally-ordered sequence of records ordered by time.
记录附加到日志的末尾,读取从左到右进行。 每个条目都分配有唯一的顺序日志条目号。
记录的顺序定义了“时间”的概念,因为左边的条目被定义为比右边的条目更旧。 日志条目号可以被认为是条目的“时间戳”。 将这种排序描述为时间概念起初看起来有点奇怪,但它具有与任何特定物理时钟分离的便利特性。 当我们进入分布式系统时,此属性将变得至关重要。
出于本讨论的目的,记录的内容和格式并不重要。 此外,我们不能只是继续向日志添加记录,因为我们最终将耗尽空间。 我会稍微回过头来看看。
因此,日志与文件或表格完全不同。 文件是一个字节数组,一个表是一个记录数组,一个日志实际上只是一种表或文件,其中记录按时间排序。
在这一点上,您可能想知道为什么值得谈论这么简单的事情? 如何以仅附加的记录序列与数据系统相关联? 答案是日志有一个特定的目的:它们记录发生的事情和时间。 对于分布式数据系统,这在许多方面都是问题的核心。
但在我们走得太远之前,让我澄清一些有点令人困惑的事情。 每个程序员都熟悉另一个日志记录定义 - 应用程序可能使用 syslog 或 log4j 写入本地文件的非结构化错误消息或跟踪信息。 为清楚起见,我将其称为“应用程序日志记录”。 应用程序日志是我描述的日志概念的退化形式。 最大的区别是文本日志主要是供人阅读,而我所描述的“日志”或“数据日志”是为编程访问而构建的。
(实际上,如果你对它进行深入的思考,那么人们读取某个机器上的日志这种理念有些不顺应时代。当涉及到许多服务和服务器的时候,这种方法很快就变成一个难于管理的方式,而且为了认识多个机器的行为,日志的目标很快就变成查询和图形化这些行为的输入了-对多个机器的某些行为而言,文件里的英文形式的文本同这儿所描述的这种结构化的日志相比几乎就不适合了。)
Logs in databases
我不知道日志概念的起源 - 可能是二元搜索这样的事情之一,对于发明者来说太简单了,不能发现它是一项发明。 它早在 IBM 的 System R 就已存在。数据库中的使用与在出现崩溃时保持同步的各种数据结构和索引有关。 为了使这种原子性和持久性,数据库使用日志来写出有关他们将要修改的记录的信息,然后将更改应用于它维护的所有各种数据结构。 日志是发生的事件的记录,每个表或索引是将该历史记录投影到一些有用的数据结构或索引中。 由于日志会立即保留,因此在崩溃时将其用作恢复所有其他持久性结构的权威来源。
随着时间的推移,日志的使用从 ACID 的实现细节发展到在数据库之间复制数据的方法。 事实证明,数据库上发生的更改顺序正是保持远程副本数据库同步所需的。 Oracle,MySQL 和 PostgreSQL 包括日志传送协议,用于将部分日志传输到充当从属服务器的副本数据库。 Oracle 已将日志产品化为非 oracle 数据订阅者的通用数据订阅机制,其XStream和GoldenGate以及 MySQL 和 PostgreSQL 中的类似工具是许多数据架构的关键组件。
由于这个起源,机器可读日志的概念主要局限于数据库内部。 使用日志作为数据订阅的机制似乎几乎是偶然出现的。 但这种抽象是支持各种消息传递,数据流和实时数据处理的理想选择。
Logs in distributed systems
日志解决的两个问题 - 排序更改(ordering changes)和分发数据(distributing data) - 在分布式数据系统中更为重要。 同意订购更新(或同意不同意和应对副作用)是这些系统的核心设计问题。
分布式系统的以日志为中心的方法源于一个简单的讨论,我称之为状态机复制原则:
If two identical, deterministic processes begin in the same state and get the same inputs in the same order, they will produce the same output and end in the same state.
如果两个相同的确定性过程以相同的状态开始并以相同的顺序获得相同的输入,则它们将产生相同的输出并以相同的状态结束。
这可能看起来有点难懂的(obtuse),所以让我们深入了解它的含义。
Deterministic(确定性)意味着处理不依赖于时间,并且不允许任何其他“外部的”输入影响其结果。 例如,一个程序的输出受到线程执行的特定顺序的影响,或者通过调用 gettimeofday
或其他一些不可重复的东西,通常被认为是非确定性的。
进程的 state
状态是处理结束时机器上的任何数据,无论是在内存中还是在磁盘上。
以相同的顺序获得相同输入的地方应当引起注意-这就是引入日志的地方。这儿有一个重要的常识:如果给两段确定性代码相同的日志输入,那么它们就会生成相同的输出。
分布式计算这方面的应用就格外明显。你可以把用多台机器一起执行同一件事情的问题缩减为实现分布式一致性日志为这些进程输入的问题。这日志的目的是把所有非确定性的东西排除在输入流之外,来确保每个复制进程能够同步地处理输入。
当你理解了这个以后,状态机复制原理就不再复杂或者说不再深奥了:这或多或少的意味着"deterministic processing is deterministic"
(确定性的处理过程就是确定性的)。不管怎样,我都认为它是分布式系统设计里较常用的工具之一。
这种方式的一个美妙之处就在于索引日志的时间戳就像时钟状态的一个副本——你可以用一个单独的数字描述每一个副本,这就是经过处理的日志的时间戳。时间戳与日志一一对应着整个副本的状态。
由于写进日志的内容的不同,也就有许多在系统中应用这个原则的不同方式。举个例子,我们记录一个服务的请求,或者服务从请求到响应的状态变化,或者它执行命令的转换。理论上来说,我们甚至可以为每一个副本记录一系列要执行的机器指令或者调用的方法名和参数。只要两个进程用相同的方式处理这些输入,这些进程就会保持副本的一致性。
一千个人眼中有一千种日志的用法。数据库工作者通常区分物理日志和逻辑日志。物理日志就是记录每一行被改变的内容。逻辑日志记录的不是改变的行而是那些引起行的内容被改变的 SQL 语句(insert,update 和 delete 语句)。
分布式系统通常可以宽泛分为两种方法来处理数据和完成响应。“状态机器模型”通常引用一个主动-主动的模型——也就是我们为之记录请求和响应的对象。对此进行一个细微的更改,称之为“主备模型”,就是选出一个副本做为 leader,并允许它按照请求到达的时间来进行处理并从处理过程中输出记录其状态改变的日志。其他的副本按照 leader 状态改变的顺序而应用那些改变,这样他们之间达到同步,并能够在 leader 失败的时候接替 leader 的工作。
为了理解两种方式的不同,我们来看一个不太严谨的例子。假定有一个算法服务的副本,保持一个独立的数字作为它的状态(初始值为 0),并对这个值进行加法和乘法运算。主动-主动方式应该会输出所进行的变换,比如“+1”,“*2”等。每一个副本都会应用这些变换,从而得到同样的解集。主动-被动方式将会有一个独立的主体执行这些变换并输出结果日志,比如“1”,“3”,“6”等。这个例子也清楚的展示了为什么说顺序是保证各副本间一致性的关键:一次加法和乘法的顺序的改变将会导致不同的结果。
分布式日志可以理解为一致性问题模型的数据结构。因为日志代表了后续追加值的一系列决策。你需要重新审视 Paxos 算法簇,尽管日志模块是他们最常见的应用。 在 Paxos 算法中,它通常通过使用称之为多 paxos 的协议,这种协议将日志建模为一系列的问题,在日志中每个问题都有对应的部分。在ZAB, Raft等其它的协议中,日志的作用尤为突出,它直接对维护分布式的、一致性的日志的问题建模。
我怀疑的是,我们就历史发展的观点是有偏差的,可能是由于过去的几十年中,分布式计算的理论远超过了其实际应用。在现实中,共识的问题是有点太简单了。计算机系统很少需要决定单个值,他们几乎总是处理成序列的请求。这样的记录,而不是一个简单的单值寄存器,自然是更加抽象。
此外,专注于算法掩盖了 抽象系统需要的底层的日志。我怀疑,我们最终会把日志中更注重作为一个商品化的基石,不论其是否以同样的方式 实施的,我们经常谈论一个哈希表而不是纠结我们 得到是不是具体某个细节的哈希表,例如线性或者带有什么什么其它变体哈希表。日志将成为一种大众化的接口,为大多数算法和其实现提升提供最好的保证和最佳的性能。
Changelog 101: Tables and Events are Dual
让我们继续聊数据库。数据库中存在着大量变更日志和表之间的二相性。这些日志有点类似借贷清单和银行的流程,数据库表就是当前的盈余表。如果你有大量的变更日志,你就可以使用这些变更用以创建捕获当前状态的表。这张表将记录每个关键点(日志中一个特别的时间点)的状态信息。这就是为什么日志是非常基本的数据结构的意义所在:日志可用来创建基本表,也可以用来创建各类衍生表。同时意味着可以存储非关系型的对象。
这个流程也是可逆的:如果你正在对一张表进行更新,你可以记录这些变更,并把所有更新的日志发布到表的状态信息中。这些变更日志就是你所需要的支持准实时的克隆。基于此,你就可以清楚的理解表与事件的二相性: 表支持了静态数据而日志捕获变更。日志的魅力就在于它是变更的完整记录,它不仅仅捕获了表的最终版本的内容,它还记录了曾经存在过的其它版本的信息。日志实质上是表历史状态的一系列备份。
这可能会引起你对源代码的版本管理。源代码管理和数据库之间有密切关系。版本管理解决了一个大家非常熟悉的问题,那就是什么是分布式数据系统需要解决的— 时时刻刻在变化着的分布式管理。版本管理系统通常以补丁的发布为基础,这实际上可能是一个日志。您可以直接对当前 类似于表中的代码做出“快照”互动。你会注意到, 与其他分布式状态化系统类似,版本控制系统 当你更新时会复制日志,你希望的只是更新补丁并将它们应用到你的当前快照中。
最近,有些人从Datomic – 一家销售日志数据库的公司得到了一些想法。这个演讲回顾了他们是如何讲这个想法应用到他们的系统中的。这些想法使他们对如何 在他们的系统应用这些想法有了开阔的认识。 当然这些想法不是只针对这个系统,他们会成为 十多年分布式系统和数据库文献的一部分。
这可能似乎有点过于理想化。但是不要悲观!我们会很快把它实现。
What’s next
在这篇文章的其余部分,我将试图说明日志除了可用在分布式计算或者抽象分布式计算模型内部之外,还可用在哪些方面。其中包括:
- 数据集成(Data Integration)-让机构的全部存储和处理系统里的所有数据很容易地得到访问。
- 实时数据处理(Real-time data processing)-计算生成的数据流。
- 分布式系统设计(Distributed system design)-实际应用的系统是如何通过使用集中式日志来简化设计的。
所有这些用法都是通过把日志用做单独服务来实现的。
在上面任何一种用法里,日志的用途开始都是使用了日志所能提供的某个简单功能:生成永久的、可重现的历史记录。令人意外的是,问题的核心是可以让多少台机器以特定的方式,按照自身的速度重现历史记录的能力。
Part Two: Data Integration
请让我首先解释 一下“数据集成”是什么意思,还有为什么我觉得它很重要,之后我们再来看看它和日志有什么关系。
Data integration is making all the data an organization has available in all its services and systems.
数据集成就是将数据组织起来,使得在与其有关的服务和系统中可以访问它们
而更常见的术语 ETL 通常只是覆盖了数据集成的一个有限子集(译注:ETL,Extraction-Transformation-Loading 的缩写,即数据提取、转换和加载)——相对于关系型数据仓库。但我描述的东西很大程度上可以理解为,将 ETL 推广至实时系统和处理流程。
对数据的高效使用遵循一种 马斯洛的需要层次理论 。金字塔的基础部分包括捕获所有相关数据,能够将它们全部放到适当的处理环境(那个环境应该是一个奇妙的实时查询系统,或者仅仅是文本文件和 python 脚本)。这些数据需要以统一的方式建模,这样就可以方便读取和数据处理。如果这种以统一的方式捕获数据的基本需求得到满足,那么就可以在基础设施上以若干种方法处理这些数据——映射化简(MapReduce),实时查询系统,等等。
很明显,有一点值得注意:如果没有可靠的、完整的数据流,Hadoop 集群除了象昂贵的且难于安装的空间取暖器哪样外不会做更多事情了。一旦数据和处理可用,人们就会关心良好数据模型和一致地易于理解的语法哪些更细致的问题。最后,人们才会关注更加高级的处理-更好的可视化、报表以及处理和预测算法。 以我的经验,大多数机构在数据金字塔的底部存在巨大的漏洞-它们缺乏可靠的、完整的数据流-而是打算直接跳到高级数据模型技术上。这样做完全是反着来做的。
因此,问题是我们如何构建通过机构内所有数据系统的可靠的数据流。
Data Integration: Two complications
两种趋势使数据集成变得更困难。
第一个趋势是增长的事件数据(event data)。事件数据记录的是发生的事情,而不是存在的东西。在 web 系统中,这就意味着用户活动日志,还有为了可靠的操作以及监控数据中心的机器的目的,所需要记录的机器级别的事件和统计数字。人们倾向称它们为“日志数据”,因为它们经常被写到应用的日志中,但是这混淆了形式与功能。这种数据位于现代 web 的中心:归根结底,Google 的资产是由这样一些建立在点击和映像基础之上的相关管道所生成的——那也就是事件。
这些东西并不是仅限于网络公司,只是网络公司已经完全数字化,所以它们更容易用设备记录。财务数据一直是面向事件的。RFID(无线射频识别)将这种跟踪能力赋予物理对象。我认为这种趋势仍将继续,伴随着这个过程的是传统商务活动的数字化。
这种类型的事件数据记录下发生的事情,而且往往比传统数据库应用要大好几个数量级。这对于处理提出了重大挑战。
TThe explosion of specialized data systems
第二个趋势来自于专门的数据系统的爆发,通常这些数据系统在最近的五年中开始变得流行,并且可以免费获得。专门的数据系统是为OLAP, 搜索, 简单 在线 存储, 批处理, 图像分析, 等 等 而存在的。
更多的不同类型数据的组合,以及将这些数据存放到更多的系统中的愿望,导致了一个巨大的数据集成问题。
Log-structured data flow
为了处理系统之间的数据流,日志是最自然的数据结构。其中的秘诀很简单:
将所有组织的数据提取出来,并将它们放到一个中心日志,以便实时查阅。
每个逻辑数据源都可以建模为它自己的日志。一个数据源可以是一个应用程序的事件日志(如点击量或者页面浏览量),或者是一个接受修改的数据库表。每个订阅消息的系统都尽可能快的从日志读取信息,将每条新的记录保存到自己的存储,并且提升其在日志中的地位。订阅方可以是任意一种数据系统 —— 一个缓存,Hadoop,另一个网站中的另一个数据库,一个搜索系统,等等。
例如,日志针对每个更改给出了逻辑时钟的概念,这样所有的订阅方都可以被测量。推导不同的订阅系统的状态也因此变得相对简单的多,因为每个系统都有一个读取动作的“时间点”。
为了让这个显得更具体,我们考虑一个简单的案例,有一个数据库和一组缓存服务器集群。日志提供了一种同步更新所有这些系统,并推导出每一个系统的接触时间点的方法。我们假设写了一条日志 X,然后需要从缓存做一次读取。如果我们想保证看到的不是陈旧的数据,我们只需保证没有从任何尚未复制 X 的缓存中读取即可。
日志也起到缓存的作用,使数据生产与数据消费相同步。由于许多原因这个功能很重要,特别是在多个订阅方消费数据的速度各不相同的时候。这意味着一个订阅数据系统可以宕机,或者下线维护,之后重新上线以后再赶上来:订阅方按照自己控制的节拍来消费数据。批处理系统,如 Hadoop 或者是一个数据仓库,或许只是每小时或者每天消费一次数据,而实时查询系统可能需要及时到秒。由于无论是原始数据源还是日志,都没有各种目标数据系统的相关知识,因此消费方系统可以被添加和删除,而无需传输管道的变化。
Eache working data pipeline is designed like a log; each broken data pipeline is broken is its own way – Anna Karenina principle
特别重要的是:目标系统只知道日志而不知道原始系统的任何细节。 消费者系统不需要关心数据是来自 RDBMS,新的键值存储,还是没有任何类型的实时查询系统。 这似乎是一个小问题,但实际上是需要仔细鉴别的(critical)。
这里我使用术语“日志”取代了“消息系统”或者“发布-订阅”,因为它在语义上更明确,并且对支持数据复制的实际实现这样的需求,有着更接近的描述。我发现“发布订阅”并不比间接寻址的消息具有更多的含义——如果你比较任何两个发布-订阅的消息传递系统的话,你会发现他们承诺的是完全不同的东西,而且大多数模型在这一领域都不是有用的。你可以认为日志是一种消息系统,它具有持久性保证和强大的订阅语义。在分布式系统中,这个通信模型有时有个(有些可怕的)名字叫做atomic broadcast。
值得强调的是,日志仍然只是基础设施。这并不是管理数据流这个故事的结束:故事的其余部分围绕着元数据,模式,兼容性,以及处理数据结构的所有细节及其演化。除非有一种可靠的,一般的方法来处理数据流运作,语义在其中总是次要的细节。
At LinkedIn
在 LinkedIn 从集中式关系数据库向分布式系统集合转化的过程中,我看到这个数据集成问题迅速演变。
主要的数据系统包括:
- Search
- Social Graph
- Voldemort (key-value store)
- Espresso (document store)
- Recommendation engine
- OLAP query engine
- Hadoop
- Terradata
- Ingraphs (monitoring graphs and metrics services)
这种使用日志作为数据流的思想,甚至在我到这里之前就已经与 LinkedIn 相伴了。我们开发的一个最早的基础设施之一,是一种称为 databus 的服务,它在我们早期的 Oracle 表上提供了一种日志缓存抽象,可伸缩订阅数据库修改,这样我们就可以很好支持我们的社交网络和搜索索引。
我会给出一些历史并交代一下上下文。我首次参与到这些大约是在 2008 年左右,在我们转移键值存储之后。我的下一个项目是让一个工作中的 Hadoop 配置演进,并给其增加一些我们的推荐流程。由于缺乏这方面的经验,我们自然而然的安排了数周计划在数据的导入导出方面,剩下的时间则用来实现奇妙的预测算法。这样我们就开始了长途跋涉。
我们本来计划是仅仅将数据从现存的 Oracle 数据仓库中剖离。但是我们首先发现将数据从 Oracle 中迅速取出是一种黑暗艺术。更糟的是,数据仓库的处理过程与我们为 Hadoop 而计划的批处理生产过程不适合——其大部分处理都是不可逆转的,并且与即将生成的报告具体相关。最终我们采取的办法是,避免使用数据仓库,直接访问源数据库和日志文件。最后,我们为了加载数据到键值存储 并生成结果,实现了另外一种管道。
这种普通的数据复制最终成为原始开发项目的主要内容之一。糟糕的是,在任何时间任意管道都有一个问题,Hadoop 系统很大程度上是无用的——在错误的数据基础上运行奇特的(fancy)算法,只会产生更多的错误数据。
虽然我们已经以一种通用的方式创建事物,但是每个数据源都需要自定义配置安装。这也被证明是巨量错误与失败的根源。我们在 Hadoop 上实现的网站功能已经开始流行起来,同时我们发现我们有一长串感兴趣的工程师。每个用户都有他们想要集成的一系列系统,他们想要的一系列新数据源。
ETL in Ancient Greece. Not much has changed.
有些东西在我面前开始渐渐清晰起来。
首先,我们已建成的通道虽然有一些杂乱,但实质上它们是很有价值的。在采用诸如 Hadoop 的新的处理系统生成可用数据的过程,它解锁了大量的可能性。 基于这些数据过去很难实现的计算,如今变为可能。 许多新的产品和分析技术都来源于把分片的数据放在一起,这些数据被锁定在特定的系统中。
第二, 众所周知,可靠的数据加载需要数据通道的深度支持。如果我们可以捕获所有我们需要的结构,我就可以使得 Hadoop 数据全自动的加载,这样就不需要额外的操作来增加新的数据源或者处理模式变更–数据就会自动的出现在 HDFS,Hive 表就会自动的生成对应于新数据源的恰当的列。
第三,我们的数据覆盖率仍然非常低。如果你查看存储于 Hadoop 中的可用的 Linked 数据的全部百分比,它仍然是不完整的。花费大量的努力去使得各个新的数据源运转起来,使得数据覆盖度完整不是一件容易的事情。
我们正在推行的,为每个数据源和目标增建客户化数据加载,这种方式很显然是不可行的。我们有大量的数据系统和数据仓库。把这些系统和仓库联系起来,就会导致任意一对系统会产生如下所示的客户化通道。
需要注意的是:数据是双向流动的:例如许多系统诸如数据库和 Hadoop 既是数据转化的来源又是数据转化的目的地。这就意味着我们我们不必为每个系统建立两个通道:一个用于数据输入,一个用于数据输出。
这显然需要一大群人,而且也不具有可操作性。随着我们接近完全连接,最终我们将有差不多 O(N2)条管道。
Instead,我们需要像这样通用的东西:
我们需要尽可能的将每个消费者与数据源隔离。理想情形下,它们应该只与一个单独的数据仓库集成,并由此让他们能访问到所有东西。
这个思想是增加一个新的数据系统——或者它是一个数据源或者它是一个数据目的地——让集成工作只需连接到一个单独的管道,而无需连接到每个数据消费方。
在相当长的时间内,Kafka 是独一无二的底层产品,它既不是数据库,也不是日志文件收集系统,更不是传统的消息系统。但是最近 Amazon 提供了非常类似 Kafka 的服务,称之为 Kinesis.相似度包括了分片处理的方式,数据的保持,甚至包括在 Kafka API 中,有点特别的高端和低端消费者分类。我很开心看到这些,这表明了你已经创建了很好的底层协议,AWS 已经把它作为服务提供。他们对此的期待与我所描述的吻合:通道联通了所有的分布式系统,诸如 DynamoDB, RedShift, S3 等,它同时作为使用 EC2 进行分布式流处理的基础。
Relationship to ETL and the Data Warehouse
我们再来聊聊数据仓库。数据仓库是清洗和归一数据结构用于支撑数据分析的仓库。这是一个伟大的理念。对不熟悉数据仓库概念的人来说,数据仓库方法论包括了:周期性的从数据源抽取数据,把它们转化为可理解的形式,然后把它导入中心数据仓库。对于数据集中分析和处理,拥有高度集中的位置存放全部数据的原始副本是非常宝贵的资产。在高层级上,也许你抽取和加载数据的顺序略微调整,这个方法论不会有太多变化,无论你使用传统的数据仓库 Oracle 还是 Teradata 或者 Hadoop。
数据仓库是极其重要的资产,它包含了原始的和规整的数据,但是实现此目标的机制有点过时了。
对以数据为中心的组织关键问题是把原始的归一数据联结到数据仓库。数据仓库是批处理的基础查询:它们适用于各类报表和临时性分析,特别是当查询包含了简单的计数、聚合和过滤。但是如果一个批处理系统仅仅包含了原始的完整的数据的数据仓库,这就意味着这些数据对于实时数据处理、搜索索引和系统监控等实时的查询是不可用的。
依我之见,ETL 包括两件事:首先,它是抽取和数据清洗过程–特别是释放被锁在组织的各类系统中的数据,移除系统专有的无用物。第二,依照数据仓库的查询重构数据。例如使其符合关系数据库类型系统,强制使用星号、雪花型模式,或者分解为高性能的柱状格式等。合并这两者是有困难的。这些规整的数据集应当可以在实时或低时延处理中可用,也可以在其它实施存储系统索引。
在我看来,正是因为这个原因有了额外好处:使得数据仓库 ETL 更具了组织级的规模。数据仓库的精典问题是数据仓库负责收集和清洗组织中各个组所生成的全部数据。各组织的动机是不同的,数据的生产者并不知晓在数据仓库中数据的使用情况,最终产生的数据很难抽取,或者需要花费规模化的转化才可以转化为可用的形式。当然, 中心团队不可能恰到好处的掌握规模,使得这规模刚好与组织中其它团队相匹配,因此数据的覆盖率常常差别很大,数据流是脆弱的同时变更是缓慢的。
较好的方法是有一个中心通道,日志和用于增加数据的定义良好的 API。与通道集成的且提供良好的结构化的数据文件的职责依赖于数据的生产者所生成的数据文件。这意味着在设计和实施其它系统时应当考虑数据的输出以及输出的数据如何转化为结构良好的形式并传递给中心通道。增加新的存储系统倒是不必因为数据仓库团队有一个中心结点需要集成而关注数据仓库团队。数据仓库团队仅需处理简单的问题,例如从中心日志中加载结构化的数据,向其它周边系统实施个性化的数据转化等。
如图所示:当考虑在传统的数据仓库之外增加额外的数据系统时,组织结构的可扩展性显得尤为重要。例如,可以考虑为组织的完整的数据集提供搜索功能。或者提供二级的数据流监控实时数据趋势和告警。无论是这两者中的哪一个,传统的数据仓库架构甚至于 Hadoop 聚簇都不再适用。更糟的是,ETL 的流程通道的目的就是支持数据加载,然而 ETL 似乎无法输出到其它的各个系统,也无法通过引导程序,使得这些外围的系统的各个架构成为适用于数据仓库的重要资产。这就不难解释为什么组织很难轻松的使用它的全部数据。反之,如果组织已建立起了一套标准的、结构良好的数据,那么任何新的系统要使用这些数据仅仅需要与通道进行简单的集成就可以实现。
这种架构引出了数据清理和转化在哪个阶段进行的不同观点:
- 由数据的生产者在把数据增加到公司全局日志之前。
- 在日志的实时转化阶段进行,这将会产生一个新的转化日志。
- 在向目标系统加载数据时,做为加载过程的一部分进行。
理想的模形是:由数据的生产者在把数据发布到日志之前对数据进行清理。这样可以确保数据的权威性,不需要维护其它的遗留物例如为数据产生的特殊处理代码或者维护这些数据的其它的存储系统。这些细节应当由产生数据的团队来处理,因为他们最了解他们自己的数据。这个阶段所使用的任何逻辑都应该是无损的和可逆的。
任何可以实时完成的增值转化类型都应当基于原始日志进行后期处理。这一过程包括了事件数据的会话流程,或者增加大众感兴趣的衍生字段。原始的日志仍然是可用的,但是这种实时处理产生的衍生日志包含了参数数据。
最终,只有针对目标系统的聚合需要做了加载流程的一部分。它包括了把数据转化成特定的星型或者雪花状模式,从而用于数据仓库的分析和报表。因为在这个阶段,大部分自然的映射到传统的 ETL 流程中,而现在它是在一个更加干净和规整的数据流集在进行的,它将会更加的简单。
Log Files and Events
我们再来聊聊这种架构的优势:它支持解耦和事件驱动的系统。
在网络行业取得活动数据的典型方法是把它记为文本形式的日志,这些文本文件是可分解进入数据仓库或者 Hadoop,用于聚合和查询处理的。由此产生的问题与所有批处理的 ETL 的问题是相同的:它耦合了数据流进入数据仓库系统的能力和流程的调度。
在 LinkedIn 中,我们已经以中心日志的方式构建了事件数据处理。我们正在使用 Kafka 做为中心的、多订阅者事件日志。我们已经定义了数百种事件类型,每种类型都会捕获用于特定类型动作的独特的属性。这将会覆盖包括页面视图、表达式、搜索以及服务调用、应用异常等方方面面。
为了进一步理解这一优势:设想一个简单的事务–在日志页面显示已发布的日志。这个日志页面应当只包括显示日志所需要的逻辑。然而,在相当多的动态站点中,日志页面常常变的添加了很多与显示日志无关的逻辑。例如,我们将对如下的系统进行集成:
- 需要把数据传送到 Hadoop 和数据仓库中用于离线数据处理。
- 需要对视图进行统计,确保视图订阅者不会破坏一些内容片段。
- 需要聚合这些视图,视图将用于作业发布者的分析页面显示。
- 需要记录视图以确保我们为作业推荐的使用者提供了恰当的印象覆盖,我们不想一次次的重复同样的事情。
- 推荐系统需要记录日志用于正确的跟踪作业的普及度。
- 等等。
不久,简单的作业显示变得相当的复杂。我们增加了作业显示的其它终端–移动终端应用等–这些逻辑必须继续存在,复杂度不断的增加。更糟的是我们需要与之做接口交互的系统现在是错综复杂的–在为显示日作业而工作的工程师们需要知晓多个其它系统和它们的特征,才可以确保它们被正确的集成了。这仅仅是问题的简单版本,真实的的应用系统只会更加的复杂。
“事件驱动”的模式提供了一种简化这类问题的机制。作业显示页面现在只显示作业并记录与正在显示的作业,作业订阅者相关的其它属性,和其它与作业显示相关的其它有价值的属性。每个与此相关的其它系统诸如推荐系统、安全系统、作业推送分析系统和数据仓库,所有这些只是订阅种子文件,并进行它们的操作。显示代码并不需要关注其它的系统,也不需要因为增加了数据的消费者而相应的进行变更。
Building a Scalable Log
当然,把发布者与订阅者分离不再是什么新鲜事了。但是如果你想要确保提交日志的行为就像多个订阅者实时的分类日志那样记录网站发生的每件事时,可扩展性就会成为你所面临的首要挑战。如果我们不能创建快速、高性价比和可扩展性灵活的日志以满足实际的可扩展需求,把日志做为统一的集成机制不再是美好的想像,
人们普遍认为分布式日志是缓慢的、重量经的概念(并且通常会把它仅仅与“原数据”类型的使用联系起来,对于这类使用 Zookeeper 可以适用)。但是深入实现并重点关注分类记录大规模的数据流,这种需求是不切实际的。在 LinkedIn, 我们现在每天通过 Kafka 运行着超过 600 亿个不同的消息写入点(如果统计镜相与数据中心之间的写入,那么这个数字会是数千亿。)
我们在 Kafk 中使用了一些小技巧来支持这种可扩展性:
- 日志分片
- 通过批处理读出和写入优化吞吐力
- 规避无用的数据复制
为了确保水平可扩展性,我们把日志进行切片:
每个切片都是一篇有序的日志,但是各片之间没有全局的次序(这个有别于你可能包含在消息中的挂钟时间)。把消息分配到特定的日志片段这是由写入者控制的,大部分使用者会通过用户 ID 等键值来进行分片。分片可以把日志追加到不存在协作的片段之间,也可以使系统的吞吐量与 Kafka 聚簇大小成线性比例关系。
每个分片都是通过可配置数量的复制品复制的,每个复制品都有分片的一份完全一致的拷贝。无论何时,它们中的任一个都可以做为主分片,如果主分片出错了,任何一个复制品都可以接管并做为主分片。
缺少跨分片的全局顺序是这个机制的局限性,但是我们不认为它有多重要。事实上,与日志的交互主要来源于成百上千个不同的流程,以致于对于它们的行为排一个总体的顺序是没什么意义的。相反,我们可以确保的是我们提供的每个分片都是按顺序保留的。Kafka 保证了追加到由单一发送者送出的特定分片会按照发送的顺序依次处理。
日志,就像文件系统一样,是容易优化成线性可读可写的样式的。日志可以把小的读入和写出组合成大的、高吞吐量的操作。Kafka 一直至立于实现这一优化目标。批处理可以发生在由客户端向服务器端发送数据、写入磁盘;在服务器各端之间复制;数据传递给消费者和确认提交数据等诸多环节。
最终,Kafka 使用简单的二进制形式维护内存日志,磁盘日志和网络数据传送。这使得我们可以使用包括“0 数据复制传送”在内的大量的优化机制。
这些优化的积累效应是你常常进行的写出和读入数据的操作可以在磁盘和网络上得到支持,甚至于维护内存以外的大量数据集。 这些详细记述并不意味着这是关于 Kafka 的主要内容,那么我就不需要了解细节了。你可在这个链接阅读到更多的关于 LinkedIn 的方法,和这个链接是关于 Kafka 的设计总述。
Part Three: Logs & Real-time Stream Processing
到此为止,我只是描述从端到端数据复制的理想机制。但是在存储系统中搬运字节不是所要讲述内容的全部。最终我们发现日志是流的另一种说法,日志是流处理的核心。
但是,等等,什么是流处理呢?
如果你是 90 年代晚期或者 21 世纪初数据库文化或者数据基础架构产品的爱好者,那么你就可能会把流处理与建创 SQL 引擎或者创建“箱子和箭头”接口用于事件驱动的处理等联系起来。
如果你关注开源数据库系统的大量出现,你就可能把流处理和一些开源数据库系统关联起来,这些系统包括了:Storm,Akka,S4和Samza.但是大部分人会把这些系统作为异步消息处理系统,这些系统与支持群集的远程过程调用层的应用没什么差别(而事实上在开源数据库系统领域某些方面确实如此)。
这些视图都有一些局限性。流处理与 SQL 是无关的。它也局限于实时流处理。不存在内在的原因限制你不能处理昨天的或者一个月之前的流数据,且使用多种不同的语言表达计算。
我把流处理视为更广泛的概念:持续数据流处理的基础架构。我认为计算模型可以像 MapReduce 或者分布式处理架构一样普遍,但是有能力处理低时延的结果。
处理模型的实时驱动是数据收集方法。成批收集的数据是分批处理的。数据是不断收集的,它也是按顺序不断处理的。
美国的统计调查就是成批收集数据的良好典范。统计调查周期性的开展,通过挨门挨户的走访,使用蛮力发现和统计美国的公民信息。1790 年统计调查刚刚开始时这种方式是奏效的。那时的数据收集是批处理的,它包括了骑着马悠闲的行进,把信息写在纸上,然后把成批的记录传送到人们统计数据的中心站点。现在,在描述这个统计过程时,人们立即会想到为什么我们不保留出生和死亡的记录,这样就可以产生人口统计信息这些信息或是持续的或者是其它维度的。
这是一个极端的例子,但是大量的数据传送处理仍然依赖于周期性的转储,批量转化和集成。处理大容量转储的唯一方法就是批量的处理。但是随着这些批处理被持续的供给所取代,人们自然而然的开始不间断的处理以平滑的处理所需资源并且消除延迟。
例如 LinkedIn 几乎没有批量数据收集。大部分的数据或者是活动数据或者是数据库变更,这两者都是不间断发生的。事实上,你可以想到的任何商业,正如:Jack Bauer 告诉我们的,低层的机制都是实时发生的不间断的流程事件。数据是成批收集的,它总是会依赖于一些人为的步骤,或者缺少数字化或者是一些自动化的非数字化流程处理的遗留信息。当传送和处理这些数据的机制是邮件或者人工的处理时,这一过程是非常缓慢的。首轮自动化总是保持着最初的处理形式,它常常会持续相当长的时间。
每天运行的批量处理作业常常是模拟了一种一天的窗口大小的不间断计算。当然,低层的数据也经常变化。在 LinkedIn,这些是司空见贯的,并且使得它们在 Hadoop 运转的机制是有技巧的,所以我们实施了一整套管理增量的 Hadoop 工作流的架构。
由此看来,对于流处理可以有不同的观点。流处理包括了在底层数据处理的时间概念,它不需要数据的静态快照,它可以产生用户可控频率的输出,而不用等待数据集的全部到达。从这个角度上讲,流处理就是广义上的批处理,随着实时数据的流行,会儿更加普遍。
这就是为什么从传统的视角看来流处理是利基应用。我个人认为最大的原因是缺少实时数据收集使得不间断的处理成为了学术性的概念。
我想缺少实时数据收集就像是商用流处理系统注定的命运。他们的客户仍然需要处理面向文件的、每日批量处理 ETL 和数据集成。公司建设流处理系统关注的是提供附着在实时数据流的处理引擎,但是最终当时极少数人真正使用了实时数据流。事实上,在我在 LinkedIn 工作的初期,有一家公司试图把一个非常棒的流处理系统销售给我们,但是因为当时我们的全部数据都按小时收集在的文件里,当时我们提出的最好的应用就是在每小时的最后把这些文件输入到流处理系统中。他们注意到这是一个普遍性的问题。这些异常证明了如下规则:流处理系统要满足的重要商业目标之一是:财务, 它是实时数据流已具备的基准,并且流处理已经成为了瓶颈。
甚至于在一个健康的批处理系统中,流处理作为一种基础架构的实际应用能力是相当广泛的。它跨越了实时数据请求-应答服务和离线批量处理之间的鸿沟。现在的互联网公司,大约 25%的代码可以划分到这个类型中。
最终这些日志解决了流处理中绝大部分关键的技术问题。在我看来,它所解决的最大的问题是它使得多订阅者可以获得实时数据。对这些技术细节感兴趣的朋友,我们可以用开源的 Samza,它是基于这些理念建设的一个流处理系统。这些应用的更多技术细节我们在此文档中有详细的描述。
Data flow graphs
流处理最有趣的角度是它与流处理系统内部无关,但是与之密切相关的是如何扩展了我们谈到的早期数据集成的数据获取的理念。我们主要讨论了基础数据的获取或日志–事件和各类系统执行中产生的数据等。但是流处理允许我们包括了计算其它数据的数据。这些衍生的数据在消费者看来与他们计算的原始数据没什么差别。这些衍生的数据可以按任意的复杂度进行压缩。
让我们再深入一步。我们的目标是:流处理作业可以读取任意的日志并把日志写入到日志或者其它的系统中。他们用于输入输出的日志把这些处理关联到一组处理过程中。事实上,使用这种样式的集中日志,你可以把组织全部的数据抓取、转化和工作流看成是一系列的日志和写入它们的处理过程。
流处理器根本不需要理想的框架:它可能是读写日志的任何处理器或者处理器集合,但是额外的基础设施和辅助可以提供帮助管理处理代码。
日志集成的目标是双重的:
首先,它确保每个数据集都有多个订阅者和有序的。让我们回顾一下状态复制原则来记住顺序的重要性。为了使这个更加具体,设想一下从数据库中更新数据流–如果在处理过程中我们把对同一记录的两次更新重新排序,可能会产生错误的输出。 TCP 之类的链接仅仅局限于单一的点对点链接,这一顺序的持久性要优于 TCP 之类的链接,它可以在流程处理失败和重连时仍然存在。
第二,日志提供了流程的缓冲。这是非常基础的。如果处理流程是非同步的,那么上行生成流数据的作业比下行消费流数据的作业运行的更快。这将会导致处理流程阻塞,或者缓冲数据,或者丢弃数据。丢弃数据并不是可行的方法,阻塞将会导致整个流程图立即停止。 日志实际上是一个非常大的缓冲,它允许流程重启或者停止但不会影响流程图其它部分的处理速度。如果要把数据流扩展到更大规模的组织,如果处理作业是由多个不同的团队提供的,这种隔离性是极其重的。我们不能容忍一个错误的作业引发后台的压力,这种压力会使得整个处理流程停止。
Storm和Sama这两者都是按非同步方式设计的,可以使用 Kafka 或者其它类似的系统作为它们的日志。
Stateful Real-Time Processing
一些实时流处理在转化时是无状态的记录。在流处理中大部分的应用会是相当复杂的统计、聚合、不同窗口之间的关联。例如有时人们想扩大包含用户操作信息的事件流(一系列的单击动作)–实际上关联了用户的单击动作流与用户的账户信息数据库。不变的是这类流程最终会需要由处理器维护的一些状态信息。例如数据统计时,你需要统计到目前为止需要维护的计数器。如果处理器本身失败了,如何正确的维护这些状态信息呢?
最简单的替换方案是把这些状态信息保存在内存中。但是如果流程崩溃,它就会丢失中间状态。如果状态是按窗口维护的,流程就会回退到日志中窗口开始的时间点上。但是,如果统计是按小时进行的,那么这种方式就会变得不可行。
另一个替换方案是简单的存储所有的状态信息到远程的存储系统,通过网络与这些存储关联起来。这种机制的问题是没有本地数据和大量的网络间通信。
我们如何支持处理过程可以像表一样分区的数据呢?
回顾一下关于表和日志二相性的讨论。这一机制提供了工具把数据流转化为与处理过程协同定位的表,同时也提供了这些表的容错处理的机制。
流处理器可以把它的状态保存在本地的表或索引–bdb,或者leveldb,甚至于类似于 Lucene 或 fastbit 一样不常见的索引。这些内容存储在它的输入流中(或许是使用任意的转化)。生成的变更日志记录了本地的索引,它允许存储事件崩溃、重启等的状态信息。流处理提供了通用的机制用于在本地输入流数据的随机索引中保存共同分片的状态。
当流程运行失败时,它会从变更日志中恢复它的索引。每次备份时,日志把本地状态转化成一系列的增量记录。
这种状态管理的方法有一个优势是把处理器的状态也做为日志进行维护。我们可以把这些日志看成与数据库表相对应的变更日志。事实上,这些处理器同时维护着像共同分片表一样的表。因为这些状态它本身就是日志,其它的处理器可以订阅它。如果流程处理的目标是更新结点的最后状态,这种状态又是流程的输出,那么这种方法就显得尤为重要。
为了数据集成,与来自数据库的日志关联,日志和数据库表的二象性就更加清晰了。变更日志可以从数据库中抽取出来,日志可以由不同的流处理器(流处理器用于关联不同的事件流)按不同的方式进行索引。
我们可以列举在 Samza 中有状态流处理管理的更多细节和大量实用的例子。
Log Compaction
当然,我们不能奢望保存全部变更的完整日志。除非想要使用无限空间,日志不可能完全清除。为了澄清它,我们再来聊聊 Kafka 的实现。在 Kafka 中,清理有两种选择,这取决于数据是否包括关键更新和事件数据。对于事件数据,Kafka 支持仅维护一个窗口的数据。通常,配置需要一些时间,窗口可以按时间或空间定义。虽然对于关键数据而言,完整日志的重要特征是你可以重现源系统的状态信息,或者在其它的系统重现。
随着时间的推移,保持完整的日志会使用越来越多的空间,重现所耗费的时间越来越长。因些在 Kafka 中,我们支持不同类型的保留。我们移除了废弃的记录(这些记录的主键最近更新过)而不是简单的丢弃旧日志。我们仍然保证日志包含了源系统的完整备份,但是现在我们不再重现原系统的全部状态,而是仅仅重现最近的状态。我们把这一特征称为日志压缩。
Part Four: System Building
我们最后要讨论的是在线数据系统设计中日志的角色。
在分布式数据库数据流中日志的角色和在大型组织机构数据完整中日志的角色是相似的。在这两个应用场景中,日志是对于数据源是可靠的,一致的和可恢复的。组织如果不是一个复杂的分布式数据系统呢,它究竟是什么?
分类计价吗?(Unbundling)
如果换个角度,你可以看到把整个组织系统和数据流看做是单一的分布式数据系统。你可以把所有的子查询系统(诸如 Redis, SOLR,Hive 表等)看成是数据的特定索引。你可以把 Storm 或 Samza 一样的流处理系统看成是发展良好的触发器和视图具体化机制。我已经注意到,传统的数据库管理人员非常喜欢这样的视图,因为它最终解释了这些不同的数据系统到底是做什么用的–它们只是不同的索引类型而已。
不可否认这类数据库系统现在大量的出现,但是事实上,这种复杂性一直都存在。即使是在关系数据库系统的鼎盛时期,组织中有大量的关系数据库系统。或许自大型机时代开始,所有的数据都存储在相同的位置,真正的集成是根本不存在的。存在多种外在需求,需要把数据分解成多个系统,这些外在需求包括:规模、地理因素、安全性,性能隔离是最常见的因素。这些需求都可以由一个优质的系统实现:例如,组织可以使用单一的 Hadoop 聚簇,它包括了全部的数据,可以服务于大型的和多样性的客户。
因此在向分布式系统变迁的过程中,已经存在一种处理数据的简便的方法:把大量的不同系统的小的实例聚合成为大的聚簇。许多的系统还不足以支持这一方法:因为它们不够安全,或者性能隔离性得不到保证,或者规模不符合要求。不过这些问题都是可以解决的。
依我之见,不同系统大量出现的原因是建设分布式数据库系统很困难。通过削减到单一的查询或者用例,每个系统都可以把规模控制到易于实现的程度。但是运行这些系统产生的复杂度依然很高。
未来这类问题可能的发展趋势有三种:
第一种可能是保持现状:孤立的系统还会或长或短的持续一段时间。这是因为建设分布式系统的困难很难克服,或者因为孤立系统的独特性和便捷性很难达到。基于这些原因,数据集成的核心问题仍然是如何恰当的使用数据。因此,集成数据的外部日志非常的重要。
第二种可能是重构:具备通用性的单一的系统逐步融合多个功能形成超极系统。这个超级系统表面看起来类似关系数据库系统,但是在组织中你使用时最大的不同是你只需要一个大的系统而不是无数个小系统。在这个世界里,除了在系统内已解决的这个问题不存在什么真正的数据集成问题。我想这是因为建设这样的系统的实际困难。
虽然另一种可能的结果对于工程师来说是很有吸引力的。新一代数据库系统的特征之一是它们是完全开源的。开源提供了一种可能性:数据基础架构不必打包成服务集或者面向应用的系统接口。在 Java 栈中,你可以看到在一定程度上,这种状况已经发生了。
- Zookeeper用于处理多个系统之间的协调,或许会从诸如Helix 或者Curator等高级别的抽象中得到一些帮助。
- Mesos和YARN用于处理流程可视化和资源管理。
- Lucene和LevelDB等嵌入式类库做为索引。
- Netty,Jetty和Finagle,rest.li等封装成高级别的用于处理远程通信。
- Avro,Protocol Buffers,Thrift和umpteen zillion等其它类库用于处理序列化。
- Kafka和Bookeeper提供支持日志。
如果你把这些堆放在一起,换个角度看,它有点像是简化版的分布式数据库系统工程。你可以把这些拼装在一起,创建大量的可能的系统。显而易见,现在探讨的不是最终用户所关心的 API 或者如何实现,而是在不断多样化和模块化的过程中如何设计实现单一系统的途径。因为随着可靠的、灵活的模块的出现,实施分布式系统的时间周期由年缩减为周,聚合形成大型整体系统的压力逐步消失。
The place of the log in system architecture
那些提供外部日志的系统如今已允许个人电脑抛弃他们自身复杂的日志系统转而使用共享日志。在我看来,日志可以做到以下事情:
- 通过对节点的并发更新的排序处理数据的一致性(无论在及时还是最终情况下)
- 提供节点之间的数据复制
- 提供”commit“语法(只有当写入器确保数据不会丢失时才会写入)
- 位系统提供外部的数据订阅资源
- 提供存储失败的复制操作和引导新的复制操作的能力
- 处理节点间的数据平衡
这实际上是一个数据分发系统最重要的部分,剩下的大部分内容与终端调用的 API 和索引策略相关。这正是不同系统间的差异所在,例如:一个全文本查询语句需要查询所有的分区,而一个主键查询只需要查询负责键数据的单个节点就可以了。
下面我们来看下该系统是如何工作的。系统被分为两个逻辑区域:日志和服务层。日志按顺序捕获状态变化,服务节点存储索引提供查询服务需要的所有信息(键-值的存储可能以 B-tree 或 SSTable 的方式进行,而搜索系统可能存在与之相反的索引)。写入器可以直接访问日志,尽管需要通过服务层代理。在写入日志的时候会产生逻辑时间戳(即 log 中的索引),如果系统是分段式的,那么就会产生与段数目相同数量的日志文件和服务节点,这里的数量和机器数量可能会有较大差距。
服务节点订阅日志信息并将写入器按照日志存储的顺序尽快应用到它的本地索引上。
客户端只要在查询语句中提供对应的写入器的时间戳,它就可以从任何节点中获取”读写“语义。服务节点收到该查询语句后会将其中的时间戳与自身的索引比较,如果必要,服务节点会延迟请求直到对应时间的索引建立完毕,以免提供旧数据。
服务节点或许根本无需知道”控制“或”lerder 选举(leader election)“的概念,对很多简单的操作,服务节点可以爱完全脱离领导的情况下提供服务,日志即是信息的来源。
分发系统所需要做的其中一个比较复杂的工作,就是修复失败节点并移除几点之间的隔离。保留修复的数据并结合上各区域内的数据快照是一种较为典型的做法,它与保留完整的数据备份并从垃圾箱内回收日志的做法几乎等价。这就使得服务层简单了很多,日志系统也更有针对性。
有了这个日志系统,你可以订阅到 API,这个 API 提供了把 ETL 提供给其它系统的数据内容。事实上,许多系统都可以共享相同的日志同时提供不同的索引,如下所示:
这个系统的视图可以清晰的分解到日志和查询 API,因为它允许你从系统的可用性和一致性角度分解查询的特征。这可以帮助我们对系统进行分解,并理解那些并没按这种方式设计实施的系统。
虽然 Kafka 和 Bookeeper 都是一致性日志,但这不是必须的,也没什么意义。你可以轻松的把 Dynamo 之类的数据构分解为一致性的 AP 日志和键值对服务层。这样的日志使用起来灵活,因为它重传了旧消息,像 Dynamo 一样,这样的处理取决于消息的订阅者。
在很多人看来,在日志中另外保存一份数据的完整复本是一种浪费。事实上,虽然有很多因素使得这件事并不困难。首先,日志可以是一种有效的存储机制。我们在 Kafka 生产环境的服务器上存储了 5 TB 的数据。同时有许多的服务系统需要更多的内存来提供有效的数据服务,例如文本搜索,它通常是在内存中的。服务系统同样也需样硬盘的优化。例如,我们的实时数据系统或者在内存外提供服务或者使用固态硬盘。相反,日志系统只需要线性的读写,因此,它很乐于使用 TB 量级的硬盘。最终,如上图所示,由多个系统提供的数据,日志的成本分摊到多个索引上,这种聚合使得外部日志的成本降到了最低点。
LinkedIn 就是使用了这种方式实现它的多个实时查询系统的。这些系统提供了一个数据库(使用数据总线做为日志摘要,或者从 Kafka 去掉专用的日志),这些系统在顶层数据流上还提供了特殊的分片、索引和查询功能。这也是我们实施搜索、社交网络和 OLAP 查询系统的方式。事实上这种方式是相当普遍的:为多个用于实时服务的服务系统提供单一的数据(这些来自 Hadoop 的数据或是实时的或是衍生的)。这种方式已被证实是相当简洁的。这些系统根本不需要外部可写入的 API,Kafka 和数据库被用做系统的记录和变更流,通过日志你可以查询系统。持有特定分片的结点在本地完成写操作。这些结点盲目的把日志提供的数据转录到它们自己的存储空间中。通过回放上行流日志可以恢复转录失败的结点。
这些系统的程度则取决于日志的多样性。一个完全可靠的系统可以用日志来对数据分片、存储结点、均衡负载,以及用于数据一致性和数据复制等多方面。在这一过程中,服务层实际上只不过是一种缓存机制,这种缓存机制允许直接写入日志的流处理。
结束语
如果你对于本文中所谈到的关于日志的大部内容,如下内容是您可以参考的其它资料。对于同一事务人们会用不同的术语,这会让人有一些困惑,从数据库系统到分布式系统,从各类企业级应用软件到广阔的开源世界。无论如何,在大方向上还是有一些共同之处。