2 分布式系统原理
2.5 日志技术
日志技术是宕机恢复的主要技术之一[3]。日志技术最初使用在数据库系统中。严格来说日志技 术不是一种分布式系统的技术,但在分布式系统的实践中,却广泛使用了日志技术做宕机恢复,甚 至如BigTable 等系统将日志保存到一个分布式系统中进一步增强了系统容错能力。
本章首先简单介绍数据库系统中的日志技术,进而抽象简化问题模型,在简化模型的基础上介 绍两种实用的日志技术Redo Log 与 No Redo/No undo Log。
2.5.1 数据库系统日志技术简述
在数据库系统中实现宕机恢复,其难点在于数据库操作需要满足 ACID,尤其在支持事务
(transaction)的数据库系统中宕机往往发生在某些事务只执行了部分操作的时候。此时宕机恢复的 主要目标就是数据库系统恢复到一个稳定可靠状态,消除未完成的事务对数据库状态的影响。
数据库的日志主要分为Undo Log、Redo Log、Redo/Undo Log 与 No Redo/No Undo Log。这四 类日志的区别在更新日志文件和数据文件的时间点要求不同,从而造成性能和效率也不相同。本文 不就数据库中的这四类日志技术做深入讨论,相关信息可以参考有关数据库系统方面的资料。
2.5.2 Redo Log 与 Check point
2.5.2.1 问题模型
首先简化原数据库系统中的问题模型为一个较为简单的模型:假设需要设计一个高速的单机查询系 统,将数据全部存放在内存中以实现高速的数据查询,每次更新操作更新一小部分数据(例如 key-value 中的某一个 key)。现在问题为利用日志技术实现该内存查询系统的宕机恢复。与数据库的 事务不同的是,这个问题模型中的每个成功的更新操作都会生效。这也等效为数据库的每个事务只 有一个更新操作,且每次更新操作都可以也必须立即提交(Auto commit)。
2.5.2.2 Redo Log
Redo Log 是一种非常简单实用的日志技术。在上节的问题模型中,只需按如下流程更新既可以 实用Redo Log。
流程2.5.1:Redo Log 更新流程
1. 将更新操作的结果(例如 Set K1=1,则记录 K1=1)以追加写(append)的方式写入磁盘的 日志文件
2. 按更新操作修改内存中的数据 3. 返回更新成功
上述更新流程中第 2 步没有考虑修改内存数据需要多线程互斥等问题,但对于说明 Redo Log 的原理没有影响。
从Redo Log 的流程可以看出,Redo 写入日志的是更新操作完成后的结果(虽然本文不讨论 Undo Log,这点是与 Undo Log 的区别之一),且由于是顺序追加写日志文件,在磁盘等对顺序写有力的 存储设备上效率较高。
用Redo Log 进行宕机恢复非常简单,只需要“回放”日志即可。
流程2.5.2:Redo Log 的宕机恢复
1. 从头读取日志文件中的每次更新操作的结果,用这些结果修改内存中的数据。
从Redo Log 的宕机恢复流程也可以看出,只有写入日志文件的更新结果才能在宕机后恢复。这 也是为什么在Redo Log 流程中需要先更新日志文件再更新内存中的数据的原因。假如先更新内存中 的数据,那么用户立刻就能读到更新后的数据,一旦在完成内存修改与写入日志之间发生宕机,那 么最后一次更新操作无法恢复,但之前用户可能已经读取到了更新后的数据,从而引起不一致的问 题。
2.5.2.3 Check point
宕机恢复流量的缺点是需要回放所有redo 日志,效率较低,假如需要恢复的操作非常多,那么 这个宕机恢复过程将非常漫长。解决这一问题的方法即引入check point 技术。在简化的模型下,check point 技术的过程即将内存中的数据以某种易于重新加载的数据组织方式完整的 dump 到磁盘,从而 减少宕机恢复时需要回放的日志数据。
流程2.5.3:check point
1. 向日志文件中记录“Begin Check Point”
2. 将内存中的数据以某种易于重新加载的数据组织方式 dump 到磁盘上 3. 向日志文件中记录“End Check Point”
在check point 流程中,数据可以继续按照流程 2.5.1 被更新,这段过程中新更新的数据可以 dump 到磁盘也可以不dump 到磁盘,具体取决于实现。例如,check point 开始时 k1=v1,check point 过程 中某次更新为k1 = v2,那么 dump 到磁盘上的 k1 的值可以是 v1 也可以是 v2。
流程2.5.4:基于 check point 的宕机恢复流程 1. 将 dump 到磁盘的数据加载到内存。
2. 从后向前扫描日志文件,寻找最后一个“End Check Point”日志。
3. 从最后一个“End Check Point”日志向前找到最近的一个“Begin Check Point”日志,并回 放该日志之后的所有更新操作日志。
上述check point 的方式依赖 redo 日志中记录的都是更新后的数据结果这一特征,所以即使 dump 的数据已经包含了某些操作的结果,重新回放这些操作的日志也不会造成数据错误。同一条日志可 以重复回放的操作即所谓具有“幂等性”的操作。工程中,有些时候 Redo 日志无法具有幂等性,
例如加法操作、append 操作等。此时,dump 的内存数据一定不能包括“begin check point”日志之 后的操作。为此,有两种方法,其一是check point 的过程中停更新服务,不再进行新的操作,另一
0/1 目录技术中有两个目录结构,称为目录 0(Directory 0)和目录 1(Directory 1)。另有一个结构 称为主记录(Master record)记录当前正在使用的目录称为活动目录。主记录中要么记录使用目录 0,
要么记录使用目录1。目录 0 或目录 1 中记录了各个数据的在日志文件中的位置。
Directory 0
Directory 1
图 1-1 给出了一个 0/1 目录的例子。活动目录为目录 1,数据有 A、B、C 三项。查目录 1 可得 A、B、C 三项的值分别为 2、5、2。
0/1 目录的数据更新过程始终在非活动目录上进行,只是在数据生效前,将主记录中的 0、1 值 反转,从而切换主记录。
流程2.5.5:0/1 目录数据更新流程 1. 将活动目录完整拷贝到非活动目录。
2. 对于每个更新操作,新建一个日志项纪录操作后的值,并在非活动目录中将相应数据的位置 修改为新建的日志项的位置。
3. 原子性修改主记录:反转主记录中的值,使得非活动目录生效。
0/1 目录的更新流程非常简单,通过 0、1 目录的主记录切换使得一批修改的生效是原子的。
0/1 目录将批量事务操作的原子性通过目录手段归结到主记录的原子切换。由于多条记录的原 子修改一般较难实现而单条记录的原子修改往往可以实现,从而降低了问题实现的难度。在工程中 0/1 目录的思想运用非常广泛,其形式也不局限在上述流程中,可以是内存中的两个数据结构来回 切换,也可以是磁盘上的两个文件目录来回生效切换。
2.5.4 工程投影
日志技术的使用非常广泛,在 zookeeper 系统中,为了实现高效的数据访问,数据完全保存在 内存中,但更新操作的日志不断持久化到磁盘,另一方面,为了实现较快速度的宕机恢复,zookeeper 周期性的将内存数据以checkpoint 的方式 dump 到磁盘。
MySQL 的主从库设计也是基于日志。从库只需通过回放主库的日志,就可以实现与主库的同 步。由于从库同步的速度与主库更新的速度没有强约束,这种方式只能实现最终一致性。
Mola*与 Armor*系统支持多种不同的存储引擎,对于接受到的更新操作,这两个系统将操作日 志(redo log)保存到磁盘,引擎可以通过回放日志实现副本数据的同步。在 mola*中,由于不需要 强一致性,日志与数据分离,且日志也保存多个副本,当日志副本更新满足 quorum 要求后就返回 用户更新成功。引擎通过回放日志的方式实现数据更新,由于回放速度不一致,mola 提供最终一致 性保证。同时,由于返回用户更新成功时只保证日志更新成功,此时读取引擎数据未必可以读到最 新更新的数据。Armor*中更新了这一设计,日志与数据不分离,更新日志的同时也更新引擎数据,
从而可以立刻读取到成功更新的数据。