GPDB - 高可用特性 - 同步复制与异步复制

GPDB - 高可用特性 - 同步复制与异步复制

GreenPlum是基于PostgreSQL的分布式数据库,master用于接收用户请求并生成执行计划与分发,当然也可以参与计算;而segment则用于存储数据,将计算的结果传递给master。Segment本身具有高可用特性,即分为primary和mirror,通过主从复制构建高可用关系。默认使用同步复制,若FTS检测到mirror发生异常,则修改为异步复制。本文关注如何从同步复制切换到异步复制。

1、几个重要配置项

1)synchronous_commit

当数据库提交事务时是否需要等待WAL日志写入磁盘才向客户端返回。该参数可选值为on、off、local、remote_apply、remote_write。参数值说明如下图所示:

根据synchronous_commit参数,对应的同步复制可以分为3种模式:WRITE、FLUSH、APPLY。复制过程中,每个备机都有自己的复制模式,主机根据不同的备机模式决定事务提交是否需要等待备机。GPDB中该参数默认是on,也就是primary要等待mirror将接收到的WAL持久化到磁盘才可以提交

2)synchronous_standby_names

该参数是PG带过来的,用于设定需要支持同步复制的备节点服务器名称列表。必须与备机的application_name一样。有3种语法格式,如下:

css 复制代码
[FIRST] num_sync (standby_name [,...])
ANY num_sync (standby_name [,...])
standby_name [,...]

其中

1)num_sync表示事务需要等待响应的同步备节点个数

2)standby_name表示同步复制备节点服务器名字,这里指备节点连接配置信息中的application_name,对于流复制,可以查看recovery.conf配置文件(PG12开始,在postgresql.conf)中的primary_conninfo配置项,缺省值是walreceiver。对于逻辑复制可以在订阅连接信息中设置,缺省值是订阅名本身

3)FIRST和ANY指定从列表中选择同步备节点的方法。大小写不敏感。FIRST表示基于优先级的同步复制,ANY表示基于法定人数的同步复制

4)第3种格式在PG9.6版本之前使用,现在仍然支持,等价于FIRST 1(...)。比如FIRST 1(s1,s2)与s1,s2等价。

例1:FIRST 2(s1,s2,s3,s4):规定每个事务必须等待优先级最高的2个备节点确认才能提交,该列表排的越靠前的名字优先级越高。本例中s1>s2>s3>s4。没排在前2位的备节点都属于潜在的同步备节点。

例2:ANY 2(s1,s2,s3):规定每个事务必须等待至少任意2个节点确认后才能提交。

在GPDB中,对于异步复制,该参数配置为空;对于同步复制,配置为"*",表示匹配任意备名称这里需要注意,GPDB中segment对于该值默认为空,通过fts将其更改为"*",然后保存到postgresql.auto.conf文件中,以此达到同步复制的目的

2、同步复制情况下的提交

关于WaitEventSet及Latch的唤醒,参考前文:

https://mp.weixin.qq.com/s?__biz=MzU1OTgxMjA4OA==&mid=2247485254&idx=1&sn=c4e84024a01dbb27b5c449e7658cbacd&chksm=fc10dbd1cb6752c702b7df959d7b233c72bf9971e34006461c5a815f623b2af1a9b15e6fc40b&token=2029515501&lang=zh_CN#rd

https://mp.weixin.qq.com/s?__biz=MzU1OTgxMjA4OA==&mid=2247485259&idx=1&sn=62332c14bf4a4cd5c01fcf49455b3ed4&chksm=fc10dbdccb6752ca94325ff9124b576bde5db0d953ac5a736745a91d2f37799307f67ed4a8ef&token=2029515501&lang=zh_CN#rd

下图为同步复制正常场景下,commit流程被唤醒的逻辑:

1、事务提交

1)事务提交时RecordTransactionCommit->SyncRepWaitForLSN进入同步复制等待。上图所示,会将等待进程MyProc插入WalSndCtl->SyncRepQueue[mode]队列中,以供sender进程向mirror发送WAL接收到mirror落盘回复后,唤醒等待;

2)WaitLatch进入同步复制等待,mirror回复唤醒WaitLatch后,进入MyProc->syncRepState判断,同步复制完成,break退出循环,事务提交流程可继续下去。

