不惧宕机,数据库高可用理论与实践

delphi6fans
发布于 2022-5-18 18:04
浏览
0收藏

数据库是现代很多与数据相关的程序正常运行的必要组件,数据库的正常运行会直接或间接地影响到程序的可用性,高可用是分布式系统架构设计中必须考虑的因素之一,它通常指,通过设计减少系统不能提供服务的时间。本文将深入了解数据库系统的高可用,并窥探Greenplum是怎样实现高可用的,如何为企业的数据保驾护航。

 

一、高可用简介

 

首先我们介绍一下高可用的环境,下图是一个简单的网络应用结构。用户通过互联网访问网络服务器,网络服务器访问的数据库里包含了各种各样的数据。当数据库不可用时,服务器也得不到其需要的数据,则整个服务都会被中断停止。 

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

那么如何避免此类问题呢?如何做到即便数据库出现故障,也依旧能够访问数据库的服务呢?这就是我们今天要讨论的问题。 

 

针对这种问题,一般会有两种方式。

 

1. 单机做到无故障

 

即不管发生任何问题,单机都能够正常的工作,或是有极小的概率会发生崩溃。但单机做到无故障,本身会有很大的局限性。

 

  • 缺陷故障的避免非常困难。做到无故障,不但需要避免硬件故障,还需要避免系统软件的缺陷和数据库软件本身的bug。避免所有的这些缺陷故障本身就是一件非常难做到的事情。

 

  •  可用性的极限。随着软件的bug的减少,成本也会随着可用性增加成倍的增加,很有可能会高到难以接受的程度。

 

地理因素。单机只可能部署到同一个机房里,如果发生一些无法避免的天灾人祸,就更不可能做到单机的无故障。

2. 假定单机会出现软硬件故障

 

面对单机无故障的缺陷,我们往往会希望用多台机器来提供服务,即假设单机出现故障,并且这种错误可以被接受时,用多机去提供数据库服务。此时,只要有一台机器可以工作,整个数据库的服务就不会被中断,可靠性也会大幅提高 。

 

下面是关于多机提供数据库服务可靠性的一个公式。我们可以看到单机的可用性越高,备用的节点数目越高,则可靠性 越高。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区
 二、高可用的一般性原理

 

下图中,我们用两个节点来做数据库服务。其中,当上面一个节点发生崩溃时,服务器会访问下面的数据库,则整个网络服务都可以正常运行。
 不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

 从用户角度来看,采用多机需要满足两点条件。

 

  1. 切换时间足够小,可以接受

 

如果存在故障的节点,服务器需要被切换到没有故障的数据库上,切换时间需要足够小。如果说切换时间太久,数据库系统也是不可用的。

 

2.数据的一致性

 

新节点和老节点的数据需要一致,这一点是非常重要的。

 

接下来,我们将介绍多节点数据库提高可用性的三大组成部分。

 

1.数据复制

 

为了满足前面提到的第一个条件,保证切换时间足够小,需要在运行时,数据从一个数据库节点实时地复制到另一个节点上面。即不能在数据库停机的时候进行复制,而是需要数据库边运行边复制。

 

2.数据同步时施加限制

 

为了满足条件的第二点,需要在数据同步的时候施加一些限制,来保证用户看到的数据是一致的。 

 

3.检测故障并切换到新的可用节点

 

此外,我们还需要一个检测故障并切换到新的可用节点的机制。假定存在一个节点是可用的,这个对外提供服务的节点被称为主节点。其他节点都被称为备节点。这里就存在主从结构或主备结构,这里我们不做赘述,大家感兴趣可以留言,我们将在后面的文章详细描述。

 

三、日志复制与数据一致性

 

前文中,我们介绍了实现高可用的三大组成部分。下面我们将逐一详细介绍。

 

1. 数据复制

 

第一部分就是数据复制,也是最简单的组成部分。如今,主流的数据库一般都采用事务日志流来进行数据同步。下图中,左边在运行的数据库把它产生的事务日志流,通过网络的形式传输到它的备用节点上。备用节点接收到事务日志流后,会把事务日志流进行重放。即primary上的一个事务日志系列,通过网络传输被secondary接收到,secondary通过重放就可以得到相同的数据。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

