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种语法格式,如下:
[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进程来唤醒让同步复制不在等待而赶紧走掉:
PostgresMain
pqsignal(SIGHUP, PostgresSigHupHandler);
|-- {
| ConfigReloadPending = true;
| SetLatch(MyLatch);
|-- }