0%

日志:每个软件工程师都应该知道的实时数据的统一抽象

原文:The Log: What every software engineer should know about real-time data’s unifying abstraction

作者:Jay Kreps

我大约在六年前加入了领英,那是一个特别有趣的时间点。那时我们正面临着单体集中式数据库极限的挑战,需要开始向专门的分布式系统转变。这真是一个有趣的经历:我们构建、部署和运行了分布式图数据库、分布式搜索后端、Hadoop套件、一代及二代键值数据库,并运行至今。

在这个过程中,我学到的最有用的一件事情是,我们在这里构建的大多数组件,其核心都有一个简单的概念:日志,有时也被称作先写日志、提交日志或者事务日志,日志几乎与计算机的历史一样悠久,它是许多分布式数据系统和实时应用架构里面的核心。

如果你不了解日志,你也就不可能完全了解数据库、NoSQL存储、键值存储、副本、paxos、hadoop、版本控制、甚至几乎任何软件系统,然而大多数软件工程师都不熟悉它,我希望改变这个现状。在这篇文章中,我将告诉你一切你需要知道的关于日志的事情,包括什么是日志、如何在数据集成、实时处理和系统构建中使用日志。

第一部分:什么是日志

日志可能是最简单的存储抽象,它是一个只能追加、按时间排序的完全有序的序列。

img

记录被追加到到日志的末尾,读取时从左到右进行。每条记录都按序分配了一个唯一数字。

记录的顺序包含了时间的概念,因为左边的条目在时间上是比右边的条目更加早的。日志记录的数字可以认为是这条记录的时间戳。将这种顺序描述为时间的概念,乍一看可能会有点奇怪,但有一个好处是,它可以从任何特定的物理时钟中脱离出来,这个好处在我们谈到分布式系统时显得更加至关重要。

记录的内容和形式不在这次讨论的重点。当然,我们不能一直往日志中追加记录,因为磁盘的空间是有限的,这一点稍后会谈到。

日志并不安全不同于文件或者数据库表。一个文件是一个字节数组,数据库表是一系列的记录,日志也是一种表或者文件,但它是时间上有序的。

你可能会有疑问,这么简单的东西,为什么会值得讨论?只能追加的记录序列和数据系统有什么关系?答案是,日志有一个很关键的点,它记录了发生了什么事情以及是什么时候发生的,在多数情况下,这点对于分布式系统而言,是问题的关键。

在我们深入之前,先让我说明一件可能让人有疑惑的事情。每个工程师都很熟悉另一种日志的定义,那就是一个应用通过syslog或者log4j向本地文件写入的非结构化的错误信息或者跟踪信息,为了避免歧义,我称这个为“应用日志”。相对于我在本文描述的日志,应用日志具有另一种形式。这两种日志最大的不同之处在于,应用日志主要是用于人进行阅读,而我所描述的日志或数据日志被用于程序化访问。

(事实上,如果你稍微思考下,就会发现,人在单台机器上阅读日志的想法有些不合时宜。当涉及到许多服务和服务器时,这种方法很快就变得一种难于管理。日志的目的很快就变成了查询和图表的输入,以理解机器的行为,因此用英文书写在文件的日志并不如在本文中描述的结构化日志合适。)

数据库中的日志

我不知道日志的概念起源于哪里,或许是因为它太过简单以至于发明者不认为它是一种发明,就像十分查找算法。它早在IBM的Sytem R时就出现了。日志在数据库的使用是在崩溃的时候,必须保持数据结构和索引的同步。为了保证原子性、持久性,数据库系统在对它维护的所有数据结构应用修改之前,会通过日志写入将要修改的记录的信息。日志是对发生的事情的记录,每个数据库表或索引都是对有用的数据结构或索引的历史数据的投影。由于日志是即时持久化的,它也作为一种可靠数据来源,用于在崩溃时恢复其他持久化结构的。

随着时间的推进,日志的用法也从用于ACID的实现细节,演变为在数据库之前同步数据的一种方式。发生在主数据库上的变更序列也需要被应用于远程的从库上,以保持同步。Oracle、MySQL、PostgreSQL都包含了传输协议,将部分日志传输到扮演从库角色的副本数据库。Oracle还将日志产品化为一种通用通用的数据订阅机制,非Oracle数据订阅者可以使用它们的XStreams和GoldenGate进行订阅使用。在MySQL和PostgreSQL上,相似的组件也作为许多数据库构中的关键组件存在。

由于这个原因,机器可读的日志的概念也被局限于数据库的内部。日志作为数据订阅机制的使用似乎是偶然出现的。但这种抽象非常适用于消息、数据流和实时数据处理的所有类型。

分布式系统中的日志

在分布式系统中,变更的序列和数据的分发是日志解决的两个尤为重要的问题。就更新的顺序达成一致(或达成不一致、应对副作用),是分布式系统设计的核心问题。

以日志为中心的分布式系统方法源于一个简单的观察,我称它为“状态机复制原理”:

如果两个相同的、确定性的进程的起始状态是相同的,并且以同样的顺序获取了相同的输入,那么这两个进程将产生同样的输出,最终也拥有相同的状态。

这听起来有点晦涩,让我们深入了解它的含义。

确定性意味着这个进程是不依赖于时钟的,也不会有其他“带外数据”影响它的输出。如果一个程序的输出受线程执行的特定顺序影响、调用了gettimeofday或其他不可重复调用的方法,就可以认为是非确定性的。

进程的状态是最终保留在机器上内存或者磁盘的数据。

以相同的数据获取相同的输入听起来很熟悉,这跟日志相同。这是一个非常直观的概念:如果你给两段确定性的代码提供相同的日志输入,它们将得到相同的输出。

应用在分布式计算上就非常明显,你可以将使多个机器做相同的事情的问题,简化为实现分布式的一致性日志,并作为这些进程的输入的问题。在这里,日志的目的就是将所有的非确定性排除在输入流之外,以确保所有副本处理这些输入时保持同步。

当你理解了这个,这个也就不再复杂和深奥了:这差不多说,确定性的处理就是确定性的。然而,我仍然认为它是分布式系统设计中更通用的工具。

一个关于这个方法的最奇妙的一个事情就是,作为日志索引的时间戳也扮演着副本状态的时钟角色,你可以通过每个副本当前处理的最大的记录的一个简单的数字或者时间戳来描述每个副本。这个时间戳对应的日志体现了副本的状态。

根据日志写入的内容,在系统中应用这个原理有多种方式。例如,我们可以在日志中记录服务的请求、在对请求响应时服务的状态变化、或者执行的变更指令。理论上,我们甚至可以记录每个副本上要执行的一系列机器指令,要调用的方法名和参数。只要这两个进程以同样的方式处理这些输入,这些进程将在副本中保持一致的状态。

不同的用户群体似乎用不一样的方式描述日志的使用。数据库使用群体通常会区分物理日志和逻辑日志,物理日志指的是记录每一行变更的内容的日志,而逻辑日志记录的不是行的变更,而是会引起行数据变更的SQL命令(insert、update、delete语句)。

