首页 > > > 分布式数据库的故障和常见处理机制
作者:徐文韬
ACID 是事务的四个特性,其中 D(Duration)就是讲的持久性,数据库的一大价值就在于可以有效处理的故障,保证数据不会丢失。随着分布式数据库的发展,部署的复杂度上升,数据库面临的故障场景也越来越多。
接下来,我们看下常见数据中心的故障概率

常见数据中心故障概率表 1

常见数据中心故障概率表 2
《Designs, Lessons and Advice from Building Large Distributed Systems》,jeff dean
除了上述故障,对于分布式系统设计,还有一些额外的网络故障需要考虑
实际上,数据中心也没有想象中的那么稳定,下图是笔者 2017 年11 月 22 日截取的 cloudharmony 监控数据,监控 300 多家数据中心的可靠性情况。

2017-11-22 数据中心监控
可以看到,即使大名鼎鼎的azure,也未能达到宣称99.95%,有兴趣的详细了解的可以看这里
除此之外,再给大家举几个详细的例子。
北欧数据中心部分在定期的常规灭火系统维护中发生了意外,释放出了灭火剂。然后导致了专门用于遏制和安全的空气处理单元(AHU)自动关闭。而受到影响区域的某些系统为防止系统过热对部分机器进行关机和重启,AHU在35分钟后手动恢复,因为系统突然关机导致部分数据需要恢复,系统在7小时后才恢复正常。该事故导致了部分用户的存储服务不可用。
运维工程师定位账务系统变慢这个问题时,想要删除一小部分服务器,结果命令输入错误删除了大批服务器,包括 index subsystem 和 placement subsystem 的服务器,导致 S3 服务从 9:37AM 开始不可用,直到 1:54PM,其中最有意思的是,AWS Service Health Dashboard 系统依赖 S3,因此从故障发生直到 11:37AM,监控页面没有显示故障。这个故障据说弄倒了半个墙外的互联网世界。
2016 年 4 月 13 日 Google Compute Engine 停止服务
全球所有区域的 Google Compute Engine 停止服务,18 分钟后恢复。该故障由一个运维工程师删除一个无用的 ip blocks 引起的,而删除 ip 这个操作并没有合理做配置同步,这个操作触发了网络配置系统的一致性检测,当网络配置系统检测到不一致后,进行了重启,导致服务中断。
光纤挖断后,部分用户无法使用,两小时后恢复。
银行二部(2014)187 号正式发全国文件,对宁夏银行事故的描述大致如下,2014 年7 月 1 日,宁夏银行核心系统数据库出现故障,导致该行(含异地分支机构)存取款、转账支付、借记卡、网上银行、ATM 和 POS 业务全部中断。
经初步分析,在季末结算业务量较大的情况下,因备份系统异常导致备份存储磁盘读写处理严重延时,备份与主存储数据不一致,在采取中断数据备份录像操作后,造成生产数据库损坏并宕机。因宁夏银行应急恢复处置机制严重缺失,导致系统恢复工作进展缓慢,直至 7 月 3 日 5 点 40 分核心系统才恢复服务,业务系统中断长达 37 小时 40 分钟,其间完全依靠手工办理业务。
看一下数据中心网络的互联图

图上任何的硬件设备都可能发生故障,从各个主机,交换机到网线。
我们尝试以故障域对故障做一个简单的分类。所谓故障域,就是会因为一个故障而同时不可用的一组组件,常见的故障域包括:
不同组件发生故障的概率是不同,google一项研究表明,在36 °C和47 °C范围内运转的磁盘,故障率最低,随着时间的发展,磁盘故障率也逐渐提升,第一年只有1.7%,第三年达到8.6%。
现在也有很多研究,将大数据和人工智能引入了磁盘故障预测领域,取得了不错的成果。
数据库会为数据修改记录日志,日志记录了数据的变化,根据不同的日志用途,可以分为 redo 日志、. undo 日志、redo/undo 日志,现在流行的是 redo 日志。
看一下 postgresql 日志的结构:

