日志技术

日志技术是宕机恢复的主要技术之一

日志技术最初使用在数据库系统中。

严格来说日志技术不是一种分布式系统的技术,但在分布式系统的实践中,却广泛使用了日志技术 做宕机恢复,甚至如 BigTable 等系统将日志保存到一个分布式系统中进一步增强了系统容错能力 。

本章首先简单介绍数据库系统中的日志技术,进而抽象简化问题模型,在简化模型的基础上介绍两种实用的日志技术 Redo Log 与 No Redo/No undo Log 。

数据库系统日志技术简述

在数据库系统中实现宕机恢复,其难点在于数据库操作需要满足 ACID ,尤其在支持事务 (transaction )的数据库系统中宕机往往发生在某些事务只执行了部分操作的时候。

此时宕机恢复的主要目标就是数据库系统恢复到一个稳定可靠状态,消除未完成的事务对数据库状态的影响。

数据库的日志主要分为 Undo Log 、 Redo Log 、 Redo/Undo Log 与 No Redo/No Undo Log 。

这四类日志的区别在更新日志文件 和数据文件的时间点要求不同,从而造成性能和效率也不相同。

本文不就数据库中的这四类日志技术做深入讨论,相关信息可以参考有关数据库系统方面的资料。

Redo Log 与 Check point

问题模型

首先简化原数据库系统中的问题模型为一个较为简单的模型:

假设需要设计一个高速的单机查询系统,将数据全部存放在内存中以实现高速的数据查询,每次更新操作更新一小部分数据(例如 key value 中的某一个 key )。

现在问题为利用日志技术实现该内存查询系统的宕机恢复。

与数据库的事务不同的是,这个问题模型中的每个成功的更新操作都会生效。

这也等效为数据库的每个事务只有一个更新操作,且每次更新操作都可以也必须立即提交( Auto commit )。

Redo Log

Redo Log 是一种非常简单实用的日志技术。

在上节的问题模型中,只需按如下流程更新既可以使用 Redo Log 。

Redo Log 更新流程

  1. 将更新操作的结果 (例如 Set K1=1 ,则记录 K1=1 )以追加写 append )的方式写入磁盘的日志文件

  2. 按更新操作修改内存中的数据

  3. 返回更新成功

从 Redo Log 的流程可以看出, Redo 写入日志的是更新操作完成后的结果(虽然本文不讨论 Undo Log ,这点是与 Undo Log 的区别之一),且由于是顺序追加写日志文件,在磁盘等对顺序写有利的存储设备上效率较高。

Redo Log 的宕机恢复

用 Redo Log 进行宕机恢复非常简单,只需要“回放”日志即可。

  1. 从头读取日志文件中的每次更新操作的结果,用这些结果修改内存中的数据。

从 Redo Log 的宕机 恢复 流程也可以看出,只有写入日志文件的更新结果才能在宕机后恢复。

这也是为什么在 Redo Log 流程中需要先更新日志文件再更新内存中的数据的原因。

假如先更新内存中的数据,那么用户立刻就能读到更新后的数据,一旦在完成内存修改与写入日志之间发生宕机,那么最后一次更新操作无法恢复,但之前用户可能已经读取到了更新后的数据,从而引起不一致的问题。

Check point

宕机恢复流量的缺点是需要回放所有 redo 日志,效率较低,假如需要恢复的操作非常多,那么这个宕机恢复过程将非常漫长。

解决这一问题的方法即引入 check point 技术。

在简化的模型下,check point 技术的过程即将内存中的数据以某种易于重新加载的数据组织方式完整的 dump 到磁盘,从而减少宕机恢复时需要回放的日志数据。

更新 check point

  1. 向日志文件中记录 “Begin Check Point

  2. 将内存中的数据以某种易于重新加载的数据组织方式 dump 到磁盘上

  3. 向日志文件中记录 “End Check Point

在 check point 流程中,数据可以继续按照前面的更新流程,这段过程中新更新的数据可以 dump 到磁盘也可以不 dump 到磁盘,具体取决于实现。

例如, check point 开始时 k1=v1 check point 过程 中某次更新为 k1 = v2 ,那么 dump 到磁盘上的 k1 的值可以是 v1 也 可以是 v2 。

基于 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 的过程中停更新服务,不再进行新的操作,另一种方法是,设计一种支持快照( snapshot )的内存数据结构,可以快速的将内存 生成 快照,然后写入check point 日志再 dump 快照数据。

至于如何设计支持快照的内存数据结构,方式也很多,例如假设内存数据结构维护 key value 值,那么可以使用哈希表的数据结构,当做快照时,新建一个哈希表接收新的更新,原哈希表用于 dump 数据,此时内存中存在两个哈希表,查询数据时查询两个哈希表并合并结果。

No Undo/No Redo log

本节介绍另一种特殊的日志技术“No Undo/No Redo log ”,这种技术也称之为 0/1 目录” ”(0/1 directory) 。

本节介绍这种技术并不再使用上节的问题场景,而假设另一种问题场景:

若数据维护在磁盘中,某批更新由若干个更新操作组成,这些更新操作需要原子生效,即要么同时生效,要么都不生效。

0/1 目录技术中有两个目录结构,称为目录 0 (Directory 和目录 1 (Directory 。

另有一个结构称为主记录( Master record )记录当前正在使用的目录称为活动目录。

主记录中要么记录使用目录 0 要么记录使用目录 1 。

目录 0 或目录 1 中记录了各个数据的在日志文件中的位置。

0/1 目录数据更新流程

  1. 将活动目录完整拷贝到非活动目录 。

  2. 对于每个更新操作,新建一个日志项纪录操作后的值,并在非活动目录中将相应数据的位置修改为新建的日志项的位置。

  3. 原子性修改主记录:反转主记录中的值,使得非活动目录生效。

0/1 目录的更新流程非常简单,通过 0 、 1 目录的主记录切换使得一批修改的生效是原子的。

0/1 目录 将批量事务操作的原子性通过目录手段归结到主记录的原子切换。

由于多条记录的原子修改一般较难实现而单条记录的原子修改往往可以实现,从而降低了问题实现的难度。

在工程中 0/1 目录 的思想运用非常广泛,其形式也不局限在上述流程中,可以是内存中的两个数据结构来回切换,也可以是磁盘上的两个文件目录来回生效切换。

ps: 这是一种思想,就是通过冗余空间,来实现原子性的替换。

参考资料

《分布式系统原理》