2. 数据的一致性

 

数据的复制只保证了数据可以从主节点传输到备节点上,但是传输的过程仍存在延迟,并不能够保证在任意时间点上主备节点的数据没有冲突。而这里提到的一致性就是指用户从主备节点上看到的用户数据是完全相同的。

 

这里,我把数据的一致性简单的分为两类,

 

  • 强一致性:如果事务A在主节点上完成,那么它在备节点上也已经完成
  • 弱一致性:如果事务A在切换到备节点后,它的可见性不变

 

数据库在运行的时候,任意时刻都有可能发生崩溃。假设事务A在主节点上刚完成,还没来得及传输到备节点上,而刚好发生断电,此时事务A在备节点上可能不会被完成。因此第一类的数据一致性是很难实现的。

 

从而,就存在了一个弱化的一致性,也就是第二种一致性。

 

在介绍同步事务日志的逻辑之前,我们先介绍一下两个概念,

 

  • 事务的完成:是指事物的提交,并且更改已经持久化。
  • 事物的可见:隐含事务已提交,且当前快照对事务可见。

 

此外,我们还需要了解一下同步事务日志的类型。同步事务日志的类型主要有三种,

 

1. Write: replication wait for remote write xlog(不可靠)

 

第一种类型表示主节点需要等待事务日志同步到备节点上,且备节点只需要写入日志即可。这种类型不可靠,这是因为在做写操作时如果说仅调用write操作,并不能够保证事务日志真正的在磁盘上可见。如果在写的过程中,备节点也出现故障,则这个写操作的日志不会落盘。 

 

2. Flush: replication wait for remote flush xlog(可靠)

 

第二种类型表示主节点传输事务日志到备节点上,备节点需要把事务日志刷盘到本地磁盘上。

 

3. Apply: replication wait for remote apply xlog(可靠)

 

第三种类型是指就是主节点的事务日志必须传输到备节点上,且备节点做完重放操作。

接下来,我们来讲解同步数日志的逻辑。这里,我们将以Flush的同步方式为例来介绍一下数据的一致性是怎样做到的。

 

日志的同步过程如下:

 

  • primary写入(包含刷盘)本地commit日志记录(事务完成)
  • primary传输commit日志记录给secondary,并等待对方确认
  • secondary写入(包含刷盘)接收到的commit记录,并发送确认。
  • primary收到commit确认
  • primary返回结果给用户

 

下图的例子中,Primary在本地,把commit记录进行了刷盘。接着primary把它本地的 commit记录发送给secondary,并等待secondary发回确认。此时,如果 primary crash了,secondary并没有收到来自primary的commit记录。如果此时把secondary做一个提升,也就是说用secondary作为primary对外提供数据服务,就会遇到问题。

 

第三列的query中,第一个read操作读取了刚刚提交的事务。由于read操作是在commit操作之后,因此第一个reader的操作是可见的。而由于第二个read操作会读取来自secondary的节点上的事务,但在secondary节点上,该事务并未完成。因此第二次读操作时,用户看到的数据不可见,此时就会发生数据的不一致。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

从上面的例子中,我们可以看到,当第一次的read操作是不可见的,且第二次操作也不可见,也是满足一致性的。这里我们对前面的操作逻辑做一个简单的修正。

 

修正同步逻辑:

  • primary写入本地commit日志记录(事务完成)
  • primary传输commit日志记录给secondary,并等待对方确认
  • secondary写入接收到的commit记录,并发送确认
  • primary收到commit确认
  • primary将事务对其他查询可见
  • primary返回结果给用户

 

具体的操作如下图

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

首先primary刷新本地的commit记录,接着primary发送commit记录,等secondary的确认。secondary收到后刷新commit记录,并回复primary。primary收到commit确认后,刚刚提交的事务才对其他的事务可见。 

 

最右边的一列中,第一个read所读取的事务,虽然已经提交,但是仍对当前读操作的事务 不可见。因此第一次读也是不可见的。在第二次读之后,由于事务已经提交,且已经对其他的快照可见,因此第二次读操作对刚才的事务是可见的。那么,此时如果发生崩溃,会怎么样呢? 

 