分布式系统文献通常将处理和复制广泛的分为两种方式。“状态机模型”通常被称为“主-主模型”,在这个模型下,我们维护了请求的日志,每个副本都处理这些请求。对“主-主模型”作了轻微的修改后,出现了“主-从模型”,在这个模型下,会选出一个副本作为主副本,来顺序处理到来的请求,并在处理过程中,在日志记录下状态的变更,其他的副本则顺序应用主副本上的状态变更,保持同步,以便在主副本异常时接替成为主副本。

img

为了理解这两种方式之前的不同,我们先来看一个不太严谨的例子,假设有多个算术服务,这些服务里维护了一个数字(初始值为0)作为服务的状态,并对这个数字应用加法和乘法。“主-主模型”下,在日志会记录下对这个值作出的变更,如“+1”、“*2”等,每个副本都会应用这些变更并得到相同的数字集。在“主-从”模型下,将存在唯一一个主副本,用于对维护的数字作出变更,并将结果记录到日志,如“1”、”3“、“6”等。从这个例子中,我们也知道了,为什么顺序是副本中保持一致性的关键因素,因为对一个加法和乘法操作调整了执行顺序将得到不一样的结果。

分布式日志可以被视为共识问题模型的一种数据结构,毕竟日志代表了一系列要追加的下一个值的决定。你必须要认真一点才能在Paxos算法中看到日志的身影,尽管日志是它们最常见的应用。Paxos通常被作为”multi-paxos”协议的一个扩展存在,把日志作为一系列共识问题的建模,日志中的每个槽都有一个值。但日志在ZAB、RAFT和Viewstamped Replication这些其他的协议中更加显而易见,在这些协议里面,直接作为维护分布式、一致性的日志的模型存在。

我有点怀疑我们的对这个的看法一定程序上受到了历史进程的影响,也许是由于过去几十年里,分布式计算理论的发展领先于其实际的应用。实际上,共识问题被过于简单化了。计算机系统几乎不会需要决定单个值,而几乎总是处理一系列请求。所以日志不能认为是一个简单的单值寄存器,而是一个更自然的抽象。

此外,对算法的关注使得系统需要的底层日志抽象变得模糊。我个人认为,我们最终将更加关注于作为商品化模块存在的日志,而不是它的实现,正如我们经常讨论哈希表而不需要了解它是使用的线性探测的杂音哈希还是其他,这种细节。日志将成为商品化接口的存在,同时许多算法和实现竞争以提供最好的保证或者其他可选的特性。

变更日志:表和事件的二象性

回到数据库的讨论,在变更日志和表之间,有着有趣的二象性。日志类似于借贷和银行处理的列表,数据库表则是当前所有账号的余额,如果你有变更日志,你可以应用这些变更日志,以创建拥有当前状态的表,这个表记录了每条记录某个特定时间的最新的状态。从某种意义上说,日志是更加基础的数据结构,除了创建原始表,你还可以将其转换并创建各种类型的派生表(是的,对于非关系数据库使用群体,数据库表也是关键的数据存储)。

这个过程也是可逆的,如果你有一张要更新的表,你可以记录这些变更,然后发布所有对这个表状态变更的日志。变更日志就是需要用于支持近实时副本的。在这个场景中,你可以看到表和事件的二象性:表支持的静态数据,而日志记录了变更。日志的魅力在于,它不仅拥有了数据库表最终版本的内容,也允许你通过它重建其他任何可能存在的版本,它就相当于数据库表中每个旧版本的备份集合。

这可能让你想起版本控制系统,版本控制系统和数据库系统有着非常密切的关系,版本控制解决了分布式系统必须解决的一个非常相似的问题:管理分布式的、并发的状态变更。一个版本控制系统建模的是补丁序列,而那实际上是日志。你检出了当前代码的一个快照,那其实类似于数据库表,你会注意到,在版本控制系统中,复制是通过日志完成的,就像是在其他有状态的分布式系统一样:当你更新时、也就是拉取补丁并将补丁应用到你当前的版本。

有些人最近从销售日志数据库的公司Datomic那里看到了这样一些想法,这个视频比较好的看到了他们是如何应用这些想法的,这些想法并不是专属于这个系统的,在过去十年里,他们很好地贡献了一部分分布式系统和数据库的文献。

这节内容可能有一些理论化了,别沮丧,马上就会有实操了。

接下来的内容

在文章的剩余内容,我将尝试讲述日志在分布式内部实现和抽象分布式模型之外的好处。这包括了:

  1. 数据集成。使一个组织中的数据更容易地用在所有存储和处理系统中。
  2. 实时灵气处理。计算派生数据流。
  3. 分布式系统设计。如何通过集中式日志设计来简化实际的应用系统。

这些用法都围绕着日志作为一个单体服务来解决实现。

在每个场景中,日志的好处都来源于日志能提供的简单的功能:生成持久化的、可重放的历史记录。惊讶地是,让多台机器能够以确定的方式并按各自的速度重试历史数据的能力,是这些问题的核心。

第二部分:数据集成

首先我将说明下“数据集成”指的是什么,以及它为什么重要,然后我们可以发现它是怎么跟日志联系在一起的。

数据集成正使得一个组织的数据在所有系统和服务中变得可用。

“数据集成”这个词语并不常见,但我找不到一个更好的词语。大家更熟知的ETL仅仅覆盖了数据集成的一个有限子集——主要在关系型数据仓库使用。但我正在描述的事物更多的可以认为是广泛用于实时系统和处理流的ETL。

在人们对专注于大数据,对大数据大肆宣扬中,你可能很少听到过数据集成,尽管如此,我仍认为“让数据变得可用”这个普通的问题,是一个组织可以关注的最有价值的一个问题之一。

对数据的有效使用遵循了马斯洛的需求层次理论,金字塔的底层部分是获取所有相关的数据,并将其放到一个适用的处理环境中(可以是华丽的实时查询系统,或者仅仅是文件文件和python脚本)。数据需要采用统一建模的方式,使得数据更易于获取和处理。一旦用统一的方式获取数据的基本需求被满足了,接着就可以在这基础上用不同的试进行数据的处理——MapReduce、实时查询系统等。

值得注意的是,在缺少一个可靠和完整的数据流时,Hadoop集群只不过是一个昂贵和难以安装的加热器。一旦数据和处理变得可用,人们就可以更多关注于好的数据模型和一致的易于理解的语义这种更精细的问题上。最后关注点可以转换到更高级的处理上,更好的可视化、报表、算法处理和预测上。

以我的经验,大多数组织在金字塔的底部都有巨大的漏洞,它们缺乏可靠的完整的数据流,而且想要直接跳到先进数据建模技术上。这是本末倒置的。

所以主要的问题是,我们怎么在组织中构建一个可靠的数据流,贯穿在所有的数据系统中。

数据集成:两个难题

两个趋势使得数据集成变得困难。

事件数据管道

第一个趋势是事件数据的增长。事件数据记录了发生的事情,而不是事物是什么。在网页系统中,指的是用户的活动日志,以及需要可靠操作和监控一个数据中心机器价值的机器事件和统计数据。人们倾向于称它们为日志数据,因为它通常被写到应用日志,但这混淆了形式和功能。数据是现代网站的核心,毕竟谷歌的财富是由点击和展示构建的相关性管道产生的,而这正是事件。

