本文主要介绍亚信安慧AntDB数据库的分布式架构下的特有进程之snapshot receiver的设计,这也是分布式架构的核心进程之一

进程简介

该进程的作用从逻辑上解释包含两个方面:

  • 同步快照,并且是作为通信的client端存在
  • 同步事务号,同样也是client端(

与此同时,该进程只存在于Coordinator节点(后文简称CN节点)和Datanode节点(后文简称DN节点)上,且主机和备机上都有,这些节点上有且仅有一个该进程。

(注:通信的server端则是snapshot sender进程,这部分内容笔者将会在另一篇文章中阐述)

进程的内存结构

Snapshot receiver进程在物理上并没有特别的文件存储方式,启动进程时同时会申请共享内存,所有的数据都在这部分内存之中。

内存结构名为SnapRcvData,代码内通常以SnapRcv指针来访问这部分内存,完整的结构体定义较为琐碎,为了便于读者理解,这里仅挑选部分重要的内容给出说明,完整的定义参考源码文件(src/backend/replication/snapreceiver.c):

  • mutex:访问这部分共享内存时要加的锁
  • gxid_mutex:访问事务号成员时要加的锁
  • geters:backend进程等待申请事务号时的队列,等待receiver进程响应申请事务号的请求
  • reters:已获取事务号的backend队列,等待backend取走事务号
  • xcnt:当前集群内活跃事务号的个数
  • xip[MAX_BACKENDS]:当前集群内活跃事务号的列表
  • wait_finish_cnt:本节点申请的事务号个数,仅CN节点会有这个值
  • wait_xid_finish[MAX_BACKENDS]:本节点申请的事务号列表,仅CN节点会有这个值
  • latestCompletedXid:当前集群内最大的已完成的事务号
  • global_xmin:当前集群内的xmin(分布式集群内最老的活跃事务号)
  • local_global_xmin:本节点内的xmin(当前节点内最老的活跃事务号)
  • global_finish_id:本节点最近一次完成的事务号

进程的通信协议

“通信”特指与snapshot sender进程之间的通信,这里借用了逻辑复制的通信格式,采用了‘libpqwalreceiver’库的接口,使用到的接口的代码如下(src/backend/replication/libpqwalreceiver/libpqwalreceiver.c):

  • walrcv_connect
  • walrcv_disconnect
  • walrcv_startstreaming
  • walrcv_send
  • walrcv_receive
  • walrcv_get_senderinfo

不难看出,各接口的用途与名称有一定的必然联系,首先是建连,然后开始流复制,并通过send和receive传输数据。当snapshot receiver进程启动时,会主动连接snapshot sender进程,处理过程如下图所示:

亚信安慧AntDB数据库分布式架构剖析之snapshot receiver进程_数组

图1:snapshot receiver进程启动处理过程

建连成功之后会调用walrcv_startstreaming,把一些流复制选项同步到服务端,当服务端确认之后就会切换为copy-both模式,这时即表示可以正式传输数据了。当然,snapshot receiver进程同步的内容并不是流复制相关内容,而是事务号相关的内容,包括以下两个方面:

  • 下一个待分配的事务号(nextFullXid)
  • 本节点残留的2pc事务号列表,如果没有即为空(

注:这部分属于进程的异常处理逻辑,笔者会在后续的文章中专题剖析

建连完毕之后,进程即进入正式的通信阶段,一般来说,进入这个阶段集群基本已经启动完毕了,receiver和sender之间通过不同的消息类型去完成不同场景的需求,下面逐个介绍receiver进程的通信逻辑:

  • 消息‘a’

负责处理sender进程的广播消息,消息内容是其他CN申请的事务号,其接口是SnapRcvProcessAssign,处理步骤大致如下:

1、锁住共享内存(SnapRcv)

2、从消息流获取xid,然后遍历SnapRcv->xip数组,做去重处理,然后放入该数组内,同时SnapRcv->xcnt加1

3、解锁

4、如果新的xid大于等于本地的ShmemVariableCache->nextFullXid,并且不是备节点,就需要扩展新的clog,同时更新nextFullXid

  • 消息‘g’                                    

负责处理申请事务号后sender进程反馈回来的数据,其接口是SnapRcvProcessAssignRequest,处理步骤大致如下:                     

1、锁住共享内存(SnapRcv)

2、从消息流获取xid和procno,然后遍历SnapRcv->reters队列,找出正在等待事务号的进程对应的procno

3、找到进程之后,xid赋值给proc->getGlobalTransaction

4、然后setlatch,通知backend进程,backend进程会取出proc->getGlobalTransaction返回给上层调用

5、解锁           

  • 消息‘c’                                     

负责处理sender进程的广播消息,消息内容是其他CN结束的事务号,其接口是SnapRcvProcessComplete,处理步骤大致如下:

1、锁住共享内存(SnapRcv)

2、从消息流获取xid,然后遍历SnapRcv->xip数组,若找到就把这个xid从该数组内删除,同时SnapRcv->xcnt减1;如若没找到,会报ERROR

3、同时,如果该xid大于SnapRcv->latestCompletedXid,那就更新SnapRcv->latestCompletedXid的值

4、唤醒正在等待该事务提交的backend进程

5、解锁

6、用最新的SnapRcv->latestCompletedXid值来更新本地的ShmemVariableCache->latestCompletedXid

                                   

  • 消息‘f                                  

负责处理结束事务号后sender进程反馈回来的数据,其接口是SnapRcvProcessFinishRequest,处理步骤大致如下:

1、锁住共享内存(SnapRcv)

2、从消息流获取xid和procno,然后遍历SnapRcv->wait_commiters队列,找出正在等待的进程对应的procno和xid

3、找到进程之后,proc->getGlobalTransaction的值恢复为InvalidTransactionId

4、然后setlatch,通知backend进程

5、解锁

  • 消息‘s’                                     

负责处理sender进程同步来的快照数据,其接口是SnapRcvProcessSnapshot,处理步骤大致如下:

1、从消息流获取快照、xmin和latestCompletedXid三份数据

2、锁住共享内存(SnapRcv)

3、用gtm传来的值更新SnapRcv->latestCompletedXid

4、把快照数据整体memcpy到SnapRcv->xip数组

5、唤醒等待中的进程

6、解锁

7、更新SnapRcv->global_xmin(这个值在backend获取快照时,用于更新snapshot->global_xmin)

  • 消息‘h’                                    

处理心跳消息的反馈,其接口是SnapRcvProcessHeartBeat,处理步骤大致如下:

1、接收的心跳包内包含2个内容,时间戳和xmin

2、接收到心跳之后,更新SnapRcv->gtm_delta_time和SnapRcv->global_xmin

相对应的,还有一个发送心跳包的接口为SnapRcvSendHeartbeat,这个并不是‘h’消息的处理内容,而是SnapReceiverMain主函数的流程,处理步骤大致如下:

1、每次循环到对应的处理阶段(参考图2的流程),判断当前时间是否超过了设定的心跳间隔时间阈值(snap_receiver_timeout),这个是guc参数,默认60s 

2、 如果超过了阈值,就发送心跳包,否则跳过

3、发送的内容同接收一样,时间戳和xmin

  • 消息‘u’                                    

sender进程会通过这个消息,把nextFullXid同步到所有其他节点,其接口是SnapRcvProcessUpdateXid,处理步骤如下:

1、sender发来的只有一个事务号,即nextFullXid(下一个待分配的事务号)

2、如果拿到的xid比本地ShmemVariableCache->nextFullXid的要大,就更新ShmemVariableCache->nextFullXid和ShmemVariableCache->latestCompletedXid

3、最后更新SnapRcv->latestCompletedXid(先锁后更新)

进程的工作主流程

Snapshot receiver进程整体上同其他辅助进程相类似,启动之后会处于一个无限循环之中,不断监听各种事件和信号,笔者整理了一份流程图帮助读者理解,如下图所示:

亚信安慧AntDB数据库分布式架构剖析之snapshot receiver进程_共享内存_02

图 2:Snapshot receiver进程工作主流程图

上述流程图对应的既是SnapReceiverMain函数,又是snapshot receiver进程的主函数,之前的各种消息处理都是从这里开始的,想深入理解各种细节的读者可以从这里入手。

小结

本文介绍了snapshot receiver进程在正常情况下的处理逻辑,通过流复制协议,配合以不同的消息处理函数,完成了分布式事务号、分布式快照的同步功能。一个进程不可能总是处于正常状态,那当异常发生时,AntDB如何来保证事务的一致性呢?敬待后续的专题分享。

关于亚信安慧AntDB数据库

AntDB数据库始于2008年,在运营商的核心系统上,服务国内24个省市自治区的数亿用户,具备高性能、弹性扩展、高可靠等产品特性,峰值每秒可处理百万笔通信核心交易,保障系统持续稳定运行超十年,并在通信、金融、交通、能源、物联网等行业成功商用落地。