第1章 导言
这是一本关于日志的书。为什么有人会写这么长的文章来讲日志?事实证明,朴素的日志是一种抽象,它是从NoSQL数据库到加密货币的各种系统的核心。除了可能偶尔查阅一下日志文件外,大多数工程师对日志都没有太深的思考。为了解决这个问题,我会大致地描述日志在分布式系统中的工作方式,然后将这些概念实际应用于一些常见用途:数据集成,企业系统架构,实时数据处理以及数据系统设计。我还将谈论我在LinkedIn工作期间,有关数据基础结构系统的工作中,将其中一些想法付诸实践的经验。但是在正式开始之前,我还是要解释一下你可能已经了解的内容。
什么是日志?
当大多数人想到日志时,他们想到的可能会是图1-1所示的样子。
图1-1.Apache日志的摘录每个程序员都对这种日志很熟悉——一系列松散的结构请求,错误或其他消息(一系列旋转文本文件)。
这种日志是我将要描述的“日志”概念的退化形式。最大的区别是这类应用程序日志主要供人类阅读,而我将介绍的日志同时还需要供程序来访问。
实际上,如果你细思一下就会发现,由人类在单台机器上阅读日志的想法并不好。当涉及许多服务和服务器时,这种方法很快就会变得难以持续。日志的作用很快会变成根据输入查询或展示图形,以便了解更多机器上的行为,此时,日志文件中的英文文本并不像我将要讨论的那种结构化日志那样合适。
我想讨论的日志是一种更为通用,并且更接近数据库或系统领域中可能称为“commit log(提交日志)”或“journal(通报)”的概念。它是按时间排序的只可追加的记录序列,如图1-2所示。
图1-2 结构化日志(记录从0开始编号,根据它们的写入顺序)每个矩形代表一个附加到日志的记录。记录按其添加顺序存储。读取从左到右进行。附加到日志的每个条目都分配有一个唯一的顺序日志条目编号,该编号是它的唯一键。记录的内容和格式在这里并不重要。具体来说,我们可以想象每个记录都是一个JSON Blob,当然,任何数据格式都可以。
记录的顺序定义了“时间”的概念,因为左侧的条目定义为比右侧的条目更早。日志条目编号可以认为是条目的“时间戳”。将这种顺序描述为时间概念起初似乎有些奇怪,但是它具有与任何特定物理时钟解耦的便利特性。当我们讨论分布式系统时,此特性将变得至关重要。
一个日志与一个文件或一张表并没有什么不同。文件是字节数组,表是记录数组,日志实际上就是一种表或文件,只是其中的记录要按时间排序。
与我之前展示的Apache日志相比,你可以看到:两者都是仅追加记录的序列。但是,重要的是我们将日志视为抽象数据结构,而不是文本文件。
基于这一点,您可能会想:“这么简单的事有什么值得谈的?” 以何种方式追加记录序列与数据系统有什么关系?答案是日志有特定的用途:它们记录发生的情况和时间。对于分布式数据系统来说,从很多方面看,这都是问题的核心。
数据库中的日志
我不知道日志概念起源于何处——可能类似于二进制搜索,发明它的人可能会觉得太过简单,以至于无法意识到这是一项发明。它早在IBM的R系统中就已存在。在数据库中的应用与出现崩溃时保持各种数据结构和索引的同步有关。为了让其具有原子性和持久性,数据库会在将一次更改应用到任何数据结构之前,使用日志写出即将修改的记录的有关信息。日志就是发生过的事情的记录,每个表或索引都是这些历史记录对数据结构以及索引的投射。由于日志会即可生成,因此在发生崩溃时,它将用作还原所有其他持久性结构的权威来源。
随着多年的发展,日志的使用从ACID数据库(atomicity 原子性,consistency一致性 ,isolation隔离性和durability持久性)的特性实现细节发展为在数据库之间复制数据的方法。事实证明,数据库中发生的更改顺序正是保持远程副本数据库同步所需的。Oracle,MySQL,PostgreSQL和MongoDB都包含日志传送协议,来将日志的一部分传输到从属的副本数据库中。然后,从服务器中将日志所记录的更改应用于本地数据结构,以保持与主服务器同步。Oracle已将日志作为非Oracle数据订阅者的XStreams和GoldenGate产品的通用数据订阅机制,使其产品化,并且MySQL和PostgreSQL中也存在类似的功能。
事实上,本书其余大部分内容中日志的使用方式都是数据库中的两种用法的变体:
- 日志用作发布/订阅机制,以将数据传输到其他副本
- 日志用作一致性机制,用于订阅要应用于的更新多个副本
也许是由于起源于数据库内部,机器可读日志的概念尚未广为人知,尽管正如我们将看到的那样,这种抽象是支持各种消息传递,数据流和实时数据处理的理想选择。
分布式系统日志
对于所有分布式系统,最基本的问题之一就是数据库日志解决的相同问题(例如,将数据分发给副本并同意更新顺序)。
以日志为中心的分布式系统方法源于一个简单的观察,即我称之为“状态机复制”的理论:
如果两个相同的确定性过程,以相同的状态开始,并以相同的顺序获得相同的输入,则它们将产生相同的输出并以相同的状态结束。
这似乎有点难理解,所以让我们深入了解它的含义。
确定性 意味着操作不依赖于时序,同时也不允许任何其他影响其结果的输入存在。例如,以下情况就符合不确定性的模型:一个多线程程序,其输出取决于线程的执行顺序;或者一个程序受对gettimeofday()函数的调用结果影响,或依赖其他不可重复的源输入。当然,这些东西是否真的是确定性的,更多情况还和物理因素有关。对我们而言没什么关系,我们对它们的状态和输入不足够了解,还无法将它们的输出建模为适当的数学函数。
进程的状态指的是操作完成后保留在计算机内存或磁盘中的任何数据。
关于以相同顺序获得相同输入的部分,必须提醒一下——这就是日志的来源。
因此,这实际上是一个非常直观的概念:如果向两个确定性代码段输入相同的日志,则它们将以相同的顺序产生相同的输出。
分布式计算的应用非常明显。你可以减少让所有机器做一样的事的问题以及实现一致的日志以将输入提供给这些进程的问题。在这里,日志的作用是从输入流中杜绝掉所有的不确定性,以确保正在处理此输入的每个副本都保持同步。
一旦你理解了它,就不会对这个原理感到艰深晦涩:它所表示的仅仅是“确定性的过程是确定的”。不过,我认为它是用于分布式系统设计的更通用的工具之一。
这都不是什么新鲜的技术。如果说分布式计算古老到已经具备了比较经典的方法,那么这些就是。
但是,这种基本设计模式的含义并未得到广泛的理解,并且在企业系统架构的应用中甚至更少。
还有一个好处就是,离散的日志条目号可以充当副本状态的时钟——你可以用一个数字来描述每个副本的状态:已处理的最大日志条目的时间戳。同时的两个副本将处于相同状态。因此,此时间戳与日志结合可以唯一地捕获副本的整个状态。这提供了一个离散的,由事件驱动的时间概念,与机器的本地时钟不同,它可以很容易地在不同机器之间进行比较。
其他以日志为中心的设计
根据日志中的内容,如何应用此原理有很多变体。例如,我们可以将传入请求记录到服务中,并让每个副本独立处理这些请求。或者,我们可以创建一个实例来处理请求,并记录服务响应请求所经历的状态变化。从理论上讲,我们甚至可以记录一系列x86机器指令或需要调用的方法名称和参数以供每个副本执行。只要两个进程以相同的方式处理这些输入,这些进程就将在副本之间保持一致。
不同的社区会以不同的方式描述相似的模式。数据库人员通常区分为物理日志记录和逻辑日志记录。基于物理或基于行的日志记录意味着记录已更改的每一行的内容。逻辑或语句记录则意味着不记录已更改的行,而是记录导致行更改的SQL命令(insert,update和delete语句)。
在分布式系统文献中通常将两者区分为操作和副本 。状态机模型 通常是指一个Active-Active模型,在该模型中,我们保留传入请求的日志,每个副本以日志顺序处理每个请求。如果稍作改变,选出一个作为主副本,则可被称为主从备份模型。主副本按请求到达的顺序处理请求,并将由于处理请求而发生的更改记录到其状态中。其他副本则应用主副本的状态更改,以便在主副本挂掉时,同步并接管主副本。
如图1-3所示,在主从备份模型中,选择了一个主副本来处理所有读取和写入操作。每次写入都会发布到日志中。从副本通过订阅日志,将主副本执行的更改应用于其本地状态。如果主副本发生故障,则会从从属副本中选择一个新的主服务器。在状态机复制模型中,所有节点都是对等的。写操作首先进入日志,并且所有节点均按日志确定的顺序应用写操作。
图1-3 在主备份模型中,选择一个主节点来处理所有读取和写入。在状态机复制模型中,所有节点都充当对等方。一个例子
想要了解使用日志构建系统的不同方法,我们先来看一个小问题。假设我们要实现一个复制的算术服务,该服务将维护一组变量(初始值为零),并对这些值应用加,乘,减,除和查询。我们的服务将响应以下命令:
x? // get the current value of x
x+=5 // add 5 to x
x-=2 // subtract 2 from x
y*=2 // double y
假设该服务作为远程网络服务运行,并通过HTTP发送请求和响应。
如果我们只有一台服务器,则实现将非常简单。它可以将变量存储在内存或磁盘中,并以接收请求的任何顺序更新它们。但是,由于只有一台服务器,因此我们缺乏容错能力,也没有办法扩展服务范围(假如我们的算术服务很受欢迎)。
我们可以通过添加复制此状态和处理逻辑的服务器来解决此问题。但是,这带来了一个新问题:服务器之间可能不同步。发生这种情况的方式有很多。例如,服务器可能以不同的顺序接收更新命令(并非所有操作都是可交换的),或者有的服务器发生故障或无响应而错过更新。
当然,实际上,大多数人只是将查询和更新推送到远程数据库中。这样做确实可以让我们的应用程序不发生问题,但并不能真正解决问题。毕竟,现在我们需要解决数据库中的容错问题。因此,出于示例的考虑,让我们直接讨论应用程序中日志的使用。
使用日志,有好几种方法可以解决这个问题。使用状态机副本的方法是,首先将要执行的操作写入日志,然后让每个副本按日志顺序应用这些操作。这样,日志里就要包含一系列命令,例如“ x + = 5”或“ y * = 2”。
主从副本的方法也是可行的。在这种设计中,我们将选择其中一个副本作为主要副本(也叫领导者或母版)。这个主副本将在本地按顺序执行它所接收到的所有命令,并且记录执行命令所产生的一系列变量值。在这种设计中,日志仅包含结果变量值,例如“ x = 1”或“ y = 6”,而不包含产生值的原始命令。其余副本将充当备份(也叫跟随者或从属);它们订阅这个日志,并将新的变量值被动地应用于其本地存储。当主副本宕机时,我们则从其余副本中选择一个新的主副本。
这个示例清楚说明了为什么排序对于确保副本之间的一致性至关重要:对加法和乘法命令进行重新排序将产生不同的结果,就像对同一变量的两次更新重新排序一样。
日志和共识
分布式日志可以看作是模拟共识问题的数据结构。毕竟,日志代表的是产生下一个值的一系列决策。你必须瞥一眼看看Paxos系列算法中的日志,尽管日志构建是它们最常见的实际应用。使用Paxos时,通常使用称为“ multi-paxos”的协议扩展来完成,该协议将日志建模为一系列共识问题,每个问题对应一个日志。该日志在ZAB,RAFT和 Viewstamped Replication等其他协议中更为突出,它们直接对维护分布式一致日志的问题进行建模。
我怀疑,我们过去对这些东西的看法有些偏颇,这可能是由于几十年来分布式计算理论已经赵越了其实际应用。实际上,共识问题有点太简单了。计算机系统很少需要确定的单个值,它们几乎总是处理一系列请求。因此,日志不是简单的单值寄存器而是更自然的抽象。
此外,对算法的关注也掩盖了系统所需的底层日志抽象。我怀疑我们最终将把重点放在作为商品化构建基块的日志上,而不管其实现如何,就像我们经常谈论哈希表一样,而不必费心去弄清楚是线性探测还是杂项哈希其他一些变体。日志将变成商品化的界面,许多算法和实现相互竞争将提供最佳的可靠性和最优的性能。
变更日志101:表和事件是一对
让我们先回到数据库。变更日志和表格之间有一种奇妙的二重性。日志类似于银行处理的所有借贷操作列表,而表格则是所有当前帐户余额。如果你更改了日志,则可以应用这些更改以创建表并捕获当前状态。表格将记录每个键的最新状态(以特定的日志时间为准)。从某种意义上说,日志是更基本的数据结构:除了创建原始表之外,您还可以对其进行转换以创建各种派生表。(是的,表可以为非关系人员提供键控数据存储。)
这个过程也可以反向进行:如果您对一个表进行了更新,则可以记录这些更改并发布所有更新到该表状态的更改日志。这个变更日志正是支持“Near-Real-Time(近实时)”副本所需要的。从这个意义上讲,您可以将表和事件视为一对:表支持静态数据,日志捕获更改行为。日志的神奇之处在于,如果它是更改的完整日志,则它不仅可以保存表最终版本的内容,而且还可以重新创建可能已经存在的所有其他版本。实际上,它是对表的每个先前状态的一种备份。
这可能会使您想起代码版本控制。源代码管理与数据库之间有着密切的关系。版本控制解决了与分布式数据系统必须解决的非常相似的问题:管理状态的分布式并发更改。版本控制系统通常需要对补丁序列进行建模,实际上就是日志。你可以直接与当前代码的已检出快照进行交互,这个快照类似于表。请注意,在版本控制系统中,就像在其他分布式有状态系统中一样,复制是通过日志进行的:更新时,您只需拉下补丁并将其应用于当前快照即可。
最近有人从Datomic,一家销售日志数据库的公司,看到了其中一些想法。当然,这些概念并不是该系统独有的,因为它们已经成为分布式系统和数据库文献的一部分已有十多年了。以上这些内容看起来似乎有点偏纯理论。不要绝望!我们将快速介绍实用的内容。
后续内容
在本书的其余部分中,我将让你尝到超越分布式系统或抽象的分布式计算模型内部以外的甜头,包括:
- 数据集成
- 使组织的所有数据可以轻松地在其所有存储和处理中使用系统。
- 实时数据处理
- 计算派生数据流。
- 分布式系统设计
- 以日志为中心的设计如何简化实际系统。
这些应用都是围绕着将日志作为独立服务的思想设计的。
在每种情况下,日志的实用性都来自于日志提供的简单功能:生成持久的,可重播的历史记录。令人惊讶的是,前面提到的这些日志的应用,其核心都是使多台计算机以确定的方式以自己的速率回放历史记录的能力。
章节列表
网友评论