但这并不仅限于网站公司,这仅仅是因为网站公司是完全数字化的,所以它们很容易完成。金融数据一直以来都是以事件为中心的。RFID能将这种根据应用到物理对象上。我们认为,随着传统业务和活动的数据化,这种趋势将继续下去。

这种类型的事件数据记录了发生的事情,并且往往比传统数据库的使用要大上好几个数量级。这对处理有着巨大的挑战。

专用数据系统的兴起

第二个趋势来自于专用数据系统的兴起,这些系统在过去5年变得非常受欢迎并且是可以免费获取的。专用的系统用于OLAP、搜索、简单的在线存储、批处理、图分析等等。

更加多样化的更多数据,以及希望将这些数据用于更多系统的需求,导致了巨大的数据集成问题。

日志结构化的数据流

日志是在系统之间处理数据流的自然的数据结构。其中的秘诀非常简单,获取所有组织的数据,并将其放到一个用于实时订阅的日志中心。每一个逻辑数据源都可以用自己的日志作为模型。一个数据源可以是输出事件(比如点击、页面浏览)的应用,或者是可以修改的数据库表。每个订阅系统都尽可能快地从日志读取,应用每一条新记录到自己的存储系统中,并推进在日志中的位置。订阅者可以是任意一种数据系统——缓存、Hadoop、另一个站点的数据库、搜索系统等等。

img

例如,日志概念为每个变更提供了逻辑时钟,每个订阅者的状态都可以被探测到。这使得彼此间推断不同的订阅者状态变得简单,因为它们每个都有着自己的读取位置。

为了让讨论更具体,举个简单的例子,假使有一个数据库和几个缓存服务器,日志则提供了一种方式来同步更新到所有的系统,并且推断出每个系统所处的时间点。我们往日志写入了一条新记录,然后从缓存中读取,如果我们希望保证不读取到旧数据,我们需要确保不会从任何一个在复制上没跟上最新数据的缓存中读取。

日志也可以作为一种缓冲,使数据的产生和消费异步。有许多原因使得这点非常重要,尤其是当有多个订阅者以不同的消费速率进行消费的时候。这意味着订阅者可能崩溃或者停机维护时,并在恢复的时候赶上来:订阅者以自己控制的速度进行消费。一个批处理系统,如Hadoop或者是一个数据仓库,每小时或每天消费一次数据,但一个实时查询系统却可能是需要即时消费的。原始数据源和日志都不知道各种目标数据系统,因此消费系统可以在不做变更的情况下在管道中被添加进来或移除出去。

特别重要的是,目标系统仅仅知道日志的存在而不知道源系统的任何细节。消费系统也不需要去关注数据是来源于数据库管理系统、还是新型的键值存储系统,抑或是在没有任何实时查询系统的情况下生成的。这似乎是一个很小的知识点,实际上却非常重要。

在这里我使用了“日志”这个词而不是“消息系统”或者“发布订阅”,这里因为这在语义上是更加准确的,是对你在实现数据复制支持时所需要的事情的更加准备的描述。我发现“发布订阅”只是表达出了消息的间接寻址。如果你比较任意两个具有发布订阅功能的消息系统,你会发现它们承诺的是完全不同的事情,在这个领域里面,大多数模型都是没有用处的。你可以认为日志是一种有着持久化保证和强有序语义的消息系统。在分布式系统中,通信模型也叫原子广播。

值得强调的是,日志仍然是基础设施,它并不是掌握数据流故事的结束:故事剩余部分围绕着元数据、模式、兼容性和处理数据结构和深化的所有细节来展开。但是,除非有一种可靠的、通用的处理数据流的机制,否则语义的细节仍然是次要的。

在领英

随着领英从中心化的关系型数据库过渡到分布式系统,我注意到数据集成的问题也在逐渐演变。

目前我们的数据系统主要包括:

搜索、社交图谱、Voldemort(键值存储)、Espreso(文档存储)、推荐引擎、OLAP查询引擎、Hadoop、Terradata、Ingraphs(监控图表和监控服务)

以上这些都是特定的分布式系统,在特定的领域提供了高级的功能特性。

使用日志作为数据流的想法在我来到这里之前就已经出现了。我们开发的最早的一个基础设施之一是一个被称为databus的服务,它在我们早期的Oracle表上提供了一种缓存抽象,以扩展数据库更改的订阅,给我们的社交图谱和搜索索引提供输入数据。

我将简单介绍一些历史以提供讨论的上下文。在我们发布了自己的键值存储系统之后,大约是2008年,我开始参与这个项目。我的下一个项目是尝试部署配置一个可用的Hadoop,并将我们的推荐处理迁移到那里。由于缺乏这方面的经验,我们很自然的花了几周时间来完成数据的导入导出,剩下的时间则用来实现复杂的预测算法,就这样我们开始了长途跋涉。

我们最初的计划是将数据从现有的Orcale数据仓库中删除。但是我们首先发现快速将数据从Oracle中取出是一门黑暗的艺术。更糟糕的是,数据仓库的处理过程并不适用于我们为Hadoop设计的生产批处理,大部分的处理都是不可逆的,并且与要生成的具体报表相关。我们最终避免了数据仓库,转而直接使用源数据库和日志文件。最后,我们实现了另一个管道,将数据加载到键值存储,以提供结果。

这种简单的数据复制最终成为了原始开发的主要项目之一。糟糕的是,只要在任意时候管道内如果有问题,Hadoop系统也变得毫无用处——在错误的数据上运行了华丽算法只会产生更多的错误数据。

虽然我们使用了一种很通用的构建方式,但每一个新的数据源都需要自定义的安装配置。它也被证明是大量错误和失败的根源。我们用Hadoop实现的网站功能开始流行起来,而我们发现有大量对此感觉兴趣的工程师,第个人都许多想要集成的一系列系统,并且想要导入许多新的数据。

有些东西渐渐地清晰了起来。

首先,我们构建的管道,虽然有一些杂乱,但实际上是极有价值的。在一个新处理系统(Hadoop)上让数据变得可用的过程解锁了大量的可能性。在这些数据上做新的计算变得可能,这在过去是很难实现的。许多新产品和分析都来自于将以前锁定在特定系统的多种数据放在一起。

第二,很明显,可靠的数据加载需要数据管道的尝试支持。如果我们获取到了所需要的所有结构,我们可以让Hadoop数据加载变得完全的自动化,因此添加新的数据源或者处理模式的变更不再需要人工的介入——数据会自动的出现在HDFS中,Hive表将会自动地为新的数据源生成合适的列。

第三,我们的数据覆盖率仍然很低。如果你看一下,领英在Hadoop中所有可用的数据的百分比,你会发现它非常不完整。考虑到需要在新数据源上付出大量的努力,让数据变得完整这项工作并不容易。

我们一起在进行的,为每个数据源和目标数据构建一个个性化的数据加载很显然是不切实际的。我们有几十个数据系统和数据仓库,连接这些系统将会导致,在第两个系统之间构建一个个个性化的管道,就像这样:

img

需要注意的是,数据是双向流动的,就像许多系统一样(数据库、Hadoop),都需要在来源和目的地之前传输数据。这意味着我们最终在每个系统构建两条管道:一条用于获取数据,另一条用于流出数据。

很显然,这需要大批的人来做这个事情,而且是不可行的。当接近全连接时,我们将得到O(N2)条管道。