根据不同日志记录方式,可以分为如下两种类型:
数据库日志系统有两个重要的原理:
日志系统是系统软件内广泛使用的技术,不仅仅是数据库,日志代表了系统的改变,他可以用来恢复/备份,也可以用做通知系统,掌握了系统的日志流,就相当于掌握了系统的整个状态,日志可以更抽象的理解为日志+状态机,通过不断的重访日志,改变状态机的状态,可以通过传递日志将状态改变传递到整个系统的各个角落,关于日志系统,笔者见过的最好的一篇文章是 The Log: What every software engineer should know about real-time data's unifying abstraction,非常推荐一读,日志即一切。
日志代表了系统所有的变化,如果数据大小是从 0 开始扩展到 100G,那么日志至少也要有 100G,甚至更多,而日志的增长和用户做出的改动是正相关,任何系统也无法存储无限增长的日志。
回收日志所占存储空间是必然的选择,日志收回有两个好处:
实际上,对于 MVCC 机制实现的数据库,因为日志回收和事务提交没有关系,所以可以严格的将日志控制在指定大小,为系统运维提供方便。
上文所说数据恢复需要一份完好的数据作为起点,其实原因就是最开始的日志被回收了,如果能保留从初始状态到最新状态的所有日志,那么光靠日志也可以恢复系统,但是很明显,任何系统也不能保留所有日志。
checkpoint 用于回收日志,checkpoint 的流程如下:
上面是常见的做 checkpoint 方式,这种方式也叫做全量检查点(full checkpoint),这种方式实现简单,但是明显 checkpoint 是一次 IO 峰值,会造成性能抖动。
还有一种做 checkpoint 的方式,叫做增量检查点 (incremental checkpoint),过程如下:
这种方式化 checkpoint 为后台写操作,做 checkpoint 时只需要打点即可,消除了 IO 峰值,有助于平稳数据库性能。
数据库页面大小和磁盘扇区大小往往不同,因此当页面刷盘时,如果系统断电,可能只有部分页面刷盘,这种现象,我们称之为 torn page,这个页面相当于被彻底损坏,而日志 replay 需要一份完整的数据做起点,此时是无法恢复的。
处理半写有几种方式:
这里有几个例外:
磁盘写满这种问题只能通过运维手段解决,因为数据库事务提交必须写日志,如果无法写日志,那么任何事务都不能提交,相当于停库,因此应对磁盘故障一般是通过监控,在磁盘空间即将不足时提前预警。
如上文所说,数据库恢复需要一份完整的数据和日志,因此,如果数据或者日志遇到了磁盘损坏,日志系统是无法恢复,只能依赖其他的方式了。
按照级别划分,常见的备份方式有:
很显然,从上述操作的开销从大到小排列依次是,全量备份>增量备份>日志归档。数据库运维时往往会结合这三种方式,以达到缩小故障 RPO 的目标。
amazon aurora 实现了近实时备份的功能,备份时间不超过5分钟。
如果某台机器因此各种原因发生了故障,比如 cpu 烧毁,内存故障,或者操作系统 bug,甚至被炸掉了,都可以使用备份的方式恢复。
但是通过备份恢复往往耗时较长,不能满足业务连续性(Business Continuity)的需求,除了备份以外,数据库都支持单机热备,以及支持只读查询的备机。
很明显,备机要根据故障域和客户的要求,进行反亲和部署。
master-slave(-cascade)

每个主机可以挂多个备机,每个备机可以挂多个级连备,这是当前传统数据库的常见部署方式。postgres 甚至支持多级的级连备(次级连)等等,但是不是很常用。这种部署方式可以有效的处理单机故障。作为支持只读操作的备机,可以有效的分摊读负载,这是一种有延迟的读操作,本身也是满足相应隔离级别的,但是和主机放在一起考虑的话,并没有一致性可言。
根据主机事务的提交时机,有几种事务提交级别:
这三种提交级别,主机性能越来越差,一般而言,同城备机采用第二种方式,异地备机使用第一种方式。

共享磁盘方案依赖共享存储,备机只读不写,虽然备机不写盘,但仍然需要不断的在内存 replay 日志,以便主机故障后能快速升主。
很明显,share disk 方案性能类似数据单机,而且 RTO<1min,RPO=0。但是受硬件限制,sharding disk 方案只适用于同城。
技术是螺旋式前进的,在分布式计算中,share disk 的思想也很流行,很多系统依赖分布式文件系统/存储系统,在其上构建基于 share disk 的计算系统,比如大数据领域久负盛名的 hadoop,还有 OLTP 领域的新力量 aurora,还有 newsql 领域的tidb+tikv。