2、WaitLatch

epoll_create -- epoll_ctl -- epoll_wait进入等待,通过监控管道上EPOLLIN事件进行等待与唤醒。

3、WalSndLoop sender进程主函数

Sender进程接收到mirror的回复后,通过SyncRepReleaseWaiters来唤醒等待进程。分为3种同步模式的队列,若mirror回复wal已接收到了,则通过SyncRepWakeQueue处理等待队列

4、SyncRepWakeQueue

将可唤醒的进程从队列种删除,然后SetLatch设置latch的is_set为true,并通过kill命令向事务提交进程(等待进程)发送SIGUSR1信号。

为什么还要发送信号呢?

因为sender进程和commit进程属于不同进程,sender进程置位后,commit进程是看不见的,所以还需要发送信号给commit进程.

Commit进程使用的是MyLatch,而这里队列中的是MyProc->procLatch,是否同一个?

进程初始化的时候,可看到MyLatch就是MyProc->procLatch.

5、SyncRepWakeQueue

Commit进程在InitPostgres前就注册了SIGUSR1信号处理函数procsignal_sigusr1_handler:由于MyLatch->is_set还是false,所以将其值置为true,然后sendSelfPipeByte向selfpipe_writefd管道写一个字节。因为epoll_wait监控的是管道上的EPOLLIN事件,写入一个字节后,commit进程的epoll_wait就被唤醒了。

3、切换异步复制

切换异步复制的流程如下图所示:

1)事务提交时RecordTransactionCommit->SyncRepWaitForLSN进入同步复制等待。上图所示,会将等待进程MyProc插入WalSndCtl->SyncRepQueue[mode]队列中,以供sender进程向mirror发送WAL接收到mirror落盘回复后,唤醒等待;

3)这里关注for循环,WaitLatch何时被唤醒。FTS进程探测到mirror异常,需要关闭同步:UnsetSyncStandbysDefined会修改synchronous_standby_names为空,即改为异步,然后持久化到postgresql.auto.conf中,最好通过pg_reload_conf函数(通知所有进程更新配置)向Postmaster(主进程)进程发送SIGHUP信号

4)主进程处理SIGHUP信号的函数为SIGHUP_handler,该函数会向所有子进程包括后台进程发送SIGHUP信号。这里关注checkpoint进程

5)checkpoint进程接收到SIGHUP信号后,ChkptSigHupHandler将got_SIGHUP变量置为true,然后checkpoint进程轮一圈后,会判断该参数,若为true则调用UpdateSharedMemoryConfig函数

6)UpdateSharedMemoryConfig->SyncRepUpdateSyncStandbyDefined函数会根据synchronous_standby_names是否为空判断同步异步情况,若为异步,则会调用SyncRepWakeQueue处理所有同步等待的进程

7)SyncRepWakeQueue将进程的syncRepState标记为SYNC_REP_WAIT_COMPLETE,然后向该进程发送SIGUSR1信号

8)事务等待进程通过procsignal_sigusr1_handler处理SIGUSR1信号,通过管道唤醒等待进程的epoll_wait

9)SyncRepWaitForLSN的WaitLatch被唤醒后,继续for循环,此时MyProc->syncRepState已在7)标记为了SYNC_REP_WAIT_COMPLETE,所以会退出循环,不再等WAL的LSN确认了。至此,完成异步复制的切换。

注意:主进程SIGHUP_handler会向所有子进程发送SIGHUP信号,也就是事务等待进程也会接收到SIGHUP信号,它处理SIGHUP信号的函数是PostgresSigHupHandler,会SetLatch MyLatch。那么上图的SyncRepWaitForLSN的WaitLatch也会被唤醒,然后继续循环,但是MyProc->syncRepState还未更改为SYNC_REP_WAIT_COMPLETE,仍旧会进入WaitLatch等待。当然,这还得checkpoint进程来唤醒让同步复制不在等待而赶紧走掉:

ruby 复制代码
PostgresMain
  pqsignal(SIGHUP, PostgresSigHupHandler);
  |--  {
  |    ConfigReloadPending = true;
  |    SetLatch(MyLatch);
  |--  }