为了避免这个,我们需要像这样通用的东西:

img

我们需要尽可能地,将每个消费者从数据源中隔离开来。他们应当和一个数据仓库集成,并且可以从中获取所有的数据。

在这种方式下,添加一个新的数据系统——数据源或者数据目的地——可以仅仅通过连接到这单个管道上实现集成,而不需要跟每个每个数据消费者连接。

这种经历让我专注于构建Kafka,以将我们在消息系统中看到的内容与流行在数据库和分布式系统内部的日志概念相结合起来。我们冼想要一个可以作为中心管道给所以活动数据服务的东西,并最终用于其他许多用途,包括Hadoop之外的数据、监控数据等等。

在很长一段时间,Kafka是基础设施中独一无二的存在(有人说它很特别)——它不是数据库,也不是日志文件收集系统,更不是传统的消息系统。但最近Amzon提供了一种类似于Kafka的服务——Kinesis。相似之处包括分区处理的方式,数据的持有方式,还有分为高级及初级的消费者API这种奇怪的地方。对此我感到很高兴,你创建了一个好的基础设施的抽象的标识就是,Amazon将其作为了一种服务提供出去。他们对此的看法跟我描述的很相似,它是连接所有分布式系统的管道——RdynamoDB、RedShift、S3等等——同时了作为分布式流处理EC2的基础。

ETL和数据仓库的关系

让我们来谈下数据仓库,数据仓库是用于支持分析的干净的、集成数据结构的仓库。这是一个非常好的理念。对于不了解数据仓库概念的人来说,数据仓库方法涉及周期性的从源数据库中提取数据,将其转换为可理解的形式,并将加载存入中央数据仓库。集中拥有所有干净的数据的副本,对于数据密集型分析和处理而言是一项非常有价值的资产。在更高层面上,无论你使用传统的数据仓库Oracle,或者是Terada和Hadoop,这个使用方式不会有太大的不同,虽然你可能需要换一下加载和转换的顺序。

包含了清洗、数据集成的数据仓库是一项非凡的资产,但实现这个的机制有一些过时了。

对于一个以数据为中心的组织而言,关键问题是将干净的集成数据联合到数据仓库。数据仓库是批处理查询的基础设施,它非常适用于多种报表和临时分析,特别是当查询涉及到计算、聚合和过滤的时候。但如果批处理系统是唯一一个包含完成数据的仓库,将意味着数据不可用于实时的系统——实时处理、搜索索引 、监控系统等等。

在我看来,ETL包括了两件事情。首先,它是数据提取和清洗的过程,本质上是释放被锁在组织中的各类系统中的数据,并去除特定系统的约束。第二,数据被异构用于数据仓库查询(例如适配关系型数据库系统,强制使用星形或雪花型,可能打破原有的高性能的列格式)。同时做好这两件事情不容易。这些规整的数据仓库可以用于实时的或低延时的处理,也可以用于其他实时的存储系统中的索引。

我认为这具有使数据仓库ETL更加可扩展的额外的好处。典型的问题是,数据仓库团队要收集和处理其他团队的所有的数据。两边的收益是不对称的,数据的生产者常常并不知晓数据仓库中数据的使用,最终创建了很难获取或者转换成有用格式的数据。当然,中心团队也无法扩充以匹配于其他组织的发展速度,以致于数据的覆盖率问题质量不一的,数据的流向也是脆弱的,改进也是缓慢的。

一个更好的方法是,拥有一个有着为额外数据定义的良好API的中心管道,日志。集成这个管道并提供规整的、良好结构的数据由数据的生产者负责。这意味着,作为系统设计和实现的一部分,他们必须考虑获取数据和生成用于传输到中心管道的良好结构格式的数据这种问题。新的存储系统的加入对于数据仓库团队而言没有任何影响,因为它们具有集成的中心点。数据仓库团队仅仅需要处理从中心日志加载结构化数据这种简单的问题,并将特定的转换格式传输到它们的系统。

img

当一个人考虑采用传统数据仓库之外的其他数据系统时,组织上的可伸缩性变得尤为重要。例如,想在组织的完整数据上提供搜索的能力,或者想为实时趋势图的数据流提供监控和告警。在这些场景下,传统数据仓库的基础设施或者Hadoop都将变得不可用。更糟糕的是,被构建用于支持数据库加载的ETL处理管道也不再作用于其他系统,构建这些基础设施的工作量跟采用一个数据仓库一样大。这似乎是不可行的,这也解释了为什么大多数组织不具备他们在数据上的一些简单可用的能力。相反的,如果一个组织构建了统一的,良好结构的数据,那么让任意一个新系统完全获取所有的数据仅仅需要在管道上做一下集成的工作。

关于数据规整化和转换在哪里完成,这个架构了有着不同的观点:

  1. 在添加数据到公司的全局日志之前,由数据的生产者完成。
  2. 由日志的实时转换器完成(产生一个新的格式化的日志)。
  3. 由加载到目标数据系统进程的一部分完成。

最好的模型是数据发布到日志之前由数据的生产者完成数据的规整化。这意味着确保了数据处理标准形式,并且不需要包含任何由生成该数据的特定代码产生的或者存储系统维护的无意义的内容。这些细节可以很好地被生成数据的团队处理,因为他们是最了解他们数据的人。这个阶段所使用的任务逻辑都应该是无损的和可逆的。

任何可以实时完成的具有附加价值的转换都应该在原始日志上的后处理去执行。其中包括事件数据处理成会话,或者其他派生字段的添加。原始日志仍然是可用的,但实时处理将产生包含增强数据的派生日志。

最后,只有针对目标系统的聚合操作才应该被加到加载流程中。这些操作可能包括将数据转换为特定的星形或者雪花模式,用于在数据仓库中分析和报告。由于这个阶段,一般最自然的映射到传统的ETL流程,现在基于一种更规整和统一的数据流集去处理,因此也会更简单的。

日志文件和事件

让我们再聊聊这种架构的附加好处,它支持解耦的事件驱动系统。

在Web行为,获取活动数据的典型方式是将其记录到文件文件,这些文本文件可以被放到数据仓库或者Hadoop,用于聚合和查询。这存在的问题跟所有批处理ETL一样,它耦合了数据仓库数据流的能力和处理调度。

在领英,我们是以日志中心的方式构建的事件数据处理。我们正使用Kafka伪中心的、多订阅者的事件日志。我们定义了数百种事件类型,每种事件类型都有着特定类型操作的特定属性,这涵盖了从页面浏览、展示、搜索、到服务调用和应用异常等各种情况。

为了进一步理解这一种优势,想象一下有这么一种简单事件,在工作职位页面上展示职位信息。职位页面应该仅仅包含展示职位信息的逻辑。然而,在一个相当动态的网站上,这很容易变成额外的与工作无关的逻辑的点缀。例如,假设我们需要集成以下系统:

  1. 将数据发送到Hadoop和数据仓库,用于离线处理。
  2. 计算页面浏览量,确保浏览者不是内容爬虫的一种。
  3. 聚合浏览信息,并在职位发布者的分析页面展示 。
  4. 记录浏览信息,确保对用户的职位推荐合适进行展示(我们不希望一次又一次地展示相同的职位信息)。
  5. 推荐系统需要记录浏览,以便正确追踪职位的流行性。
  6. 等等

