原文:点击打开链接
自己翻译的,发现网上已经有中文版了…。
有些地方是意译,理解不到位的地方还请包涵。
我于六年前在一个特别有趣的时间点加入LinkedIn,那时候我们正面临单片、中心化数据库的限制,并需要开始向专门的分布式系统组合转型。这是一个有趣的经历:我们构建、发布、并运行至今一个分布式图形数据库,一个分布式搜索后端,一个Hadoop应用,和一个一代、二代key-value存储系统。
从中我学到的最有用的一件事是,我们构建的许多系统,其核心都有一个非常简单的概念:日志。有时候被称作“前写日志”,或“提交日志”,或“事务日志”。日志伴随计算机系统而存在,也是许多分布式数据系统和实时计算应用架构的核心。
不理解日志,你就无法透彻的理解数据库,NoSQL存储,key-value存储,复制,paxos算法,Hadoop,版本控制(version control),或几乎所有软件系统。但很多软件工程师却并不熟悉它们,我希望改变这个现状。在这篇文章里,我将向你展示你必须了解的有关日志的一切,包括什么是日志,及如何在数据集成、实时计算和系统构建中使用日志。
第一部分:什么是日志?
日志可能是最简单的存储抽象。它是只增的,严格按照时间顺序排列的记录序列,看起来就像:
记录被添加到日志的结尾,从左到右依次读取。每个条目都被分配一个唯一的连续的日志编号。
记录的顺序定义了“时序”的概念,因为左边的条目被定义为早于右边的条目。条目的日志编号可以认为是条目的“时间戳”。将此种顺序描述为时间流的概念,一开始看起来有点古怪,但是它具备同任何特别的物理时钟解耦的方便特性。在我们接触到分布式系统时,该特性将变得十分必要。
记录的内容和格式对于本文讨论的目的来说不重要。同时,因为存储空间的限制,我们不能一直不停的添加记录到日志。稍后我们还会讲到这个问题。
日志与文件或表并不是那么的不同。文件是字节的一个数组,表是记录的一个数组,而日志本质上就是其记录按照时间排序的文件或表。
此时你可能在想:如此简单的事情有什么值得讨论的?一个记录的只增序列怎么会跟数据系统有关系?答案是,日志有个明确的用途:记录何时发生了何事。对于分布式系统来说,在很多情况下,这就是问题的核心。
在我们扯远之前,让我先来澄清一件有点让人混淆的事情。每个程序员都很熟悉日志的另外一种定义——应用可能使用log4j或syslog写入本地文件的非结构化的错误信息或追踪信息,为了明晰起见,我叫这个做“应用日志”。应用日志是是我所描述的的日志概念的退化版本。最大的不同是:文本日志应该主要提供给人阅读,而我所描述的“日志”或“数据日志”则为程序可读而建。
(实际上,你想想看,由人来通读个别机器上的日志的想法本身就与时代不符。当很多服务和系统牵涉其中,日志的用途很快就变成了查询和图形化理解机器间行为的输入,这种做法很快就变成无法管理的策略——文件中的文本记录完全不如此处描述的结构化日志合适。)
数据库中的日志
我不知道日志概念起源何处——可能它就像二进制搜索一样太过简单,以至于发明者根本没有意识到它是一项发明。在最早的IBM R系统(IBM's System R)中就存在日志了。使用数据库系统必须要在系统存在崩溃可能的情况下,处理不同类型数据结构和索引的同步。为了使该操作原子化和持久化,在将变更应用到数据库维护的所有不同数据结构之前,数据库使用日志记录将要被修改的记录的信息。日志是事件发生的记录,每个表或索引都是这个历史记录到某种有用数据结构或索引的投影。由于日志是立即持久化的,因此它被用作在系统崩溃后恢复所有其他持久化结构的权威源头。
随着时间的推移,日志被更多的使用,从ACID的实现细节,到数据库之间的数据复制方法都用到了日志。事实证明一个数据库上发生的变更序列,恰恰是需要保持同步的远端镜像数据库所需的。Oracle、MySQL和PostgreSQL引入日志传输协议,将日志块传输给作为slave的镜像数据库。Oracle还把日志产品化为一个通用的数据订阅机制,这样非Oracle数据订阅用户就可以使用XStreams和GoldenGate订阅数据了,MySQL和PostgreSQL上的类似的实现则成为许多数据结构的关键组件。(后一句参考了网上的另一篇翻译)
因为这个缘起,机器可读日志的概念在很大程度上被限于数据库内部。使用日志作为数据订阅机制的兴起看起来似乎有点偶然。但是这种高度抽象对于支持所有类型的消息、数据流、实时数据处理来说很理想。
分布式系统中的日志
日志解决的两个问题:顺序化变更和分发数据,在分布式数据系统中尤为重要。对一组更新序列达成一致(或对否决达成一致,并应对副作用)是这些系统设计的核心问题之一。
以日志为中心的分布式系统源起于对一个准则的解读,我将这个准则称为“状态机复制准则”:
如果两个同样的,确定的进程开始于同样的状态,并且以相同的顺序获得相同的输入,它们将产生一致的结果,并以相同的状态结束。
这看起来有点绕,让我们来深入理解一下它的含义。
确定性意味着程序处理不是时间依赖的,并且不会让任何“越界”的输入影响结果。举例来说,如果一个程序的输出依赖执行线程的特别顺序,或调用类似gettimeofday等其它不可重复获取值的方法,一般最好认为它是不确定性的。
状态是程序处理结束后,机器
内存中或磁盘中留存数据的任何形式。
以相同的顺序获取相同的指令这点应当引起你的注意——这就是引入日志的地方。这是一个非常直觉的观念:如果你给两个确定性的程序片段提供相同的日志输入,它们将产生相同的输出。
分布式计算的程序设计是显而易见的,通过实现分布式一致性日志,并将其作为处理相同问题的复数机器的输入,来分解问题。在这里,日志的目的是将所有的不确定性从输入流中“挤”出去,来确保每个处理该输入的复制进程保持同步。
当你理解了这点,就没有关于该准则更复杂或深入的内容了:它差不多是说“确定性的处理是确定性的”,尽管如此,我认为它是设计分布式系统的更加通用的工具之一。
这种方式很美好的一个地方是:索引日志的时间戳作为复制状态的时钟——你可以通过一个简单的数字描述每个复制机,这个数字来自复制机已经处理的最大日志条目的时间戳。这个时间戳和日志组合起来,唯一地捕获了复制机的完整状态。
根据日志中写入的内容不同,有大量的方法在系统上实现该准则。比如,我们可以记录到达的对服务的请求,或者服务为了做出响应而执行的对状态的修改,或者服务执行的转换命令。理论上,我们甚至可以记录在每个复制机上执行的机器指令,或方法名及参数序列。只要两个进程以同样的方式处理这些输入,复制机群间的处理进程(状态)就能够保持一致。
不同的人群对于日志的用途有不同的描述。数据库用户一般区别对待物理日志和逻辑日志。物理日志是记录被变更的每一行的内容,逻辑日志是记录导致行数据变更的SQL(INSERT、UPDATE、DELETE语句)。 --xingrk译注:行复制、逻辑复制;
分布式系统文献大多将两个宽泛的模式区分为处理和复制。“状态机模型”一般是指“
主动-主动模型
”,在此模式下我们维护一个到达请求的日志,每个复制机处理每个请求(?)。该模式的一个修改版本,称作“主动-备动模型”,是选举一个复制机作为主机,并让主机按照请求到达的顺序处理它们,同时记录因处理请求引起的状态变化日志。其它复制机顺序应用主机产生的状态变化,从而保持同步,并准备作为新的主机接管可能失效的当前主机。
为了理解这两种方式的不同,想象一个做镜像的“算法服务”,它维护一个值及其状态(初始化为0),并对该值应用加法和乘法操作。“主动-主动模式”会记录所应用的转换操作,如“+1”,“*2”等。每个复制机都将应用这些转换操作,因此会经历同样的值变化。“主动-被动模式”会让一台主机执行转换操作并记录结果,如“1”,“3”,“6”等(译注:复制机直接同步结果)。这个例子还说明了为什么顺序是复制机之间保证一致性的关键:重排一个加法和乘法操作将导致不同的结果。
分布式日志可以看成是对共识问题建模的数据结构。日志代表了对“下一个”添加值的一系列决定。必须仔细分析才能从Paxos算法族中看到日志的存在,尽管构建日志是它们最常见的应用实践。根据Paxos算法,这通常是使用叫做“multi-paxos”的协议的一个扩展来实现,它将日志建模为一系列的共识问题,一一对应到日志中的每个槽位。在ZAB、RAFT或Viewstamped Replication等其它协议中,日志的作用更加突出,是直接对维护分布的一致性日志问题进行建模的。
我的怀疑是,因为历史的原因我们对问题的看法有些偏颇,可能要归因于近几十年分布式计算理论的发展比实践应用要快。现实中的共识问题有点太简单。计算机系统很少需要决定一个单一的值,通常都需要处理一个请求队列。因此,相对一个简单的单值寄存器,日志是更自然的抽象。
此外,对于算法的关注模糊了系统需要的底层日志抽象。我怀疑我们最终会更加关注日志作为一个商品化的构建模块而忽视它的实现,就像我们经常谈论哈希表却不愿深入底层去了解我们随口说的哈希是线性探查或其他变体(hash算法实现?)。日志成为大众化的接口,会有多种算法和实现竞相提供最好的保证和最佳的性能。
变更日志101:表与事件的二相性
让我们再来看看数据库。数据库中的变更日志和表有着很好的二相性。日志就像是银行处理的信贷和借记列表;表是所有当前帐户的结余。如果有变更日志,通过顺序应用变更操作,
就可以创建表
来捕获当前系统的状态。表会记录每个键值最后的状态(来自某个特定的日志时间)。一种看法是,日志是更基础的数据结构:除了创建原始表,还可以对其转换以创建所有类型的衍生表(是的,在非关系型数据库分支中,表也可以是键值存储)。
这个流程也是可逆的:如果有一个表接受更新操作,可以记录这些变更并发布一个包含了所有对表的状态做更新的操作的
“变更日志。”这个日志正是用来支持准实时复制所需要的。因此从这个观念当中可以看到表和事件是二相的:表维护静态的数据,而日志捕获变化。日志的灵活性体现在,如果它是一个完整的变更日志,那么它不仅持有最终版本的表的内容,还能够支持重建所有其它可能存在的表的版本。这是对表的每一种历史状态的有效备份方式。
这可能会让人想起对源代码的版本控制。在源代码控制和数据库之间有着很强的联系。版本控制解决了跟分布式数据库系统必须解决的问题相类似的问题——管理分布的、并发的状态变化。版本控制系统通常会以补丁序列进行建模,实际上就是日志。跟表类似,我们直接跟检出的当前代码的“快照”进行交互。注意到在版本控制系统,及其它分布的有状态的系统中,复制通过日志完成:当执行更新的时候,将补丁拉下来并应用到系统当前快照上。
最近一些人可能从Datomic公司(销售一个以日志为中心的数据库产品)看过一些相关的理念。这个报告给出了他们是如何将这些理念应用到他们的系统中的概述。当然,这些理念不单单只适用于这个系统,就像它们作为分布式系统和数据库系统文献的一个部分数十年之久。
这些看起来有点过于理论化。不要失望!很快我们就会接触到应用的部分。
接下来
在本文剩下的部分,我将尝试通俗的说明当涉及到跨越分布式计算或抽象的分布式计算模型内部时,日志是如何发挥作用的。这包括:
1、数据集成——让组织的所有数据在其所有存储和处理系统之间方便的共享;
2、实时数据处理——计算获取数据流;
3、分布式系统设计——以日志为中心的设计是如何简化可行的系统的;
这些都是围绕着将日志作为独立服务的理念来确定的。
在每个部分,日志系统从提供最简单的记录开始:生成持久的,可重放的历史记录。意外的是,这些问题的核心是让多台服务器以它们各自的速度和确定性的方式重放历史记录的能力。