在传输commit过程中,如果primary发生了crash,由于primary没有收到来自secondary的确认,刚刚提交的事务并不会对查询操作可见,也就是说第一次read操作不可见。第二次读操作,由于事务在secondary上并没完成,所以第二次读操作,从secondary上面读后发现事务仍然是不可见的。从用户的角度来看,数据是一致的。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

前面提到的都是一阶段提交,在Greenplum里采用的大多是两阶段提交,两阶段提交的 xlog序列一般是先产生一些写的 xlog,最后会有两个操作,首先是prepare transaction,完成之后是commit prepared。那么prepare transaction是否需要同步呢?也就是说 primary在写入prepare transaction的事务日志的时候,是否需要等待 secondary的确认。

 

两阶段提交(Two Phase Commit,2PC)

 

Begin;

W(x1);

W(x2);

Prepare Transaction;

Commit Prepared;

 

prepare transaction操作需要等待其他节点事务已经准备就绪,下一次commit prepare的时候可以成功。如果有多个节点并没有做prepare的操作,对这多个节点同时执行commit操作,就会出现有的节点commit成功,有的commit失败的情况。在分布式事务的环境下,就不满足事务的原子性了,也就是不一致了。如果在commit prepared阶段,主节点发生故障,且备节点提升为主节点,此时,master/coordinator可能已经提交了事务,那么QD/DTX recovery进程会尝试请求segment再次执行commit prepared。在执行commit prepared操作时,事务日志(直到prepare transaction)必须已经传输到备节点上了。因此在prepare的时候仍需要同步事务日志。

 

两阶段提交同步事务日志逻辑:

  • primary写入本地prepare日志记录
  • primary传输prepare日志记录给secondary,并等待对方确认
  • primary接收prepare记录的确认
  • primary写入本地commit prepared日志记录(事务完成)
  • primary传输commit prepared日志记录给secondary,并等待对方确认
  • primary接收commit prepared记录的确认
  • primary将事务对其他查询可见
  • primary返回结果给用户

 

下图是Prepare Transaction和Commit Prepared流程图。首先,primary在本地prepare transaction,等待secondary发来的确认。primary收到secondary确认后,在本地提交事务commit prepared。Primary在本地提交后,再等待secondary的确认。收到secondary的确认后,本地提交的事务才会对其他的快照可见。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

下图的参考的代码,在Postgresql或Greenplum下均可以看到。

 

参考代码:

CommitTransaction():
  latestXid = RecordTransactionCommit();
    XLogFlush(XactLastRecEnd);
    TransactionIdCommitTree()
  SyncRepWaitForLSN(XactLastRecEnd, true);
ProcArrayEndTransaction(MyProc, latestXid);

 

四、Greenplum的高可用实现FTS

 

讨论完节点之间的数据复制和节点之间如何保持数据的一致性约束,接下来,我们将介绍一下提高高可用的第三个组成部分:如何做故障检测和切换。Greenplum里面的FTS功能就是针对这一需求而设计的。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

FTS会周期性的进行故障检测,主要有三大特性:

 

1. 检测节点连通性(PROBE)

 

FTS会连接到primary节点上,mirror会通过它和primary之间的replication进行连接,间接的告知FTS状态。FTS进程周期性的向所有的primary节点发起连接进行探测。primary会检查replication状态,并且更新自身和mirror的状态,并发送给FTS进程。FTS收到后,会更新节点状态和FTS状态机。

 

2. 提升mirror节点为新的primary节点(PROMOTE)

 

如果FTS进程超时未收到primary的回复FTS会交换primary和mirror的角色,更新gp_segment_configuration catalog。接着,FTS向原mirror节点发送FTS_MSG_PROMOTE的消息,mirror节点会执行提升逻辑,成为primary。

FTS在做mirror提升前会做一些检测来确保提升前primary和mirror之间的同步和mirror的正常运行。检测primary和mirror同步的同步信息被存储在gp_segment_configration里。在做同步之前,会在mirror上创建一个replication slot,主要用来防止Xlog被删除进而不得不执行全量恢复。做增量恢复会用gprecoverseg,做全量恢复是用gprecoverseg -F。

 

