在大数据时代,Log是关系数据库对计算机行业的伟大贡献,更是基础技术之一。然而在大家热烈讨论GFS, NoSQL,乃至Paxos, LSM tree等词语的时候,Log这个基础技术以及它对大数据行业的巨大贡献却一直以来都被业界所忽略。除了Kafka作者之一Jay Kreps2013年一篇非著名的文章以外,我几乎不能发现太多讨论Log的。不论这种忽略有意无意,都让我觉得有必要写一篇文章。本文结合了Jay的文章的观点和本人在这个领域的实践经验,旨在对我们司空见惯的Log在大数据系统里面的巨大作用做一个推广和普及
01
当我们说Log的时候,通常在指两种不同东西,其一是应用程序的调试信息,通常此类log是人阅读的,形式也不固定,有时候需要像Splunk这样的工具来帮忙。另外一种则是我们今天要讲的log,由关系数据库领域发明出来,最开始的目的是为了做故障恢复。这种log教科书上有Undo Log, Redo Log 或者Undo/Redo Log三种。但是实践来说,Redo Log是最常用的,也是今天我们要谈论的。所以从这里往下,整篇文章我们讨论的是关系数据库里面的Redo Log。有关这个的定义我会在下面一节展开。
02
要理解Log,我们可以把它看做一个数据结构,类似于Hash Table或者Stack。至于这个数据结构怎么实现,本文不做详细讨论,也不影响理解本文的内容。
Log这个数据结构的基本形式是一个记录的的序列。每个记录分为两部分:一个时间戳,和记录的内容。Log要求时间戳是严格递增的。也就是说下一条记录总是比上一条记录的时间戳要大。时间序列不一定是机器时间,任何严格递增的序列,都是可以的。
对于Log的操作有两种,第一种是在序列的末尾加一条新的记录,第二是顺序阅读这个记录序列。Log里面的所有记录一旦写入以后就是只读的了。
03
在Log的记录里,每条记录可以存放的内容通常有两种形式,一种是记录要对数据库的表做什么样的操作,一种是记录数据库的表经过操作以后值发生了什么改变。我们通过一个例子来展示这两种方式的不同。
假设一个数据库表里面存的是用户和他存的钱的金额,而所有的活动仅仅限于存钱和花钱。我们假设张三的初始存款是500块。那么第一种log的方式看起来像这样:
1 张三 + 100
2 张三 – 200
3 张三 – 50
4 张三 + 100
而第二种方式则看起来像这样:
1 张三 800
2 张三 600
3 张三 550
4 张三 650
两种方式的区别在于第二种记录了钱变化后的结果,而不记录是什么样的操作导致了钱的变化。第一种则记录了实际的操作。
作为一个Log,我们通常还会有对应操作的对象,在关系数据库里面,通常这个是表或者数据库。而在NoSQL里面则是Key和Value。我们可以说,对应操作的对象存的是对象的当前状态,而Log里面则记录了从初始状态到当前状态的所有变化。所以作为Log的第一个作用,就是当系统发生故障以后,对系统进行恢复。
作为系统恢复的Log,第一种方式和第二种方式的记录有着本质的不同。当我们使用第二种方式的时候,我们没有任何需要担心的,唯一的一点,我们也同样丢失了是什么原因导致了数值的改变。这种丢失对于一个机器阅读的Log是无所谓的。而第一种方式就不一样了,我们必须假设所有的操作在给定初始状态和操作以后,返回的结果是确定的。一些操作比如说获取当前系统时间,或者取一个随机数这样的东西,是不合法的操作,否则Log作为恢复数据的作用也就不存在了。因此实践来说,使用第二种方式记录数据改变的Log居多。但是在分布式系统里面,使用第一种方式来记录也并非不存在。
Log作为恢复手段,在分布式系统和大数据系统中无处不在。比如说在BigTable里,对Memory Table进行更新之前,就要先写Log。
04
Log的重要特性:如果知道初始状态和Log,就可以恢复出期间任意一个状态。
这个特性让Log在故障恢复以外的领域很快就找到了用武之地。在分布式数据库里面,不管是高大上的Oracle还是白菜化的MySQL,在不同的备份之间进行同步,其基本的思路就是一方把Log传给另外一方。另外一方只要老老实实的按照顺序执行Log上的操作。根据我们讲过的Log的重要特性,我们知道最终的状态也必然是一致的。
当我们进入到大数据时代以后,这种传统的分布式数据库的同步方式就被取代了。无论是Chubby还是BigTable还是Spanner,有一个非常有名的耀眼的名词Paxos就这样善良登场了。
简单来讲不管是Paxos还是Raft,都是一种可以容忍一定程度的failure的分布式一致协议。题外话插一句,这些协议纸面上看起来都简单,但是在生产实践里面要有一个实用的实现,却不是一件容易的事情。通常来说,也只有大公司在吃亏无数次以后才能渐渐形成一个可用的版本。但是实用分布式一致性协议去取代传统的Log备份是大势所趋。譬如说Chubby的第一代系统应该是基于Oracle数据库实现的,而后面就很快切换到了基于Paxos的实现。
然而最重要的一点是,比如说Paxos协议,它所谓的分布式一致性,其实是指对某个递增序列里面的某个数字达成一致。可是我们知道在实践里面,任何系统对于某个数字达成一致是没有意义的。
实际的做法是这样的,对于达成一致的这个数字,我们可以认为是Log里面的某个时间戳,而大家同步的过程也就是按照Log的记录顺序执行到当前的时间戳。所以虽然说我们换用了分布式一致协议,其底下每个投票的参与者依然是需要通过Log来维系状态的一致性。
05
Log的这种特性又赋予了Log在大数据世界里更多的应用。我们先说个简单的例子。自从Spanner出来以后,大家就造出了新名词New SQL。但是通常来说这些New SQL们都必须选一个已有的dialect来说话。一般来说,当然不是MySQL就是Postgress了。
举个例子,著名Spanner中国山寨弱化版TiDB就是这样一个New SQL,用的是MySQL的dialect。这个系统出来需要用户用的时候, 有一个问题就比较麻烦了。怎么样能够从MySQL里面把数据倒出来然后塞进自己里面去。TiDB的人有很强的工程能力和思维能力。他们的解决办法就是把自己装成是一个MySQL的Slave,然后就通过MySQL的同步机制通过BinLog来同步了。BinLog虽然说格式上更复杂一些,但是本质上来说就是一个Redo Log。
这可以认为是让自己系统上线的一个聪明的想法。但是我们其实可以推广这个想法。作为Log的消费者,为什么非要和Log的生产者是同类呢?如果我们希望在大数据的时代里面在各种不同的系统之间进行同步的话,最简单的办法就是有数据的生成Log,需要同步的去按照Log执行就好。有的慢有的快,无所谓。而且Log天然就带有了故障恢复能力。用Log来搭建一个数据交换和同步的系统,在大数据时代是一个非常好的选择。
于是我们就有了Kafka。当然Kafka有很多的名词,Pub/Sub啊,message queue啊。而且Kafka的实际实现也比简单的描述要复杂。然而究其本质来说,我们使用了关系数据库里面的伟大发明Log,利用Log的重要特性,在给定初始状态以后,按部就班的执行log就可以获得最终状态,以及Log自带的故障恢复功能,构建了一个不同的数据源之间相互传递和同步数据的伟大产品:Kafka。
Log作为一个逻辑层面的发明,是一个伟大的贡献。在大数据时代的今天,我们谈论着很多时髦的词汇,高级的技术。Log这个奠基的技术的重要性,很大程度上被人忽略了。