一、前言

无论是在数据库,还是其他的业务系统,日志是非常重要的。日志通常在系统中有如下的作用:

1.     业务问题定位。系统开发中,谁没写几个BUG? 有了日志,就能方便快速定位问题,修复系统。这也是我们用日志最多的地方。

2.     系统运行流程监控。 雁过必留痕,通过日志可以进行系统校验,确保系统是按预定的流程运行,而不是你以为的方式。毕竟计算机运行的方式是它以为,而不是你以为。

3.     安全审计。 在无纸化办公的时代,谁做了什么事不是删记录就可以像鸵鸟一样,把头埋在沙里,以为万事无虞。

4.     故障修复。 这个基本是数据库的标配。无论是MySQL还是Redis, 无论是Mongodb还是ES, 都有日志协助修复系统。

本文梳理的重点就是数据库如何利用日志进行故障修复。

 

 

二、系统故障

在《事务处理概念与技术》一书中,记录了故障方方面面的来源:环境、操作、维护、硬件、软件、过程等。

作为用户,我们希望故障修复后,系统能够正常工作。或者说,我们希望系统能够容忍一些故障(容错),以保障系统工作时长更久,996是不够的,最好是7*24。毕竟在自动化处理业务流程的时代,系统处理业务流程的效率太高(例如:支付宝助力天猫双11 OceanBase 每秒处理峰值达到6100 万次)。 停机的代价太大。对于电商和金融,每笔处理的流水都是白花花的银子,系统一罢工,损失不是一个人请假或者离职那么微不足道。

故障虽然出现的概率小,但是致命,不得不防。

 

三、应对措施

 

在《事务处理概念与技术》中,事务的理解很简单:数据库状态的变更。比如,我发工资了,那么我银行卡里余额就变多了,这个余额就是银行卡的一个状态。在发工资这个业务流程中,涉及到的另一端是公司银行卡的钱变少了。

数据库需要保障业务流程中,涉及到的主体状态变更是一致的,不然公司的帐就乱了。

这就引出了事务的特性:ACID。 ACID的规约其实就类似于合同法,当流程正常时是压箱底用不到的,当流程出现争端,才需要用合同的条约解决问题。其实理解ACID,用结婚这个现实中的例子更容易理解。严谨一些,以中国的《婚姻法》为例。

原子性-A:最开始两个人的状态都是【未婚】,领到结婚证的一刻,两个人的状态都变成了【已婚】。 两个人的状态是绑定在一起的。

一致性-C:两个人的状态都从【未婚】变成了【已婚】。这里隐藏了一致性的约束:男方只能有一个现任妻子,女方只能有一个现任丈夫。 如同数据库唯一索引的约束一样。

隔离型-I:两个结婚跟别人无关,不影响其他扯证的情侣。

持久性-D:除非离婚,不然婚姻状态是持续的。


事务的隔离性由锁机制实现,原子性、一致性和持久性由事务的日志保障。

 

理解了基础概念,我们来做思想实验。假如我们的业务流程因为代码Bug(比如除0)中断了,如何保障数据库一致性约束呢?即我们的异常处理终极目的是保障数据库中状态的一致性,确保系统符合业务规范要求。

 

undo日志

如果状态异常了,恢复现场就OK了。比如驾校练车,那就是一遍一遍地roll back。我们通过undo日志记录事务变更前的状态,如果当前事务处理异常,回滚到原来的状态,就像这个事情没有发生一样。用T代表事务,X代表事务变更的元素,v代表X原来的值。那么<T,X,v>就是日志记录的三个必要字段。 例如:<发工资,张三, 0> 代表发工资前,月光族张三的银行卡上没有钱了。

 

系统处理事务中会有如下关键日志信息:

日志<START T> 表示一个事务的开始。

日志<COMMIT T>表示一个事务的提交,日志<ABORT T>表示一个事务中止。对于一个事务,<COMMIT T>和<ABORT T>只会记录一条。

 

一个事务的处理流程如下:

S1:记录<START T>表示开启一个事务。

S2:记录undo日志:<T,X,v>,表示事务元素变更前的状态。(要求持久化)

S3:改变X元素,使其状态变更。(要求持久化)

S4:记录<COMMIT T>,表示事务提交。

 

当系统出现故障,暂停提供服务并启动故障修复程序。

S1: 反向遍历日志,对于已经有<COMMIT T>的事务,无需处理。

S2:对于没有<COMMIT T>的事务,用undo日志将事务恢复到变更前的状态。

注意,故障修复的每个操作,又构成了新的事务。

 

Undo日志修复流程就引发了两个概念:检查点(checkpoint)和幂等(idompotent)。