master-master架构服务也很多,日渐成为主流,目前低一致性的大数据系统几乎都是多主架构,笔者对大数据不够熟悉,这里只列一致性较强的一些数据库系统
master-master 架构的系统,总有数据是可以提供服务的,因此可靠性更高,这是当前分布式系统的主流方案。

paxos/raft 是当前主流的分布式复制协议。
paxos 协议精确定义了在分布式系统下达成共识的最小条件。关于 paxos 的原理可以参考这篇文章《一步一步理解 Paxos 算法》。
paxos 是分布式系统的核心之一,关于这个算法给予再多的赞誉也不为过。paxos 协议有很多变种,他的应用也是有一些主要注意的地方,《SRE: google 运维解密》内 23 章讨论了 paxos 应用的一些场景和情况,有兴趣的可以了解一下。
什么样的系统调用是可靠的?几乎没有,c 语言中最容易出问题的系统调用就是 malloc,因为使用的太广泛了,在有些较深的代码逻辑内,一旦申请内存出错,处理相当棘手。在某个重要的内闭的模块中,首先申请足够的内存是一个比较好的做法,相当于半自管理的内存。
其次容易出错的系统调用是和 IO 相关的调用,比如 IO 调用出错更难处理的是 IO 变慢,读写操作的速度在故障时是完全不不能有任何期待的,几十秒,几分钟甚至更久都很正常,所以,如果自旋锁内包含一个 IO 操作,这个系统离崩溃就不远了。
凡是跨网络的操作,对网络不要有任何期待,在操作前,释放所有不必要持有的资源,并做好调用出错的准备,并为其设定一个超时时间,改为异步模式是一个好选择。
如果由于外部破坏或 bug 等原因导致数据损坏,可以通过 checksum 的方式探查,checksum 一般在如下两个时机应用:
进程卡死是不可避免的,当系统 cpu 被占满时,或者由于某些 bug,可能导致某些关键进程得不到调度,导致其无法传递某些信息,某些故障可能对整个系统都是致命。
如何侦测进程/线程卡斯,有两种常用的做法:
对于 bug 导致的程序卡死,往往杀掉进程重新拉起可以解决。
系统性能是很难做到线性提升的,对于数据库来说,更是不可能,对于大部分数据库系统来说,性能首先随连接数增加而提升到某个点,继续增加连接数,往往性能会下降。

上图是 mysql8.0.3 CATS 特性的性能测试结果,明显可以看到超过 64 连接后,性能随着连接数增加而降低。
这也是数据库系统一般都会做连接池的原因。
过量的压力可能导致系统崩溃,比如上图,FIFO 的调度方式下,512 连接,性能降低接近 5 倍。因此在大型系统中,连接池和优先级队列是一个好设计,可以方便对系统的压力进行有效控制,同时通过监控队列长度,可以直观看到这部分系统的压力和处理能力。
主流的高可用方案有两种,一种是两地三中心,一种是异地多活。
对于传统数据库,两地三中心的方案比较常见,常见的部署是同城两中心,异地一中心。
两地三中心是一个初级和简单的部署架构,一旦主库发生故障,异地中心很难顶上,只能起到冷备的作用:
还有一种公有云上可靠性更高的方案,如下图

有钱任性。当然,公有云海量部署可以摊低成本,在私有云上,这种方案更贵。
paxos 并不适合两地三中心的部署,paxos 协议要求有 3 个对等的故障域,并且能处理一个故障域的故障,两地三中心故障域并不对等
异地多活方案主要要考虑如下几个问题:
数据中心是及其昂贵的,一旦整个数据中心发生故障,作为服务整体服务质量不降级是不可能的,如何优雅降级并保证尽可能多的数据可用,这是分布式系统需要重点考虑的问题。

以 google spanner 为例,时间戳分配是分布式的,不需要中心节点,数据可以由用户选择部署方式,横跨数据中心越多,性能越差,可靠性越强,区域故障完全自闭,不影响其他部分。
异地多活是一项系统工程,在这个庞大工程里,数据库只需要做好自己的事就可以了。
原文作者:;如有侵权,请联系管理员删除。