备机在启动时会调用RequestXLogStreaming函数(这一步在哪里调用在前面的博客中已经描述过),这个函数会将GUC参数中的primary_conninfo信息保存在WalRcvData中(WalRcvData是由WalRcvShmemInit函数在共享内存中初始化的),也会保存WAL日志的复制起始LSN及时间线信息,然后通过SendPostmasterSignal(PMSIGNAL_START_WALRECEIVER)通知Postmaster进程启动WalReceiver进程,WalReciver进程负责和主机建立连接关系。(摘抄自张树杰 PostgreSQL技术内幕 事务处理深度探索)

PostgreSQL数据库复制——后台一等公民进程WalReceiver获知连接_共享内存


首先我们看一下WAL 接收器的六个状态字段,RequestXLogStreaming函数只有在WALRCV_STOPPED状态下,RequestXLogStreaming函数才会去发送PMSIGNAL_START_WALRECEIVER通知Postmaster进程启动WalReceiver进程,只有在WALRCV_WAITING状态下,才能直接调用SetLatch来唤醒wal reciver的latch。

typedef enum {
WALRCV_STOPPED, /* stopped and mustn't start up again */
WALRCV_STARTING, /* launched, but the process hasn't initialized yet */
WALRCV_STREAMING, /* walreceiver is streaming */
WALRCV_WAITING, /* stopped streaming, waiting for orders */
WALRCV_RESTARTING, /* asked to restart streaming */
WALRCV_STOPPING /* requested to stop, but still running */
} WalRcvState;

​void RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo, const char *slotname, bool create_temp_slot)​​函数的参数“recptr”表示流应该开始的位置; “conninfo”是要使用的 libpq 连接字符串; “slotname”是可选的,要获取的复制槽的名称;“create_temp_slot”表示在没有给出“slotname”时创建一个临时槽。WAL 接收器不直接加载用于连接到主节点的 GUC 参数,而是依赖此例程的调用者向下传递的值。 因此,任何新参数的添加都应通过此代码路径进行。

*walrcv = WalRcv;
bool launch = false;
pg_time_t now = (pg_time_t) time(NULL);
Latch *latch;

/* We always start at the beginning of the segment. That prevents a broken segment (i.e., with no records in the first half of a segment) from being created by XLOG streaming, which might cause trouble later on if the segment is e.g archived. 我们总是从段的开头开始。 这可以防止 XLOG 流创建损坏的段(即,段的前半部分没有记录),如果段被存档,这可能会在以后造成麻烦。 */
if (XLogSegmentOffset(recptr, wal_segment_size) != 0) recptr -= XLogSegmentOffset(recptr, wal_segment_size);
SpinLockAcquire(&walrcv->mutex); // 获取锁

/* It better be stopped if we try to restart it 只有在停止状态和等待状态才能调用 */
Assert(walrcv->walRcvState == WALRCV_STOPPED || walrcv->walRcvState == WALRCV_WAITING);

if (conninfo != NULL) strlcpy((char *) walrcv->conninfo, conninfo, MAXCONNINFO);
else walrcv->conninfo[0] = '\0';

/* Use configured replication slot if present, and ignore the value of create_temp_slot as the slot name should be persistent. Otherwise, use create_temp_slot to determine whether this WAL receiver should create a temporary slot by itself and use it, or not. 如果存在,请使用配置的复制槽,并忽略 create_temp_slot 的值,因为槽名称应该是持久的。 否则,使用 create_temp_slot 来确定这个 WAL 接收器是否应该自己创建一个临时槽并使用它,或者不 */
if (slotname != NULL && slotname[0] != '\0'){
strlcpy((char *) walrcv->slotname, slotname, NAMEDATALEN);
walrcv->is_temp_slot = false;
}else{
walrcv->slotname[0] = '\0';
walrcv->is_temp_slot = create_temp_slot;
}

if (walrcv->walRcvState == WALRCV_STOPPED) {
launch = true;
walrcv->walRcvState = WALRCV_STARTING;
}
else
walrcv->walRcvState = WALRCV_RESTARTING;
walrcv->startTime = now;

/* If this is the first startup of walreceiver (on this timeline), initialize flushedUpto and latestChunkStart to the starting point. 如果这是 walreceiver 的第一次启动(在此时间轴上),请将 flushedUpto 和 latestChunkStart 初始化为起点。 */
if (walrcv->receiveStart == 0 || walrcv->receivedTLI != tli) {
walrcv->flushedUpto = recptr;
walrcv->receivedTLI = tli;
walrcv->latestChunkStart = recptr;
}
walrcv->receiveStart = recptr;
walrcv->receiveStartTLI = tli;
latch = walrcv->latch;
SpinLockRelease(&walrcv->mutex);

if (launch) SendPostmasterSignal(PMSIGNAL_START_WALRECEIVER);
else if (latch) SetLatch(latch);
}