在LinkedIn分布式化中过程中,我学到最有用的东西,是我们构建的很多系统,在背后都有一个非常简单的思想,日志。有时日志也叫做WAL(Write Ahead Logs)或者提交日志或者事务日志。日志其实已经存在很久了,几乎和计算机存在的时间一样长,很多分布式数据系统和实时应用架构的核心就是日志。
如果你不了解日志,你就肯定不能彻底理解数据库,NoSQL存储,kv存储,复制,paxos协议,hadoop,版本控制,甚至其他任何软件系统。即使这样,现在中大多数软件工程师对日志并不熟悉。我希望改变这种现状,在这篇文章中,我会带着你了解有关日志的一切,包括日志是什么,怎样使用日志做数据集成,实时数据处理和系统架构。
第一部分,日志是什么?
日志可能是最简单的存储抽象。日志是一种只能在尾部添加,完全顺序的,按照时间排序的一组记录。日志长得像这样:
Log记录被添加到日志尾部,并从左到右读取。日志中的每一条记录都包含一个唯一的日志序列号。
日志记录的顺序定义了一个时间的概念,因为在我们的定义中,右边的记录要比左边的记录“大”。这种情况下,日志序列号就可以被当做每条记录的时间戳。把顺序定义为时间的概念一开始看起来有点奇怪,但是这其实是一个很有用的属性,因为这样一来系统的时间就不依赖任何物理时钟了。当我们说到分布式系统时,这会是一个非常有用的属性。
在我们现在的讨论当中,日志的内容和格式并不重要,同时,我们也不能一直追加日志,因为存储空间迟早会耗尽。
所以,日志和文件或者数据库表并没有本质的不同。文件是一个字节数组,数据库表是一个记录数组,日志其实就像一张数据库表或者一个文件,只是日志里的记录按照时间顺序排序过。
说到现在,你可能奇怪,为什么要说这么简单的东西?一个只能在尾部追加的记录序列到底和数据系统有什么关系?答案是,日志记录了发生的事情以及发生的时间。在很大程度上来说,这其实就是分布式系统要处理的核心问题。
在进一步阐释之前,我要先解释清楚一个疑惑。每个程序员都对另一种日志特别熟悉 - 非结构化的错误日志或者应用程序的trace信息。我把这些日志称为“应用日志”。应用日志其实我们这里讨论的日志的一种降级形式。他们最大的区别是,应用日志是用肉眼去读并理解的,而数据日志是用程序来处理的。
数据库中的日志
我其实并不知道日志这个概念是谁发明的,也有可能是这个概念像二分查找一样,太简单,连发明者都没有意识到他发明了一个东西。日志最早在IBM的System R中出现。在数据库中,日志是作用是在崩溃情况下也能保持不同种类的数据结构及索引的同步。为了实现原子性和持久性,在把修改真正应用到数据之前,数据库使用日志来保存对数据的修改。日志是真正发生的事情的记录,数据库表和索引只是把发生过的事情映射到某种有用的数据结构之中。由于日志是立即持久化的,在系统崩溃的时候会用日志来恢复其他的持久性数据结构。
随着时间推移,日志的使用已经从最初的数据库ACID性质实现,发展到数据库之间复制数据的方法。事实证明,这一连串对数据修改的记录,恰恰就是和远程数据库保持同步的关键。Oracle,MySQL和PostgreSQL都包含日志传输协议,用来在从数据库上复制数据。Oracle已经把他们的Xstreams和GoldenGate产品化,MySQL和PostgreSQL也有相似的关键技术。
正因为如此,机器可读的日志概念,仅仅被限制在数据库的范畴之内。使用日志作为数据订阅的机制好像只是因为意外而产生的。但是这种抽象对于支持消息,数据流动和实时数据处理来说非常完美。
在分布式系统中的日志
日志解决的两个问题是,数据变化顺序和数据分布,在分布式环境中显得更加重要。在数据的更新上达成一致是分布式系统设计时需要考虑的核心问题。
以日志为核心的分布式系统,是从一个简单的观察结果而来,这里我们把它叫做状态机复制原则:
如果两个相同的确定性系统,从一个相同的状态开始,且以相同的顺序获得相同的输入,两个系统会产生相同的输出,并且以相同的状态结束运行。
这个描述可能听起来有点不清楚,我来解释一下。
确定性系统的意思是,数据的处理并不依赖时间, 也不会因为其他未知输入影响输出。举例来说,如果一个程序的输出会被线程执行的顺序影响,或者被gettimeofday这个函数调用影响,或者被某些不可重复发生的事情影响,这个程序就不具有确定性。
程序的状态就是程序运行结束后机器上存储的数据,不管是在内存里还是在磁盘上。
程序以相同顺序获得相同输入这点可能会给我们一个暗示,这里和日志有关。这其实是一个非常直觉可以理解的概念,如果给两个确定性程序相同的输入日志,这两个程序会产生相同的输出。
把日志的概念应用到分布式系统上的方式就很明显了。在分布式环境下的所有机器做同一件事的问题,可以简化为,实现一个分布式一致性日志系统,把日志作为这些程序的输入就好了。日志在这里的作用是,排除一切非确定性的输入,确保所有副本同步处理输入。
这个方式的美妙之处在于,以前作为索引的日志时间戳,现在也可作为副本的状态时钟。你可以用最大处理日志序列号这个数字来描述一个副本。这个时间戳和日志结合起来,描述了副本的整个状态。
有非常多的方式可以把这个原理应用到依赖日志输入的系统。理论上,我们甚至可以用日志记录每一条副本需要执行的指令或者副本需要执行的方法名和参数。只要两个程序用相同的方式处理这些输入,这两个程序的数据会始终保持一致。
不领域的人会用不同方式来描述日志。比如数据库工作者会区分物理日志和逻辑日志。物理日志是记录了每一行记录改变的内容。逻辑日志记录了改变数据的SQL,比如insert/update/delete。
书本上通常会描述两种数据处理和复制的方式。状态机模型,通常指的是为每一个请求记录日志,并且每一个副本逐一处理每一条请求。另一个模型主-备模型稍微有点不同,在这个模型下,系统会选举一个副本作为leader,leader会执行请求并记录数据修改内容,其他备份会把数据修改通过日志同步到本地。
Two Models为了理解这两种方法的区别,我们来看一个玩具问题。假设有一个“算法服务”,它维护了一个数字作为它的状态,并且初始化为零,后续这个服务对这个数字进行加法和乘法计算。主-主的模型可能会记录数字的变化,比如“+1”或者“+2”。每个副本会经历相同顺序的数字变化。主-备模型会有一个主服务来执行计算,并且日志记录结果,比如“1”,“3”,“6”。这个简单的例子也说明为什么顺序对于数据的一致性至关重要,因为对加法和乘法的重排序会产生不同的计算结果。
Paxos分布式日志也可以被当做一致性协议的数据模型。毕竟,日志就是为数据下一个变化的值做的决定。你可以看一个paxos算法中的日志是什么样子的,及时paxos算法也被用来创建日志。
网友评论