3. 断开segment之间的主备同步(SYNCREP_OFF)

 

FTS会断开segment之间的主从同步,这个操作是非常有必要的。否则日志同步会阻塞写操作类型的事务,执行这种操作有两种情况,一种情况是提升mirror节点,另一种情况是mirror节点断开 replication。

 

五、Greenplum Master节点的高可用

 

最后一部分中,我们来介绍一下Greenplum master节点的高可用。上一章节,我们了解到FTS只对segment进行了探测,无法在master节点故障时发生切换。因此,在master节点发生故障时,也无法做切换。计划于2022年年初发行的Greenplum 7版本中会有一个新的特性:Greenplum Master Auto Failover。在Greenplum7版本里,Master将被改名为Coordinator,因此,这个特性也将被称为Greenplum Coordinator Auto Failover。

 

对 feature进行调研的时候,把它与PG Auto Failover进行了一些比较,发现他们的设计和架构上有很大的相似之处,因此最终决定在基于PG Auto Failover的基础上面进行该特性的研发。下图是PG Auto Failover的一个简单的架构示意图,主要有三个角色:primary,secondary,和monitor,左边是用户程序。monitor会周期性的向 primary和secondary进行“健康检查”:向它发起网络连接,看是否可达。primary和secondary会发送它们自身的一些状态信息给monitor,若monitor检测不到primary连接,会尝试去提升secondary。在做提升之前,monitor也会去检查secondary是否和primary同步,这一点和前面提到的FTS是类似的,区别在于它把 probe进程从 primary拿到了外部。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

图片来源:https://pg-auto-failover.readthedocs.io/en/latest/architecture.html

 

由于存在三个参与者,需要考虑什么情况下集群是可用的,什么情况下集群是不可用的。这三个角色中,只允许一个节点不可用。超过两个节点不可用,集群也会不可用。如果只有master失败,会对standby进行提升,提供对外服务。如果是standby失败,monitor只会对standby进行标记,master继续提供服务,如果monitor节点挂掉,master和standby会继续提供服务,就像没有monitor节点一样。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

由于PG Auto Failover是PG的特性,直接移植到Greenplm上自然会面临一些问题。这里我们主要提一个非常突出的问题,即可能存在两个master的情况。下图中,当monitor连接不到master节点时,monitor会尝试去提升standby为新的master,下图是master2。这种情况的出现,可能仅仅是因为monitor和master之间发生了网络故障,而master节点还存活着。 

 

此时,一旦把standby提升为新的master,就会存在两个master。在master节点上面会有一些辅助进程正在运行,比如FTS,Global Deadlock Detect、Dtx Recovery等,这些进程会连接到 segment上面,并对segment执行一些写操作。如果有多个FTS或者多个DTS recovery对同一个segment进行写操作,就有可能会发生数据冲突或者不一致的情况。

不惧宕机,数据库高可用理论与实践-鸿蒙开发者社区

因此我们就需要避免这种情况的发生。虽然在PG Auto Failover里有一些相关的处理,即master节点如果检测到和monitor失联了,会把master断开,但是我们无法确定这种断开操作是否及时,是否有效,有没有存在例外的情况导致没有及时的断开。因此,为了解决这个问题提供了两种解决思路:

 

master主动失活

 

master的主动失活主要包括两点:master节点主动退出,也就是整个PG instance退出。另一种是master不再向 segment发起网络连接,从而不再对segment进行任何操作。 

 

segment 确认master

 

Segment在执行dispatch过来的操作时,segment需要确认dispatch过来的节点是不是所记录的master节点。如果不是,就拒绝。

 

  • 完成standby提升前,通知所有segment重置活跃的master节点
  • segment收到master重置消息后,杀死所有backend进程
  • segment记录活跃的master节点
  • segment拒绝所有来自其他节点的dispatch(重置master消息除外)

 

到这里,我们关于Greenplum的高可用理论与实践就介绍完啦!下一场内核活动是关于日志与系统恢复,记得关注公众号信息哦!

 

文章转自公众号:Greenplum中文社区

分类
标签
已于2022-5-18 18:04:30修改
收藏
回复
举报
回复
    相关推荐