共计 6234 个字符,预计需要花费 16 分钟才能阅读完成。
背景
高可用是构建分布式系统的基石。一方面,出于成本考虑,分布式系统往往采取比较廉价的硬件,其可靠性相对于小型机、专有硬件有很大的不足,而分布式系统的规模一般比较大,假如硬件的可靠性只有三个 9(99.9%),一个 1000 台机器规模的集群每天将面临 1 台机器宕机的风险,在如此大规模的情况下,存储介质,比如硬盘可能会随时都有损坏,结点之间的网络可能随时都会有抖动,机房可能局部或整体断电,地区或数据中心可能会出现不可用,如果不在设计时考虑这些问题,当这些情况出现的时候,系统将很快处于不可用的状态;另一方面,分布式系统的程序在设计与实现上也更为复杂,软件上既要容错硬件的不可靠,同时软件自身也有可能有问题,在对外部环境容错的同时需要考虑对软件 BUG 的容错。
OceanBase 在设计之初就充分考虑了高可用问题,确保 OceanBase 有能力在这些异常出现后,仍然能最大可能的提供服务。
高可用的基本策略
冗余是实现高可用的通用方法,为防止数据丢失,将数据冗余多份;为防止系统中某些节点故障,对某些功能节点进行冗余。冗余带来的好处是可以抵御某些节点的损失,而带来的坏处则主要是成本、性能和一致性问题。
成本主要包括额外的存储、网络、机柜等硬件成本,这是构建高可用分布式系统的必不可少的开销,其总体成本较专有硬件仍然要低,因为专有硬件实际上也需要在内部对某些模块进行冗余以获取高可用性。性能和一致性问题则是高可用分布式系统必须要处理问题,这两个问题直接影响整个系统正确性、延时与吞吐量。
在传统 myql 或 oracle 中,我们往往通过添加备机来实现高可用,且为了实现高性能和高可用,一般会使用“最大可用”模式:
主机尽力将数据同步到备机,而不管是否同步成功,当主机挂掉以后,将备机升级成主机以继续提供服务,这就意味着如果主机在宕机前有数据没有同步到备机,要么通过某种特殊的手段将数据从宕掉的主机同步到备机,要么就接受暂时的数据不一致而继续提供服务,在这种情况下,如果出现主机永久损坏,则会造成数据丢失:
为了解决这个问题,可以使用最大保护模式(早期的 MySQL 版本不支持),即主机将日志同步到备机成功后再应答客户,这样会引入另外一个问题,即备机故障或网络抖动会导致失败,影响可用性;小微引入了共享存储,将数据库 redo log 放在共享存储上,主机宕机以后,备机需要确保主机所有的数据都已经同步才能对外提供服务:
这样在主机宕机时,备机作一些额外的检查后升级为主机继续提供服务,这样可以确保数据一致性,但引入了共享存储。传统的主备模式还有另外一个问题是主备之间无法通过自身决出主备,需要人工切换或者使用一个第三方组件:
但是又引入了 HA 模块的稳定性问题,如果 HA 模块和主机的网络不通,HA 将不能识别主机是活着还是网络有问题,此时 HA 如果进行切换而主机还活着则会造成后果很严重的双主问题。
OceanBase 高可用策略
故障可以分为单机故障 (磁盘、内存等硬件介质损坏或单台软件 Crash 都属于单机故障),机架 / 机房故障(比如整个机架或机房的电源断电) 以及地区 / 数据中心 (比如地区地震造成该区网络隔离) 故障,一般来说,故障单位越小,出现频率越高,而除非自然灾害,一个地区出现故障的概率极小,故障单位越小,实现高可用的难度和成本越低,故障单位越大,由于引入环境、距离等因素,实现高可用的难度和成本会呈指数倍增长。比如为了预防单机故障,只需要在本机房预备冗余节点,故障时通过某些技术方案,可以做到实时切换;而为了预防数据中心故障,则需要在另外一个地区预备冗余的数据中心,在故障时由于通信距离等原因,基本上无法做到无损切换。
OceanBase 虽然在设计之初就考虑到了硬件和软件的不可靠,但 OceanBase 的高可用并非一蹴而就,在实现过程中,为了快速实现或者绕过某个暂时无法攻克的技术难点,会进行综合权衡,只实现一些出现概率较高的基本高可用策略,而通过人肉或其它手段来保证在某些很难出现的灾难出现后可以尽快恢复。然后通过逐步迭代,逐渐完善,将高可用的范围提高,比如 OceanBase 最初的时候只考虑单机故障的高可用,到目前为止已经可以实现同城 IDC 之间的容灾。
分布式系统为了设计与实现的简单,往往会在系统中设置一个全局单点,来负责相对比较轻量的管理任务,OceanBase 中的 rootserver 就是这样一个角色;它主要负责集群中其它角色的管理并作为用户的入口,通常其压力不高且无本地数据需要存储,所需信息都可以通过其它角色的汇报来重建。
而作为一个分布式数据库,OceanBase 面临着两个很难解决的问题:数据一致性与分布式事务,在早期的 OceanBase 版本中,采取的策略是暂时绕过这两个问题,等技术积累到一定程度后再回过头来解决,所以在 OceanBase 中另外增加了一个单写入节点,这个节点的压力很高,数据无法通过其它节点来恢复,我们需要保证这些单节点的高可用。另外一个是保存基线数据结点的高可用,这些结点被设计成可以弹性伸缩,本身具备高可用属性,但仍然需要考虑磁盘故障以及数据副本之间的一致性。我们会在下面的章节分别描述对这两类节点的高可用策略。
系统单点
在早期 OceanBase 的版本中,主要依靠主备来为单点提供高可用,使用两台机器,其中的一台角色为主的机器对外提供服务,另外一台机器做为热备,当主机挂掉后,备机切换为主,继续提供服务。
如前所述,这种“最大可用”模式的主备机制主要有两个问题:第一个问题在于这两台机器无法通过自身来决出主备,必须要依赖于一个第三方组件,早期我们使用了 HA(linux-ha.org) 来做为仲裁组件,HA 使用 VIP 机制,两台机器共享 VIP,同一时刻 VIP 只会加载在其中的一台机器,VIP 会提供给外部应用程序作为 OceanBase 集群的入口地址,即 VIP 加载在哪一台机器上,该机就会作为主对外提供服务,程序可以通过不断检测 VIP 是否存在来判断本机是否为主机,当 HA 通过我们提供的检测程序检测到主机故障后,就会将 VIP 切换到备机,此时外部请求就会路由到原来的备机,原来的备机检测到 VIP“飘”到本机后,会将自己的角色置为主:
使用 HA 主要有几个问题:
- HA 为了防止网络抖动带来的误判,要求将两台机器使用网线直连,这就限制了两台机器只能放在一个机柜,如果整个机柜断电,则服务不可用,这样就不能抵御机柜以及机房的容灾。
- 数据一致性不能保证,一般不要求使用 HA 的角色持久化特别重要的数据。其数据应该能通过其他角色的汇报而重建。
另外一个问题在于这种机制无法保证数据不丢失,某些极端情况下需要停机恢复,如果有机器永久损失,则可能会造成数据的丢失,在某些业务上可能无法接受。
而 Updateserver 是 OceanBase 中至关重要的节点,其数据丢失直接影响用户,也不能通过其它类型节点来重建,所以 Updateserver 最早抛弃 HA 模式,而改为通过 Rootserver 来选主:
Updateserver 每次写入都会生成一条日志,每条日志有一个惟一且单调递增的日志号,各 Updateserver 向 Rootserver 汇报自己的日志号,Rootserver 选取日志号最大的 Updateserver 为主并发放租约,备 Updateserver 同时需要向主 Updateserver 注册,由主 Updateserver 发放租约。Updateserver 使用一主多备的形式,每次写入必须要写入多数派个节点成功才能应答客户,写入请求首先发送到主 Updateserver,主 Updateserver 生成日志并同步到备机,收到多数派个成功回应以后应答客户。如果收不到足够多的回应,则不会应答客户端,该条写入可能生效,也可能不生效。
由于要求写入多数派个节点才算成功,所以主备间的网络延迟不能太高,目前 OceanBase 要求 updateserver 主备分布在同城的不同 IDC,如果采取一主两备的模式,最大可以容忍一个同城 IDC 故障。
当某一台机器同步日志失败时,主机会将其剔除在恢复之前不再向其同步日志,这对网络要求很高,如果网络连续出现抖动,则会造成可用性问题。在最新版本 OceanBase,将同步日志的方式也改为 Paxos 方式,一条日志只需要写到多数派个结点上成功便为成功,不需要各台备机顺序回应,进一步容忍短暂的网络问题。
虽然 Updateserver 去掉了对 HA 的依赖,但 Rootserver 仍然需要 HA 来选主,由于 HA 无法部署在两个 IDC,所以我们对 IDC 之间的容灾使用的策略是在另外一个 IDC 部署一个备集群,在主集群出现故障时,通过人肉的方式将备集群切换为主来提供服务。
基于这个原因,OceanBase 在 0.5 里彻底取消了基于 HA 的主备机制,而是通过使用类似 paxos 的算法来进行选举:
让程序自身投票来决出主备,当一台机器得到多数派的认可,它即可以成为主,这样系统能容忍一定数量节点的不可用,比如,如果是 2 台,则不能容忍有机器宕机,3 台则可以容忍一台机器宕机,3 台机器可以部署在不同的机房以容忍机房故障。
Updateserver 仍然通过 Rootserver 来选主,但这样也存在一个问题,当 Updateserver 和 Rootserver 同时故障的时候,Updateserver 必须要等 Rootserver 恢复完成后才能恢复,增加了故障恢复的时间。在后续的 OceanBase 版本中,将去除 Updateserver 这个单写入节点,并将其选主的权力下放到自身,摆脱由 Rootserver 选主的局面。届时 Rootserver 的工作会更为简单,单点不会成为 OceanBase 的瓶颈。
基线数据
Rootserver/Updateserver 是通过冗余节点来进行容灾,备节点一般不提供服务或只提供有限的服务,基线数据则是通过冗余数据来实现高可用与扩展服务能力。通过冗余 3~6 份数据来提供更多的服务能力。冗余的数据不能存储在相同的机器上,以避免机器宕机后损失可用性。同时在可能的情况下,数据需要分布在不同的机架上,以抵御整机架断电或断网,OceanBase 在早期的实现中,为了简化实现与对机器分布的要求,未考虑数据分布在不同的机柜,曾出现过整机架断网而造成服务不可用。
基线数据的副本数决定了一个集群同时有多少台机器可以宕机,如果使用三副本,则同时可以有两台机器宕机,每个基线数据结点都和 Rootserver 保持心跳,当该结点宕机以后,rootserver 会检测到并根据目前系统中所拥有的副本数量启动复制,为了避免因网络抖动所带来的不必要的副本复制,我们设定在安全的情况下(比如剩余副本数大于 1) 可以容忍副本丢失一段时间(比如 8 小时),当副本丢失超出该时长后才启动复制。
副本数的选择和集群中机器的数量、单机数据量以及数据恢复速度相关,一般情况下会选择至少 3 个副本,因为 2 副本情况下,如果出现一个副本丢失,集群需要立即启动复制,而此时集群可能正处于请求高峰期。阳老师有一个关于副本数选择的计算方法:
“假设总共有 N 个节点计算机,它们的平均无故障时间都是 M,云计算系统对一个数据副本丢失并进行复制的时间为 T,则一台计算机在 T 时间内出故障的概率是 T /M,不出故障的概率是(1-T/M):
N 台机器在该时间内都不出故障的概率是 (1-T/M) 的 N 次方;
N 台机器在该时间内恰好有 1 台出故障的概率是:(1-T/M)的 (N-1) 次方T/MN;
N 台机器在该时间内恰好有 2 台出故障的概率是:
(1-T/M)的 (N-2) 次方T/MT/MN(N-1)/(2*1)
因此,N 台机器在该时间段内至少有两台机器故障的概率是:
P2(N, M, T)=1- 都不出故障的概率 - 恰好 1 台出故障的概率
因此,N 台机器在该时间段内至少有两台机器故障的概率是:
P3(N, M, T)=1- 都不出故障的概率 - 恰好 1 台出故障的概率 – 恰好 2 台出故障的概率
因此假如 N =1000,M=50,000 小时,T=600 秒,则
P2 (N=10 台,M=50,000 小时,T=600 秒) = 5.0*10 的 -10 次方;
P2 (N=1000 台,M=50,000 小时,T=600 秒) = 6.1*10 的 - 9 次方;
P2 (N=5000 台,M=50,000 小时,T=600 秒) = 1.4*10 的 - 4 次方;
P3 (N=10 台,M=50,000 小时,T=600 秒) = 4.5*10 的 -15 次方;
P3 (N=1000 台,M=50,000 小时,T=600 秒) = 6.2*10 的 - 9 次方;
P3 (N=5000 台,M=50,000 小时,T=600 秒) = 7.6*10 的 - 7 次方;
可以看出,当机器数量达到 5000 台时,至少 3 台机器出故障的概率低于百万分之一,而至少两台机器出故障的概率高于万分之一。因此采用 3 个数据副本时数据有比较高的可靠性。”
并计算出一个表格,假设故障时其它服务器以 25MB/ s 的速度恢复丢失数据:
MTBF 为机器的平均无故障时间,从表中可以看出三副本同时丧失的概率是极低的。
基线数据的另一个较为普遍的异常为磁盘损坏,OceanBase 存储基线数据的角色为 ChunkServer, ChunkServer 并未使用 RAID 方式来使用磁盘,一块磁盘损坏就意味着永久损失该盘上的所有数据,需要 Rootserver 从另外的机器上进行复制。当 ChunkServer 检测到该磁盘出错 (读取或写入失败) 时,会主动将该盘剔除并上报,让 Rootserver 启动复制程序补足副本。
基线数据需要和增量数据合并而产生新的基线数据,这个过程是由每台 ChunkServer 各自完成自己所负责的数据分区,即相同数据的几个副本会各自己完成这个合并过程,为了保证各台 ChunkServer 合并出来的新基线数据是一致的,每台 Chunkserver 在汇报副本信息的时候需要同时汇报校验值,以检查副本是否一致,如果出现不一致的情况,则是软件有 bug 或数据有问题,ChunkServer 保留两个版本,解决问题后回退到上一个版本进行重新合并。这种问题出现一般都是软件 Bug,根据之前的经验,出现这种情况的时候,用户有可能已经读到不正确的数据,而造成这种问题的主要原因是 Updateserver 节点的数据不一致,我们通过改造 Updateserver 的日志同步方式 (由主备改为一主多备且要求多数派成功) 和加强校验后,这个问题已经得到杜绝。
跨数据中心容灾
到目前为止,OceanBase 可以部署在同城的不同 IDC 并容忍少数个 IDC 故障。OceanBase 一般会在同城选择三个不同的 IDC 进行部署。目前还无法做到跨数据中心的容灾。主要原因是由于通信距离的增加,异地数据中心之间的网络延时较高,无法做到同步复制数据,而通过异步的形式进行复制则无法做到无损容灾,我们目前通过一些手段,比如实时拷贝数据库的 redo log 到异地数据中心,可以做到最坏情况只丢失几秒的数据。
总结
相较于传统的主备模式,OceanBase 可以容忍少数派的节点损坏不中断服务且不丢失数据,为很多需要高可用且不能丢数据的业务提供了共享存储以外的解决方案。
OceanBase 仍然在高速发展和逐步完善,包括可用性在内的各方面都在稳步推进,我们在目前也在尝试将 OceanBase 以云的形式提供给用户,我们接下来将介绍 OceanBase 云平台,请关注下一篇《OceanBase 云平台简介》。
本文作者:KB 小秘书
阅读原文
本文为云栖社区原创内容,未经允许不得转载。