WAL proposer 连接所有三个 safekeeper
不是只连 donor。WalProposerStart:
void WalProposerStart(WalProposer *wp) { /* Initiate connections to all safekeeper nodes */ for (int i = 0; i < wp->n_safekeepers; i++) ResetConnection(&wp->safekeeper[i]); WalProposerLoop(wp); }WAL 广播也是发给所有SS_ACTIVE 的 safekeeper:
static void BroadcastAppendRequest(WalProposer *wp) { for (int i = 0; i < wp->n_safekeepers; i++) if (wp->safekeeper[i].state == SS_ACTIVE) SendMessageToNode(&wp->safekeeper[i]); }Quorum 只需要 2 个(walproposer.c:179wp->quorum = 3/2 + 1 = 2),但连接保持 3 个。
Paxos 在 WAL proposer 和 safekeeper 之间,不是 safekeeper 之间
流程全部由 WAL proposer 驱动:
WAL proposer (计算节点) safekeeper 1,2,3 │ │ ├─ Greeting ──────────────────→ │ 发起连接 │ │ ├─ VoteRequest ───────────────→ │ 请求投票 │←─ VoteResponse ────────────── │ 各回各的 │ │ ├─ 收集 quorum(≥2),选出 donor │ │ │ ├─ ProposerElected ───────────→ │ 宣布当选 │ │ ├─ AppendRequest(WAL) ─────────→ │ 推送 WAL │←─ AppendResponse ──────────── │ 各回各的确认Safekeeper 之间不会互相投票、不会互相选举。WAL proposer 就是 Paxos 的 proposer,safekeeper 是 acceptor。
WAL 发三个,两个确认就算成功
核心在 walproposer.c:1956-1964:
// 收集所有 safekeeper 的 flushLsn 到数组 for (uint32 i = 0; i < mset->len; i++) { if (sk != NULL && sk->appendResponse.flushLsn >= wp->propTermStartLsn) responses[i] = sk->appendResponse.flushLsn; else responses[i] = 0; } qsort(responses, mset->len, sizeof(XLogRecPtr), CompareLsn); // 升序排列 // 取 "跳过 n - quorum 个" 之后的值 = 中间值 return responses[mset->len - MsetQuorum(mset)]; // = responses[3 - 2] // = responses[1] ← 升序排列后的第 2 个(中间值)图示(3 个 safekeeper,quorum = 2):
safekeeper1 flushLsn = 100 safekeeper2 flushLsn = 200 safekeeper3 flushLsn = 300 升序: [100, 200, 300] ↑ ↑ responses[0] responses[2] responses[3 - 2] = responses[1] = 200 → commitLsn = 200(至少 2 个节点确认到这里)这就是 Raft 标准做法:commit 到多数派都确认的位置。3 个里任意 2 个就行,所以即使你有一台 safekeeper 偶尔断连,业务也能正常工作。
WAL proposer 的超时看门狗机制:
wp_log(WARNING, "terminating connection to safekeeper '%s:%s' in '%s' state: no messages received during the last %dms or connection attempt took longer than that",
sk->host, sk->port, FormatSafekeeperState(sk), wp->config->safekeeper_connection_timeout);
ShutdownConnection(sk);
触发条件
WalProposerPoll()主循环中,每次轮询都会检查每个 safekeeper 连接:如果距离latestMsgReceivedAt(最后一次收到消息的时间)已经超过了safekeeper_connection_timeout(默认10000ms = 10秒),就会打印这条 WARNING,然后调用ShutdownConnection关闭该连接。
latestMsgReceivedAt只在三个地方更新:
| 行号 | 场景 |
|---|---|
| 431 | 发起连接时(进入SS_CONNECTING_WRITE状态) |
| 593 | 连接成功建立时 |
| 2449 | 成功从 safekeeper 读取到消息时 |
出现告警是否正常?
不频繁出现是正常的— 属于连接超时后的自动恢复机制。safekeeper 连接被关闭后,ReconnectSafekeepers()会自动尝试重连。常见触发场景:
- 网络抖动— safekeeper 与 WAL proposer 之间网络短暂不通
- safekeeper 重启— 对端进程重启导致连接静默
- safekeeper 过载— CPU/IO 繁忙导致响应变慢,10秒内未回复任何消息
频繁出现则说明有问题需要排查:
- 网络是否稳定(丢包、延迟)
- safekeeper 是否健康(CPU、IO、内存)
- 10 秒的超时是否太短(可能通过
wal_acceptor_connection_timeoutGUC 参数调整,定义在 walproposer_pg.c:66)
总结
这是 WAL proposer 的健康检查机制:超过 10 秒没收到 safekeeper 的消息就认为连接已死并重连。偶尔出现属于自愈行为,持续/频繁出现则需要排查网络或 safekeeper 节点状态。