用不了多久,展示一个职位这种简单的动作就会变得十分复杂。当我们的职位要支持在其他终端上展示——手机应用等——这样的逻辑必须要延续下来,复杂度也将上升。更糟糕的是,我们需要与之交互的系统是错综复杂的,负责展示职位工作的人也需要了解其他系统和特性,并确保正确的集成。这里只是简单的描述了该问题,实际的应用将会更加复杂。

事件驱动风格提供了一种简化的方法。职位展示页面只负责展示职位并记录职位的相关属性信息,浏览的人或者关于职位展示的其他有用的信息。每一个其他对这个感兴趣的系统——推荐系统、安全系统、职位发布者分析系统以及数据仓库,都只需要订阅这些信息并且自己处理。展示的代码并不需要关注其他的系统,如果一个新的数据消费者被添加进来也不需要作出改变。

构建可伸缩的日志

当然,将发布者和订阅者分离开来并不是什么新鲜事。但如果你想维护一个提交日志,该日志作为消费者规模网站上所有活动的多订阅者实时日志,那么可扩展性将是一个主要的挑战。如果我们不能构建一个快速的、低成本的,足够可扩展的日志,使其在大规模上可行,那么使用日志作为统一集成机制不过是一个更美好的幻想。

人们普遍认为日志是缓慢的、高度抽象的(并且通常只把它与元数据类的使用方式联系在一起(Zookeeper))。但有了一个记录大数据流的深思熟虑的实现,打破了这一看法。在领英,目前我们通过Kafka每天写入超过600亿的消息(如果算上数据中心之间镜像的消息,这个数将是数千亿)。

我们在Kafka中使用了一些技巧,以支持这种规模:

  1. 日志分区
  2. 通过批量读写优化了吞叶率
  3. 避免了无意义的数据复制

为了支持水平扩展,我们将日志进行了分区:

img

每个分区都是一个完全有序的日志,但在分区之间没有全局的顺序(除非在你的消息中可能包含了墙上时钟时间)。消息被分配到一个特定的分区是由写入者决定的,大多数使用者会选择使用某种类型的值来分区(比如用户id)。分区允许日志追加,在分片之间无需协调即可发生,并且允许系统的吞吐量随着Kafka集群大小而线性扩展。

每个分区都可以通过配置副本数量进行复制,每个副本都有一个副本日志完全一样的拷贝。在任意时候,其中的一个副本将作为leader,如果leader发生故障,剩余的其中一个副本将接替成为leader。

在分区之间缺乏全局顺序确实是一个限制,但我们并未发现这是一个主要的限制。确实,与日志进行交互的通常是成百上千的独立线程,因为在这种情况下讨论它的全局顺序是没有意义的。代替的,我们保证了每个分区的顺序,并且Kafka保证了追加到特定分区的顺序将根据发送者发送的顺序进行传送。

日志,就像文件系统,对于顺序读写可以方便地优化。日志可以把小的读写合并成更大的、更高吞吐量的操作。Kafka致力于这种优化。这种批量操作存在于从客户端发送数据到服务端时,写磁盘时、服务间的复制、将数据传送给消费者时,以及确认提交的数据时。

最后,Kafka使用了简单的二进制格式维护内存日志、磁盘日志、网络中数据传输的日志。这允许我们可以使用包括“零拷贝”在内的大量优化机制。

这些优化的积累起来的效应就是,你通常可以以磁盘或网络支持的速率进行读和写,即使维护的数据集大大超出了内存的大小。

这篇文章并不是主要讨论Kafka的,在这里我不再讨论他们的细节。你可以通过领英的这篇文章和Kafka的设计这篇文章阅读更多的细节。

第三部分:日志和实时流处理

到目前为止,我只讲述了在系统之间拷贝数据的一种奇特的方式。但在存储系统之间传输字节并不是故事的结束。事实证明,日志是流的另一个说法,日志是流处理的核心。

但等会,什么是流处理?

如果你是上世纪90年代或21世纪初数据库文献或者成功了一半的数据基础设施产品的粉丝,那么你可以会把流处理与创建SQL引擎或者事件处理驱动处理的“盒子和箭头”界面相关联起来。

如果你关注开源数据系统的爆炸性增长,你可能会将流处理与这个领域下的一些系统相关联起来——如Storm、Akka、S4、Samza。但很多人会认为这是一种异步消息处理系统,与集群感知的RPC层不同(事实上,这些领域的一些东西确实如此)。

这些观点都有些局限。流处理与SQL并无关联,也不局限于实时处理。本质上,并没有什么理由不能使你不能用多种语言来处理昨天或一个月前的数据流来表达计算。

我将流处理视为更广泛的东西:不断用于数据处理基础设施。我认为计算模型可以像MapReduce或其他分布式处理框架一样通用,但他同时具有生产低延迟数据的能力。

处理模型的真正驱动力是数据收集的方式。数据被批量收集,也自然被批量处理。当数据不断地被收集,也自然而然地被不断地处理。

美国的人口普查给批量数据收集提供了一个很好的例子。人口普查会定期开始,通过挨家挨户的蛮力调查和统计美国公民。这在1790年人口普查刚开始的时候很有用。当时的数据收集是面向批处理的,它涉及到骑马走访并在纸上记录,然后将这一批记录运输到中央地方,在那里人为进行汇总。现在,当你描述人口普查过程时,会好奇我们为什么不维护出生和死亡的记录,并借此连续地或以任何所需要的粒度得到人口的数量。

这是一个极端的例子,但许多数据传输过程仍然依赖于定期的数据下载、批量的数据传输和集成。处理批量转储的方式自然是批处理。随着这些过程被连续的数据流所取代,人们自然会开始转向连续处理,以平衡处理所需要的资源,并减小延迟。

例如,在领英,几乎没有批量数据收集。大多数数据都是活动数据或者数据库的变更,这两种数据都是连续发生的。事实上,你想到任何业务,潜在的页面几乎都是一个连续的过程——实时发生的事件,正如Jack Bauer说的那样。当数据被批量收集时,几乎总是因为人为的步骤、或缺乏数字化、或非数字化过程自动化遗留下的历史问题。数据传输并对数据作出回应依赖于邮件或者人工处理时,通常是非常慢的。自动化的初步尝试问题保留了原始流程的形式,因此这种方式通常持续很长时间。

每天运行的生产“批量”处理作为通常是在模仿一种以一天为窗口大小的连续计算。底层数据当然问题在不断变化的。在领英,这实际上很常见(并且在Hadoop中使他们工作的机制非常复杂),我们实现了一个完整的框架来管理增量的Hadoop工作流程。

从这个角度看,很容易对流处理产生不同的认识:它仅仅处理包含了时间概念的底层数据,而不需要静态的数据快照,因此它可以根据用户控制的频率输出数据,而不是等待数据集到达后再产生输出。从这个角度上看,流处理是广义上的批处理,并且鉴于实时数据的流行,这种批处理变得非常重要。

那么,为什么传统的流处理只在小范围流行呢?我们认为最大的原因是实时数据收集的缺乏,使得连续处理更多停留在学术研究的层面 。