检查点:由于系统故障概率一般较低,导致积累的日志规模庞大。如果遍历整个历史日志,故障修复的效率就太低了。所以用检查点机制来记录系统无故障的里程牌。如果出现故障,修复程序回滚到里程碑处就可以停止了。

幂等:如果故障修复时候,也出故障了。怎么办?确保日志修复的操作是幂等的。有了这个限制,重试就解决问题了。 所谓幂等,执行一次和执行多次结果是一样的。《事务处理概念与技术》中,有个非常形象的例子:

“把核反应堆的反应棒向下移动2cm”就是非幂等的,

“把核反应堆得反应棒移动到xx位置”就是幂等的。

 

Redo日志

 

使用undo日志,会引发性能问题。即事务改变的数据写到磁盘前,不能提交该事务。这就意味着,没笔事务操作都至少有3次磁盘操作。所谓按下葫芦浮起瓢。必须有一种新的机制,这就是Redo日志的来源。

 

如果说Undo日志代表一种消极的人生态度,Redo日志就是一种积极的人生态度。颇有不破楼兰终不还的气势。即如果系统出现故障,由于我们已经记录了我们要到达的终点,那么失败重试就可以了,退什么退!

类似于undo日志,redo日志记录事务<T,X,v>代表事务T改变元素X的值为v,v就是新值。

使用redo日志的事务流程如下:

S1:记录<START T>表示开启一个事务。

S2:记录redo日志:<T,X,v>,表示事务元素变更前的状态。(要求持久化)

S3:记录<COMMIT T>,表示事务提交。(要求持久化)

S4:改变X元素,使其状态变更。

 

相比undo日志,COMMIT T提前了。这样的话,事务是否完成依然是检查日志中有没有COMMIT信息,但是这个流程已经不能保障有COMMIT的事务数据固化到硬盘了。这就要求检查点生成时,必须保证数据已经落盘。

故障修复流程也是相当简单:

S1: 找出所有已经COMMIT的事务

S2:使用redo日志重新执行原事务的操作

Undo/Redo日志

Redo日志要求在事务提交和日志记录刷新前,将所有修改过的块保留在缓冲区中,这样可能增加事务需要的平均缓冲区数量。而且,如果数据库元素不是完整的块,undo日志和redo日志在检查点过程中,对于如何处理缓冲区都存在矛盾。所以新的方案由出炉了,就是undo/redo综合体,颇有要你命三千的意味。即记录日志的时候,记录<T,X,v,w>4个值,代表事务T改变了元素X,x的旧值是v,新值是w。

使用undo/redo日志的事务流程如下:

S1:记录<START T>表示开启一个事务。

S2:记录undo/redo日志:<T,X,v,w>,事务T改变了元素X,x的旧值是v,新值是w。(要求持久化)

S3:修改数据库元素

S4:记录<COMMIT T>日志。

这里S3和S4并没有明确顺序要求。

这里就可以根据当前缓冲区的状态灵活控制了,<COMMIT T>的修改数据库元素的操作谁先谁后已经不重要了。

 

故障恢复:

S1: 遍历日志

S2:对于已经COMMIT的事务,使用redo重做

S3:对于没有COMMIT的事务,使用undo回滚

后续

本文简单总结了事务、ACID的理解方式。从思想原理上梳理了undo、redo、undo/redo机制的演变过程,也梳理了检查点和幂等两个概念的来源。后续将分析MySQL, ES等数据库的日志实现,结合实际了解其在工业产品中的落地姿势。

 

 

参考

《事务处理概念与技术》

《数据库系统实现》



©著作权归作者所有:来自51CTO博客作者sbp810050504的原创作品,如需转载,请注明出处,否则将追究法律责任

你的鼓励让我更有动力

赞赏

0人进行了赞赏支持

更多相关文章

  1. 日志分析工具Log Parser介绍
  2. Linux路由/日志管理
  3. insert带来的TiDB集群性能瓶颈排障
  4. Filebeat 收集日志的那些事儿
  5. ES 慢查询日志收集实战总结
  6. 别不信,你可能连 MySQL 的 Delete 都不会!
  7. 自动化运维之日志这件“小”事
  8. 使用Logstash filter grok过滤日志文件
  9. Cosmos DB的5种事物一致性

随机推荐

  1. Android Contacts的使用(一)
  2. 使用Eclipse开发Android时整个工程或第三
  3. GreenDao 的简介和使用
  4. Android开发要看的网站(不断更新中)
  5. 麦子学院Android应用开发工程师视频教程
  6. Android PinnedHeaderListView 详解
  7. Android 状态栏通知Notification(转载)
  8. padding和margin的区别及其属性
  9. [Android][设置最小(大)宽高]
  10. Android(安卓)Activity 和 Task 设计指导