我认为,是否缺乏实时数据的收集,决定了商业流处理系统的命运。他们的客户仍然在使用基于文件的每天的批处理来完成ETL和数据集成。建设流处理系统的公司专注于提供流处理引擎来连接实时数据流,但结果是实际上那个时候很少人拥有实时数据流。事实上,在领英的早期职业生涯中,有一家公司向我们推销一个非常酷炫的流处理系统,但由于那时我们所有的数据都是小时被收集到文件上,我们能做到的最好使用场景是在每小时的最后把这些文件输入到流处理系统中,他们意识到这是一个普遍问题。这个例子实际上已经证明这个规律:在金融领域,流处理已经取得了一些成功,实时数据流已经成为了常态,而处理成为了瓶颈。

即使在拥有完善的批处理生态系统的前提下,我仍然认为流处理作为一种基础设施的应用也相当广泛。我认为它填补了实时请求/响应服务和离线批处理之间的基础设施缺口,对于现代互联网公司来说,我认为25%的代码可以划分到这个范畴。

事实证明,日志解决了流处理中的一些棘手的技术问题,正如我所描述的,但它解决的最大的问题是,使数据在实时多订阅者中变得可用。对于那些对更多细节感兴趣的人,我们开源了Samza,一个目标明确基于这些思想构建的流处理系统。我们在相关文档中,描述了大量这些应用的细节。

数据流图

img

流处理最有趣的方面与流处理系统的内部无关,而是与它如何扩展我们早期对输入数据的看法的有关。我们主要讨论了主数据的输入和日志——在多个应用的执行过程中产生的事件和数据行。但流处理也允许我们包含了由其它数据计算出的数据。对消费者而言,这些派生的数据与计算得到它们的原始数据没有什么不一样。这些派生的数据可以任意的封装组合。

让我们深入研究一下,出于我们的目的,一个数据流任务,是指从日志中读取数据,并将输出写到日志或其他系统。用于输入和输出的日志将这些处理流程连接成一个流程图。实际上,以这种方式使用中心化的日志,你可以将所有的组织数据图、转换和流向视为一系列写入它们的日志和流程。

流处理器根本不需要花哨的框架:它可以是读写日志的一个或一组处理过程,是可以用于帮助管理流程代码的额外的基础设施或支持。

在集成中,日志的目标是双重的。

首先,日志让每个数据集都支持多个订阅者,并且是有序的。回顾一下前端的“状态副本”,以便记住顺序的重要性。为了让这个更准确,假使有一个数据库的更新流——如果我们在我们的处理中对同一条记录的两条更新作重排序,我们可能会产生错误的最终结果。这种顺序比TCP等提供的更加持久,因为它不仅限于单点对点的链接,而且在处理失败和重新连接后仍然存在。

第二,日志为进程提供了缓冲。这一点非常基础。如果是以非同步的方式处理的,很有可能会发生上游数据生成作业的速度会下游作为消费的速度更快。当这种情况发生时,处理必须阻塞、缓冲或丢弃数据。丢弃数据可能不是一个可行的选择,阻塞可能会造成整个处理陷入停顿。日志充当了非常大的缓冲区,它允许进程重启或者失败,而不会造成其它处理流程中其他部分的处理缓慢。当将这种数据扩展到更大的组织时,这种隔离非常重要,处理是由许多不同的团队创建的作业进行的。我们不能让一个失败的作业造成背压,导致整个处理流的停止。

Storm和Samza都是以这种风格构建,并且可以使用Kafka或者其他同类型的系统作为他们的日志。

有状态的实时流处理

一些实时流处理只是无状态的记录转换,但许多用法更多侧重于流中窗口内的计数、聚合衙连接操作。例如,有人想要通过用用户点击的信息来丰富事件流(比如点击流)——实际上是将用户的点击流添加到用户数据库。通常,这种处理最终需要处理器维护某种状态:例如,当计数的时候,你需要维护当前的数量,如果处理器本身出问题了,这种状态要怎么维护正确呢?

最简单的做法是在内存中维护状态,然而如果处理器崩溃了,将会丢失中间状态。如果状态仅在窗口内维护,处理可以回退到日志中窗口开始的位置。然而,如果要在一个小时内计数,这可能是不可行的。

将所有状态简单地存储在远程存储系统中,并通过网络连接到该存储系统是一种可行的方法。这种方式的问题是缺少了数据的局部性,以及网络往返次数多。

如何支持像数据库表这样的东西,它可以跟我们的处理一样被分区?

当我们回顾表和日志的对偶性时,这给我们提供了可以将流转换为可以与处理共存的表的工具,同时也是一种解决表容错的机制。

一个流处理器可以在本地表或者索引维护它的状态——bdb、leveldb甚至是一些更加罕见的组件,比如Lucene或者fastbit索引。存储的内容来自于其输入流(可能首先应用了一些转换)。流处理器可以将其本地索引的变更日志记录下来,以便可以在崩溃和重启时恢复其状态。这种机制允许一个通用的方法来保持与输入流数据本地共分区的任意索引类型的状态。

当处理失败的时候,可以从变更日志中恢复。日志是每次备份时,本地状态到增量数据集的转换。

这种状态管理的方式的优雅之处在于,处理器的状态也作为日志来维护。我们可以将这个日志视为数据库表的更改。事实上,处理器同时维护了类似于联合分区表的东西。由于状态本身就是日志,其他处理器也可以订阅它。当处理的目的是更新最终状态,并且状态是处理的自然输出时,这实际上是非常有用的。

当出于数据集成的目的,将源于各数据库的日志级合起来时,日志和表的二象性的强大就变得明显了。变更日志可能是从数据库中提取而来的,并被不同的处理器异构成不同格式的索引,以跟事件流进行连接。

我们将在Samza中详细介绍这种状态处理的管理方式,这里有一些实践的示例。

日志压缩

img

当然,我们不可能保存所有时间段内所有状态变更的完整的日志。除非想要使用无限的空间,否则日志是必须要清理的。我将具体谈一些关于Kafka在这方面的实现。在Kafka中,清理有两种策略,这取决于数据是否包含键或者事件数据。对于事件数据,Kafka支持保留一个时间窗口的数据。通常,这个时间窗口被配置为几天,但这个窗口也可以基于时间或空间定义。对于有键值的数据,完整日志的一个很好的方面是你可以通过重放日志的方式来重建源系统的状态(可能在另一个系统中重建)。

然而,保留完整的日志会随着时间的推移,占用起来越多的空间,重放也将花费起来越多的时间。然而,在Kafka中,我们支持另一种保留方式。代替简单地丢弃老的日志,我们删除淘汰的记录——比如那些主键有了更新的记录。通过这种方式,我们仍然能保证日志包含源系统的完整的备份,虽然不再能重建源系统的所有旧的状态,而是保留了最新的状态。我们称这个特性为日志压缩。

第四部分:系统构建

我最后想讨论的是日志在在线数据系统的数据系统设计中扮演的角色。

这里有一个类比,日志在分布式数据库的数据流中扮演的角色和和它在一个更多的组织的数据集成中扮演的角色。在这两种情况下,它都是负责数据流、一致性和恢复。毕竟,一个组织如果不是一个复杂的分布式数据系统,那它是什么呢?

分解?

如果你换个角度,你可以将整个组织的系统和数据流看成是一个整体的分布式系统。你可以将所有的独立的面向查询的系统(Redis、SOLR、Hive表等等)看作是你的数据的专门的索引。你还可以将流处理系统看作是一个成熟的触发器和视图实现机制。我注意到传统的数据库人群非常喜欢这种观点,因为他们终于能解释通,所有的这些不同的数据系统到底是用来做什么的——他们仅仅是不同的索引类型。

不可否认的是,数据库类型正爆发式增长,但实际上,其复杂性一直存在。即便在关系型数据库的盛行的时期,组织也拥有大量的关系型数据库。因为大型机,所有的数据实际都存放在同一个位置,所有可能并不存在真正的数据集成。将数据独立拆分到多个系统有许多原因:数据伸缩性、地理位置、安全性以及性能隔离是最常见的。但这些都可以通过一个好系统来解决:组织通过单个Hadoop集群保存所有的数据,并服务于众多的多样化的用户群体是可能。

在转向分布式系统的过程中,已经有了一种可能的数据处理简化:将大量的各个系统的小实例聚合到一个大的集群里。许多系统不足以做到这些:它们缺乏安全性、或不能保证性能隔离、或不具备良好的伸缩性。但每个问题都是可以解决的。

在我看来,不同系统大量涌现的原因是构建分布式数据系统的困难性。通过将系统裁减到一个单一的查询类型或用例上,每个系统都可以回到自己范围内可以做到的事情上来。但运行所有这些系统还是太复杂了。

未来可能会有3种可能的方向。

第一种可能性是现状的延续:系统之间的隔离在往后很长的一段时间内都将保持不变。这可能是因为分布式系统构建难度太大,或者是因为这种专业化使得每个系统的便利性和功能水平达到一个新的高度。只要这种情况持续下去,数据集成问题将是数据成功使用中最重要的问题之一。在这种情况下,用于集成数据的外部日志将非常重要。

第二种可能性是统一合并一个具有足够通用性的系统,开始将所有不同的功能合并到一个单独的超级系统中。这个超级系统从表面上看可能像一个关系型数据库,但在组织中的使用将大大不同,因为你只需要一个大的系统而不是无数个小的系统。在这样的世界里,除了系统内部不存在真正的数据集成问题。我认为,构建这样的一个系统在实践上的困难性使得其不太可能发生。

还有另外一种可能的结果,作为一个工程师,我觉得它很有吸引力。新一代的数据系统最有趣的一方面是它几乎都是开源的。开源创建了一个可能性:数据设施可能被分解成一系列的服务和面向应用的系统api。你已经在Java的技术栈上看到了,正一定程序地出现这种情况:

  • Zookeeper处理了系统协调的很多问题(或者有来自于像Helix或者Curator一些的高级抽象)。
  • Mesos和YARM处理了虚拟化和和资源管理。
  • 像Lucene和LevelDB的嵌入式类库充当了索引。
  • Netty、Jetty和像Finagle和rest.li这种高级别的封装处理远程的通信。
  • Avro、Protocol Buffers、Thrift等等其他的类库处理了序列化。
  • Kafka和Bookepper提供了备份日志。

如果你把上面的这些放一起,稍微观察一下,会发现它看起来就像是分布式数据工程中的“乐高”版本。你可以将这些零件放在一起,创造大量的可能的系统。这显然不是一个与终端用户相关的故事,他们可能更多关心api而不是实现,但在一个更加多样化和模块且持续演进的世界中,这可能是一条通往实现单个系统的简单性的一条路径。如果因为可靠的、灵活的构件出现,而使得分布式系统的实现时间从年缩减到周,聚合形成单一的整体的系统的压力将会消失。

日志在系统架构中的地位

假使存在外部日志的系统允许每个系统抛弃自身的复杂性,转而依赖于共享日志。以下是我们认为日志可以做的事:

  • 通过对节点并发的更新进行排序,处理数据一致性(无论是最终的还是实时的)。
  • 在节点之间提供节点的副本。
  • 为写入者提供“提交”的语义(比如,只有在你的写入被保证不会丢失的情况下确认)。
  • 提供给外部的本系统数据的订阅。
  • 对于丢失数据的失败的副本或者新的副本,提供可恢复的能力。
  • 在节点之间处理数据的重平衡。

这实际上是分布式系统所做工作中的一个重要部分。实际上,剩余的大部分工作都与最终面向客户端的查询api和索引策略有关。这确实各个系统之间应该有所不同的部分:例如一个全文检索可能需要查询所有的分区,但通过主键的查询可能仅仅需要查询一个为该键服务的单个的节点。

这是它的工作原理。系统被分为两个逻辑部分:日志和服务层。日志顺序记录了状态的变更。服务节点存储了需要为查询服务的索引(比如,一个键-值存储系统可能有像btree或者sstable的事物、一个有着倒排索引的搜索系统)。写操作可能是直接写日志,虽然他们可能被服务层代理。写日志会产生一个逻辑时间戳(日志中的索引)。如果系统被分区了,日志服务节点将有相同的分区数量,虽然他们可能有不同的机器数量。

img

服务节点订阅日志,并且尽可能快地以存储的顺序应用更新到它的本地的索引。

客户端可以提供写入的时间戳作为查询的一部分,从任意节点获得“读取你写入的”的语义——接收到该查询请求的服务节点,将对所需的时间戳和本地的索引进行比较,必要时会延长请求,直到索引至少抵达该时间点,以避免提供了旧的数据。

服务节点可能会或可能不会感知master身份或leader选举。对于许多简单的用例,服务节点可以完全不需要leader,因为日志是事实之源。

分布式系统中必须要做的更棘手的事情之一是处理恢复失败的节点或者将分区从一个节点转移到另一个节点。一个典型的方案是,仅保留一个固定窗口的数据,并把这个数据和分片中存储的快照结合起来。同样地,也可以保留数据完整备份的日志,并自行进行垃圾回收。这把特定系统的服务层的大量的复杂性移到了通用的日志层。

有了日志系统,你将得到了一个针对数据存储内容的成熟的订阅api,通过通过ETL传输到其他系统。

img

注意,这样一个以日志为中心的系统是怎么立即成为在其他系统中处理和加载的数据流的提供者呢?同样的,一个流处理器可以消费多个输入流,又通过索引其输出的另一个系统服务于他们。

我发现这种对系统分解为日志和查询api的观点非常具有启发性,因为它可以使查询特性从系统的可用性和一致性中脱离出来。我实际上认为,这是一种通过分解系统来理解它们的一种很有用的一种方式,虽然系统并不是以这种方式构建的。

值得一提的是,虽然Kafka和Bookeeper都是一致性日志,但不是必须的。你可以简单将一个Dynamo之类的数据库分解为一个最终一致性的AP日志和一个键-值服务层。这样的日志用起来很灵活,因为它会重传旧消息,并依赖于订阅者来处理(很像Dynamo做的)。

在日志中保留一个单独的副本(特别是完整的副本),在许多人看来是浪费的。事实上,有几个因素使得这个不是一个大问题。首先,日志可以是一种非常高效的存储机制。我们在我们生产的Kafka服务上的每个数据中心都存储了超过75TB的数据。同时有许多服务系统需要更多的内存来高效的服务数据(例如,文本搜索通常问题在内存中进行)。服务也使用优化过的硬件。例如,我们大多数运行中的数据系统基于内存提供服务,或使用固态硬盘。相反,日志系统只进行线性读写,因此它非常乐意使用大型多TB的硬盘。最后,正如上图描述,数据被服务于多个系统,日志的成本也在多个索引中分摊。上面几点使得外部日志的开销相当小。

这正是领英用来构建许多实时查询系统的模式。这些系统从数据库中获取数据(使用Databus作为日志抽象,或者从Kafka中真正的日志中获取),并且在数据流的基础上,提供特定的分区、索引和查询能力。

这是我们实现了搜索、社交图谱和在线分析查询系统的方式。实际上,将单一的数据流复制到多个服务系统用于在线服务是非常通用的做法。事实证明,这是一个极大的简化的假设。这些系统不再需要提供外部写入的api,Kafka和数据库被用于记录系统和通过日志流向恰当的系统的变更流。写入操作在本地被特定分片的节点处理。这些节点机械地将日志提供的数据流记录到自己的存储系统。失败的节点可以通过重放上游的日志来恢复。

这些系统依赖日志的程度各不相同。一个完全可靠的系统可以利用日志作为数据分区、重平衡、一致性的方方面面,以及数据传播。在这样的架构中,服务层保不过是一种缓存,以支持直接写入到日志的特定处理。

最后

如果你从头一起读到了这里,那么我对日志的理解,你大部分都知道了。

以下是一些你可以想要查阅的有趣的相关资料。

人们会用不同的术语来描述相同的事物,当你从数据库系统到分布式系统、从各企业级的应用软件到开源世界,会感到疑惑。无论如何,在大方向上是有一些共同之处的。

学术论文、系统、讨论和博客:

  • 关于状态机主从备份的概述。
  • PacificA是微信用于实现基于日志的存储系统的通用框架。
  • Spanner——并不是每个人都喜欢把逻辑时间用于他们的日志。谷歌的最新的数据库学工使用物理时间,并通过把时间戳直接作为区间来直接对时间漂移的不确定性进行建模。
  • Datanomic解构数据库是Rich Hicky(Clojure的创建者)在它的首个数据库产品的重要陈述。
  • 在消息传递系统中对方回滚和恢复的调查,我发现这是错误容忍的非常有帮忙的介绍,也是通过日志恢复外部数据库的实际应用的不错的介绍。
  • 反应式宣言。我其实并不清楚反应式编程的确切涵义,但是我想它和“事件驱动”指的是同一件事。 这个链接并没有太多的信息,但 Martin Odersky (Scala大拿)讲授的这个课程很精彩。
  • Paxos!
    • 原论文在这里。关于作者 Leslie Lamport 发表的这篇论文有个有趣的历史:他在80年代就发明了这个算法,但直到1998年才发表出论文,原因是评审组不喜欢论文中的希腊寓言,而他又不愿修改。
    • 甚至于论文发布以后,人们还是不怎么理解。Lamport 再次尝试,这次甚至包含了一些关于如何在新型自动化计算机上使用的“无趣的细节”。 但算法仍然没有得到广泛的理解。
    • Fred SchneiderButler Lampson分别给出了更多细节关于在实时系统中如何应用Paxos。
    • 一些谷歌的工程师总结了他们在Chubby中实现Paxos的经验
    • 我发现所有关于Paxos的论文理解起来很痛苦,但是值得我们费大力气弄懂。你不必忍受这样的痛苦了,因为日志结构的文件系统的大师John Ousterhout这个视频 让这一切变得相当的容易。这些一致性算法用展开的通信图表述的更好,而不是在论文中通过静态的描述来说明。颇为讽刺的是,这个视频录制的初衷是告诉人们Paxos很难理解。
    • 使用Paxos来构造规模一致的数据存储。这是一篇很棒的介绍使用日志来构造数据存储的文章,Jun 是文章的共同作者之一,他也是Kafka最早期的工程师之一。
  • Paxos有很多的竞争者。以下可以更进一步的映射到日志的实施,更适合于实用性的实施。
    • 由 Barbara Liskov 提出的视图戳复制是直接进行日志复制建模的较早的算法。
    • Zab是Zookeeper所使用的算法。
    • RAFT是易于理解的一致性算法之一。由John Ousterhout讲授的这个<a href = “ “>视频非常的棒。
  • 你可以的看到在不同的实时分布式数据库中动作日志角色:
    • PNUTS是探索在大规模的传统的分布式数据库系统中实施以日志为中心设计理念的系统。
    • HbaseBigtable都是在目前的数据库系统中使用日志的样例。
    • LinkedIn自己的分布式数据库Espresso和PNUTs一样,使用日志来复制,但有一个小的差异是它使用自己底层的表做为日志的来源。
  • 如果你正在做一致性算法选型,这篇论文会对你所有帮助。
  • 复制:理论与实践,这是收录了分布式系统一致性的大量论文的一本巨著。网上有该书的诸多章节(145678)。
  • 流处理。这总结起来太广泛了,以下是一些我喜欢的:

企业级软件有着同样的问题除了名称不一样、更小的规模、XML。哈,开个玩笑:

  • 事件溯源:据我所知,这基本是企业软件工程师说“状态机复制“的方式。很有趣的一点是,同样的想法在不同的情景中被又一次发明出来。事件溯源似乎聚集于更小的、在内存中的用例。这种应用开发的方式似乎是将出现在事件日志中的流处理和应用结合起来。当处理足够大而需要数据分区进行扩容时,这变得相当复杂,我重点关注于流处理作为一个独立的原始的基础设施这一方面。
  • 变更数据捕获:这是围绕从数据库获取数据的一个小产品,这也是数据提取的最友好的日志风格。
  • 企业应用集成似乎在解决数据集成问题,当你有像CRM或者供应链管理软件的企业软件时。
  • 复杂的事件处理:相当肯定的是没有人知道这是什么,它是怎么区别于流处理的。不同之处似乎是关注点在于无序流、事件过滤和发现,而不是聚合上,但在我看来,区别并不明显。我认为任何系统都有其优秀的地方。
  • 企业服务总线:我认为企业服务总线的概念跟我在数据集成描述的非常相似。这个理念似乎在企业软件社区中取得了一定的成功,但在Web开发人员或分布式数据基础设施人群中鲜为人知。

有趣的开源组件:

  • Kafka是”日志即服务“的项目,是这篇文章的基础。
  • BookeeperHedwig是”日志即服务“的另外的开源组件。它们似乎致力于数据系统内部,而不是事件数据。
  • 数据总线是为数据库表提供了类似日志层的系统。
  • Akka是一个Scala的Actor框架,它有一个插件,事件溯源,提供了持久化和记录的功能。
  • Samza是一个我们在领英工作中使用的流处理框架。它使用了这篇文章中大量的理念,也集成了Kafka作为底层日志。
  • Storm是一个流处理框架,可以很好的跟Kafka集成。
  • Spark Streaming是Spark中的流处理框架。
  • Summingbird是在Storm和Hadoop上提供了方便的计算抽象。

我们持续关注这一领域,如果我有一些遗漏的地方,请告知。

最后我给你留下的信息是:视频