1. 从单点到高可用:为什么需要这套架构
先讲一个我实际经历的场景。某公司有一套核心业务系统,早期数据库就一台主库,数据量上来之后,备份、恢复、报表查询都挤在这一个实例上。某天凌晨磁盘告警,主库所在机器硬件故障,整个业务直接停摆。那天夜里最痛苦的不是修复机器,而是发现没有一套现成的切换预案——备库倒是配了,但从来没有演练过切换流程,真到了紧急关头没人敢动。
这件事之后,我开始认真梳理 PostgreSQL 的高可用和读写扩展方案。主从复制解决的是“数据冗余 + 故障切换”的问题,负载均衡解决的是“查询压力分摊 + 连接合理路由”的问题。这两块组合在一起,就是一套比较完整的高可用架构。
这套方案适合谁?适合数据库已经有一定规模、业务对连续性有要求、或者正在从单实例向集群演进的团队。运维人员、DBA、后端开发,只要负责 PostgreSQL 的稳定运行,这篇文章都值得看。
我下面分享的内容,都是基于 PostgreSQL 流复制(Streaming Replication)这套成熟机制展开的。切换工具我用 Patroni 和 repmgr 做过对比,负载均衡层我用 Pgpool-II 和 HAProxy 都实测过。每个方案我都会讲清楚选型理由、部署要点,以及我踩过的坑。
2. 主从复制:从 WAL 说起
2.1 流复制的底层机制
PostgreSQL 的主从复制,核心机制是基于 WAL(Write-Ahead Logging,预写日志)。主库在写入数据页之前,先把变更记录写入 WAL 日志,从库通过网络持续拉取这些 WAL 记录,并回放到本地,从而保持和主库的数据一致。
流复制(Streaming Replication)是这套机制里最常用的实现方式。从库启动后,通过 walreceiver 进程主动连接到主库的 walsender 进程,主库把 WAL 记录实时推送给从库,而不是等 WAL 文件写满后再整体传过去。这个推送是持续不断的,所以正常情况下主从之间的数据延迟非常低,通常只有几秒甚至毫秒级。
这里有个关键概念叫 LSN(Log Sequence Number,日志序号),它是 WAL 日志的定位标记。主从之间的同步状态,本质上就是看两边的最新 LSN 差了多少。从库的回放进度追赶上主库的写入进度之后,就会进入“流式复制”状态。
配置流复制有几个关键参数必须理解:
- wal_level = replica(或更高),决定 WAL 里记录的信息量。默认的 minimal 级别不包含足够的信息用于复制,必须调整。
- max_wal_senders:允许同时多少个 WAL 发送进程,也就是能连接多少个从库。每增加一个从库,至少要加一个。
- max_replication_slots:复制槽的数量。如果要做流复制,建议开启复制槽,防止主库 WAL 日志还没被从库拉走就被回收,导致从库无法续传。
- hot_standby = on:从库上开启只读查询能力,这样才有意义做读写分离。
2.2 同步复制与异步复制的取舍
这是选型里最重要的一个决策点。异步复制下,主库提交事务不需要等待从库确认,性能几乎无损,但主库一旦崩溃,可能有少量数据丢失(取决于延迟多少)。同步复制下,主库提交事务至少要等一个从库写入并确认,确保不丢数据,但写入延迟会显著增加,而且如果从库挂了,主库写入也会被阻塞。
实际业务里怎么选?我给的判断标准很简单:看你的业务能不能容忍丢失最近几秒的数据。
- 核心交易类、订单类、账户类,建议用 synchronous_commit = remote_apply 或 remote_write,至少有同步保护。
- 日志类、统计类、内容类,异步就够了,性能优先。
PostgreSQL 的同步复制可以通过 synchronous_standby_names 参数指定,支持 first、any 等不同模式。比如配置任意一个从库确认即可:
synchronous_standby_names = 'ANY 1 (standby1, standby2)'这意味着主库的提交要等到多个从库中任何一个完成确认。这套机制从 9.6 开始逐渐完善,到 10+ 版本已经比较成熟。
但同步复制有一个需要特别注意的副作用:如果同步从库宕机,主库写入会卡住,直到超时。所以通常不会把所有从库都设为同步,而是保留一个异步从库兜底,或者用 quorum 机制。
2.3 从库搭建的完整步骤
从零开始搭一个流复制从库,大致分几大步。假设主库已经正常运行,我们在新机器上初始化一个从库实例。
第一步,准备一个基础备份。用 pg_basebackup 工具,从主库拉一个一致性快照:
pg_basebackup -h 主库IP -p 5432 -U replicator -D /data/pgdata -X stream -P这里 -X stream 表示同时开始流式传输 WAL 日志,-U replicator 是专门用来做复制的账号,权限只授予 REPLICATION 即可,不要给超级权限。
第二步,在从库的 postgresql.conf 里配置复制参数。
primary_conninfo = 'host=主库IP port=5432 user=replicator application_name=standby1'primary_conninfo 是从库连接主库的关键,主库侧会看到来自 replicator 用户的连接,所以要确保 pg_hba.conf 里允许这个账号从从库 IP 发起复制连接:
host replication replicator 从库IP/32 md5第三步,启动从库。如果是 PostgreSQL 12 及以上,直接在从库 data 目录下创建一个空的 standby.signal 文件,表示该实例以只读热备模式启动:
touch /data/pgdata/standby.signal然后启动 PostgreSQL。之后通过以下 SQL 验证复制状态:
-- 主库侧执行 select * from pg_stat_replication; -- 从库侧执行 select pg_is_in_recovery();看到 pg_stat_replication 里有记录,且 state 为 streaming,说明复制链路已经建立。此时可以在从库上跑只读查询,这就是热备(Hot Standby)。
我个人强烈建议第一次搭建时,用 pg_basebackup 而不是手工拷贝数据文件。手工拷贝很容易出现数据目录不一致的问题,pg_basebackup 本身会做校验和状态检查,安全很多。
3. 故障切换:从手动到自动
3.1 手动切换的流程和风险
复制搭好之后,遇到主库故障,最简单直接的做法是手动把从库提升为新主库。PostgreSQL 从 12 开始支持 pg_promote() 函数,也可以在从库上执行:
pg_ctl promote -D /data/pgdata执行 promote 之后,从库会停止恢复模式,开始接受写入,并从本地 WAL 里生成一个新的时间线(timeline),以便和旧主库区分。
手动切换最大的问题不是命令本身,而是切换决策。什么时候切?怎么确认旧主库真的挂了?如果是网络分区而不是硬件故障,旧主库还在运行但和从库失联,此时强行提升从库,可能有两个实例同时接受写入,也就是脑裂。脑裂发生后,数据一致性恢复非常困难,远比停机更难处理。
所以即使是手动切换,也必须要求从库已经将主库的 WAL 全部拉取并回放完毕,或者确认旧主库确实无法提供服务。否则,切换后会出现“新主库比旧主库少了最后几条变更”的情况,业务数据出现回滚。
手动切换适合什么场景?适合应对可预期的维护窗口,比如硬件更换、内核升级。适合预案清晰、切换频率极低的小团队。但对于“半夜报警、5 分钟内必须恢复”的业务,手动的速度太慢了。
3.2 Patroni 自动切换的原理
自动切换,业界主流方案是 Patroni。它做三件事:通过分布式锁选主、监控所有节点健康状态、在主节点故障时自动提升备用节点。
Patroni 的核心是基于分布式一致性存储(比如 etcd 或 ZooKeeper)来做选主。所有 PostgreSQL 节点都会向 etcd 注册自己的信息,并争夺 leader 角色。正常状态下,只有一个节点持有 leader 锁,其他节点都是从库。当 leader 节点失联一段时间,剩余节点重新选举,其中一个从库会拿到锁并执行 promote。
这套机制好在哪?它从根本上避免了脑裂问题——因为所有节点都以分布式锁为准,即使网络分隔,同一时刻也只会有一个节点认为自己是主库。虽然极端情况下仍然有旧主库短暂“以为”自己还是主的情况,但 Patroni 会在旧主库恢复后将其降级为从库,并重新拉取新主库的数据。
Patroni 的使用需要用一个额外的分布式存储组件,这也意味着架构里多了一个运维对象。对很多团队来说,etcd 本身要维护 3 个节点的高可用,这本身就是成本。但相比 PostgreSQL 直接停摆,这点成本是可接受的。
3.3 我用 repmgr 和 Patroni 的实测对比
我先后在测试环境里分别用 repmgr 和 Patroni 搭建过两套主从架构,做了一些切换演练对比。
repmgr 是一个相对轻量的工具,它也是基于复制槽和 WAL 管理的,提供 repmgr standby promote、repmgr standby follow 这样的命令。它的优点是配置简单,依赖少,没有 etcd 也能跑。但它对于脑裂的防护依赖额外的判断机制,且回切(failback)的流程相对繁琐。
Patroni 的优点是把“选主”和“配置管理”结合起来,切换时不仅提升从库,还会自动更新所有节点的配置信息,让应用层能动态感知新主库的地址。比如配合 HAProxy 或者 Patroni 自带的 REST API,应用无需改连接串即可继续工作。
我实测下来,repmgr 适合中小规模、架构尽量简单的场景;Patroni 适合对自动切换要求高、节点比较多(比如一主多从)的场景。如果你只在两台机器上做一主一从,用 repmgr 就够了;三台以上、还要自动拉起故障实例,上 Patroni 更顺。
3.4 切换过程中的关键注意事项
切换不是敲一条命令那么简单的。有几个细节如果不注意,切换后会出现各种问题。
时间线问题。旧主库恢复后,如果它不知道自己已经被替代,仍以旧 timeline 对外服务,双写就会出现。PostgreSQL 的 timeline 机制就是为了解决这个问题,新主库 promote 后会开启新的 timeline,旧主库恢复连接时会发现自己落后,然后自动跟随新主库。但如果网络分区期间旧主库一直对外写入,恢复后这些数据无法合并到新主库,这就是脑裂的实际后果。
WAL 归档问题。建议主库开启 archive_mode,同时配置 archive_command 归档到共享存储或对象存储。切换后,新主库要能从同样的归档位置恢复数据,所以归档配置要一致。如果归档只指向旧主库的本地目录,切完新主库就断档了。
复制账号的权限。很多团队只在主库上创建复制账号,从库提升为新主后,应用账号、复制账号都还在(因为数据相同),但密码如果有轮换机制就容易出问题。建议把账号管理放到配置管理工具里,通过 Patroni 或类似工具统一下发。
4. 负载均衡:把查询打到正确的节点上
4.1 主从架构下的负载均衡思路
主从架构搭好之后,如果只是把从库当作备份,是很浪费的。热备节点具备只读能力,正好可以把只读业务(报表、统计、后台任务)分流过来,减轻主库压力。这就是读写分离。
读写分离的流量分发策略,主要有三种:
- 按 SQL 类型路由:写请求强制走主库,读请求分摊到主库和从库。
- 按连接路由:某些特定客户端会话固定连接到从库,比如报表服务单独使用从库地址。
- 按事务状态路由:同一个事务里先写再读,则全部走主库,避免从库数据延迟导致读到过期数据。
这里有个容易被忽略的原则:主从架构下的负载均衡不是简单把流量打散,而是要保证业务能接受“从库读到的数据可能不是最新”的延迟。如果业务对数据强一致,读也要走主库。
我在实际项目中见过一个事故:某个运营后台的列表页,查询被路由到了从库,而刚写入的状态在从库上还没回放完成,用户看到的内容和自己刚操作的记录对不上,反复排查才发现是读写分离导致的“幻读”。这种问题在数据强一致场景下非常难解释清楚。
4.2 Pgpool-II 还是 HAProxy
负载均衡层的工具有两个主流方向:应用层代理(Pgpool-II)和网络层代理(HAProxy)。
Pgpool-II 是专门为 PostgreSQL 设计的,内置了读写分离、连接池、负载均衡、自动故障切换等完整功能。它既能做语句级路由(通过解析 SQL 判断读写),也能做连接池复用。配置项多,学习曲线陡,但功能确实全。缺点是一旦 Pgpool-II 本身出问题,影响面很大,而且它管理的是 PostgreSQL 层面的连接状态,对 ODBC/JDBC 等客户端来说增加了协议转换的复杂度。
HAProxy 是通用网络负载均衡器,不感知 PostgreSQL 协议细节,只能做 TCP 层的四层转发。它把连接转发到后端不同的 PostgreSQL 节点,本身不做 SQL 路由,因此更轻、更稳。缺点是它无法判断某条 SQL 是读还是写,要让读写分离生效,只能通过配置多个前端端口来解决:一个端口只转发到主库,一个端口只转发到从库,应用层自行选择。
我实测下来的结论是:如果团队对 PostgreSQL 比较熟,需要开箱即用的读写分离,Pgpool-II 更贴合。如果团队已经有很多 HAProxy 运维经验,想用一套通用方案管理所有流量,HAProxy 更合适。
另外还有一个思路,客户端自行管理读写分离,比如 JDBC 的 PostgreSQL 插件支持多主机配置,主从不同权重,性能表现也不错。这个方案少了一层代理,但需要应用开发配合改造连接串。
4.3 连接池的配置要点
无论选择哪个代理,连接池都是必配项。PostgreSQL 每个连接都是一个进程,连接数过多时会大量消耗系统资源。连接池的作用是让多个应用连接复用到少数几个 PostgreSQL 连接上,降低连接开销。
Pgpool-II 的连接池参数里,关键的是 max_pool 和 num_init_children。num_init_children 控制 Pgpool-II 提前为每个后端 PostgreSQL 实例建立的连接数,max_pool 控制每个应用连接可以复用多少个后端连接。这两个参数需要根据应用并发和数据库 max_connections 一起规划,否则容易出现“连接池把数据库连接数打满,但应用侧还在排队”的情况。
如果是 HAProxy,它本身不带 PostgreSQL 连接池功能,只负责 TCP 转发。要复用连接,还得在 PostgreSQL 前再叠加 PgBouncer 这类专用连接池。这也是我后来更倾向 Pgpool-II 的原因——简化架构层级,少一个组件就少一份故障点。
4.4 基于延迟感知的读写分离优化
简单的读写分离只做分流,但要做得更精细,必须考虑复制延迟。
PostgreSQL 提供了一些系统函数,比如 pg_stat_replication 里的 replay_lsn,可以实时看到从库的回放进度。如果要实现“读请求可能延迟,但不超过阈值”的剥离策略,可以在应用层或代理层做延迟判断:当从库的 replay_lsn 落后主库超过某个阈值,就把这个从库从读流量池里摘除,等追平后再加回来。
实际的实现方式有几种:
- 在应用层写一个健康检查脚本,周期检查各从库的回放延迟,延迟过高时更新路由配置。
- 在 Pgpool-II 上配置 health_check,结合延迟指标做节点健康判断。
- 如果使用 Patroni,它会在 REST API 中暴露每个节点的角色和数据状态,应用层可以直接查询。
这个优化对高并发读的业务很关键。比如电商大促期间的库存查询、榜单刷新这类读多写少的场景,只要从库延迟超过几秒,用户看到的库存数据就明显不准,投诉就来了。
我在一个模拟项目中做过这套延迟感知的方案。健康检查模块每 5 秒拉取一次各从库的 pg_stat_replication,如果发现某个从库延迟超过 3 秒,就暂时把它从读流量池标记为不可用;等延迟回到 1 秒以内再接回来。这样业务读流量始终集中在可接受延迟的节点上,主库负载也明显下降。
5. 架构落地:一主双从 + 读写分离的完整实践
5.1 整体拓扑与组件规划
我在自己负责的模拟项目 X 中,落地过一套“一主双从 + Pgpool-II 读写分离 + repmgr 自动切换”的架构。选择这个组合的考虑是:三台数据库服务器保证冗余,Pgpool-II 承接应用侧连接,repmgr 负责自动切换,不需要引入额外的分布式存储,整体运维成本可控。
拓扑大致如下:
- 节点 A:主库,承担写入和少量强制读
- 节点 B:从库 1,异步复制
- 节点 C:从库 2,异步复制,并作为切换候补
- Pgpool-II 节点:承接应用连接,做读写分离和连接池
三台数据库节点都配置了相同的数据目录结构和配置模板,方便切换后快速调整。归档日志统一放到共享存储上,同时启动参数保证从库可以读取共享存储的 WAL 归档来追平数据。
5.2 Pgpool-II 的读写分离配置实操
以 Pgpool-II 4.x 为例,关键配置文件是 pgpool.conf。先开启读写分离功能:
load_balance_mode = on master_slave_mode = on master_slave_sub_mode = 'stream'后端节点配置用 backend_hostname、backend_port、backend_weight 等参数,可以指定主库、从库 IP 和权重,权重决定读请求按什么比例分发到各从库。我通常把主库权重设为 0,从库 1 和从库 2 各设 1,这样所有读流量都走从库,主库只处理写入。
注意,Pgpool-II 判断一条 SQL 是读还是写,核心依据是 SQL 的语法解析结果和事务状态。如果在一个未结束的事务里执行了写操作,该事务后续的所有 SQL 都会强制转发到主库,这是 Pgpool-II 的 keep_majority 特性,保证一致性。
配置完成后,用 pcp_attach_node 和 pcp_detach_node 命令可以动态调整节点状态,比如某个从库延迟过高时摘除它,恢复后再挂回来。健康检查参数 health_check_period 设为 10,表示每 10 秒检查一次后端节点可用性。
5.3 repmgr 的自动切换配置
repmgr 配置相对简洁。在每个节点上安装 repmgr 之后,在 repmgr.conf 里指定节点 ID、连接信息、数据目录、归档目录等。
node_id=1 node_name=node-a conninfo='host=node-a port=5432 user=repmgr dbname=repmgr' data_directory='/data/pgdata'然后用 repmgr -f repmgr.conf primary register 在主库上注册主节点信息,再在从库上执行 repmgr -f repmgr.conf standby clone 拉取数据并注册为备节点。
repmgr 的自动切换需要配置一个事件通知脚本,当它检测到主库不可达时,会根据优先级从候选从库中选一个提升:
repmgr standby promote -f /etc/repmgr.confrepmgr 支持 witness 节点(见证节点)来缓解脑裂场景。witness 节点不承载数据,但它参与投票,当主库和从库之间的网络发生分区时,witness 能帮助判断哪个分区仍然具备多数节点,从而决定是否切换。
5.4 应用连接的配置方法
应用侧连接不能只写一个数据库地址,必须配置高可用连接串。以 Java 应用为例,使用 PostgreSQL JDBC 的多主机 URL:
jdbc:postgresql://host1:5432,host2:5432,host3:5432/mydb?targetServerType=primarytargetServerType=primary 表示应用只连接主节点,适合写流量。如果要把读流量分散到多个节点,可以配置 targetServerType=any 或 preferSecondary。
如果应用使用 Pgpool-II,连接串就写 Pgpool-II 的地址,由它做读写分离。这里有个经验:不要在企业级应用中同时叠加“应用自建HA + Pgpool-II 负载均衡”两层路由,会导致故障转移时应用连接串无法确定是走了哪一层,排查起来非常痛苦。选一层做主路由,另一层做兜底就行。
5.5 切换演练的完整流程
架构落地后,切换演练是必须的,而且不要只做一次。我的演练流程大致分几个阶段:
- 停掉主库的 PostgreSQL 服务(或者模拟网络隔离,直接把主库的网卡禁用)。
- 观察 repmgr 是否识别到主库失联,以及是否触发 standby promote。
- 确认新主库对外可写,且数据不丢。这一步会对比切换前后主库 WAL 的 LSN 和业务侧事务数。
- 恢复旧主库,以从库身份重新加入集群。
- 验证旧主库上的数据是否能通过 repmgr standby follow 自动重新同步到新主库,且没有出现双写。
演练中我遇到过最典型的问题是:切换成功,但应用连接池里的旧连接没有重连到新主库,导致后续所有写操作都失败。根本原因在于连接池的 keepalive 时间过长,JDBC 连接池默认没有做自动重连。
解决方案是调整应用连接池配置:测试连接可用性的间隔调短(比如 5 秒),连接空闲超时调短(比如 60 秒),并且在 JDBC 连接串里增加 connectTimeout 和 socketTimeout。另外,在故障切换时手动触发连接池的清理和重建,也是避免业务长时间中断的有效手段。
6. 常见问题与排查技巧实录
6.1 从库一直显示 streaming 但数据不更新
现象:pg_stat_replication 里 state 是 streaming,但从库执行查询却看不到主库刚写入的数据。
大概率是回放进度卡住了。先在从库上执行:
select pg_last_wal_replay_lsn(); select pg_last_wal_receive_lsn();如果 receive_lsn 一直增长而 replay_lsn 不动,说明 WAL 已经收到但回放进程阻塞。常见原因是从库上有长时间运行的长事务,或者某个空闲事务持有了旧快照,导致 vacuum 和回放都无法推进。
排查方法:查看 pg_stat_activity,找到 state 为 idle in transaction 的进程,杀掉或等待其结束。另外检查 hot_standby_feedback 参数,如果是 on,主库会避免回收从库可能还用到的旧版本数据,但也会导致主库膨胀,需要权衡。
6.2 主库宕机后,从库自动提升失败
repmgr 或 Patroni 配置了自动切换,但主库挂掉后,从库并没有自动提升。常见原因:
- 候选从库数量不足。repmgr 默认要求至少一个可见的候选从库才能切换,如果只有一个从库且它自己也失联,无法切换。
- witness 节点配置错误。witness 节点需要和主库、从库网络互通,但某些云环境下安全组没放行,导致投票失败。
- repmgr 事件通知脚本执行出错,promote 命令没有真正执行。
排查时,先看 repmgr 日志,确认是否有 “standby promote” 的执行记录,再看数据库日志里是否有 promote 相关的错误。顺手还要检查 etcd/分布式锁的状态,如果是 Patroni,直接查 etcd 中 leader key 的归属。
6.3 负载均衡后端节点健康检查误判
Pgpool-II 配置了 health check,但偶尔会出现“明明数据库正常,却被标记为 down”的情况。这种误判大多数不是数据库真的挂了,而是健康检查的连接配置没写好。
Pgpool-II 的 health check 通过向数据库发起一个简单查询来判断节点状态,如果查询超时(比如一次 full vacuum 锁竞争导致响应变长),就判定节点故障。解决方案是调大 health_check_timeout,并且把 health_check_user 的权限设置为可查询系统视图,不要使用超级用户。
另外,健康检查不要只依赖 TCP 端口连通,要配置成查询模式,比如:
health_check_query = 'SELECT 1'这样能真正感知 PostgreSQL 的响应,而不是网卡通就以为活。
6.4 复制延迟长期偏高
复制延迟 = 主库的写入进度 − 从库的回放进度。如果延迟长期偏高,先分清是网络传输慢还是回放慢。
网络传输慢,一般体现在 pg_stat_replication 的 replay_lsn 和 sent_lsn 差距较大,说明从库没及时收到 WAL。此时检查主库的 max_wal_senders、wal_keep_segments 或复制槽设置,如果 WAL 被回收导致从库追不上,就要用 pg_replslot 扩展一下复制槽保留策略。
回放慢,主要看从库的磁盘 IOPS 和 CPU。回放是单进程串行的,如果从库还在跑大量读查询,IO 竞争激烈,回放进度会被拖慢。优化方向要么给从库更好的 IO 能力,要么减少从库上的读负载,必要时可以增加从库节点分摊读压力。
6.5 切换后旧主库重新加入集群,失败
旧主库恢复后重新加入集群,最常见的问题是 timeline 不一致。旧主库在故障期间可能已经产生了新的 WAL 记录,当它以从库身份重新连新主库时,PostgreSQL 会尝试从新主库拉取数据,但本地 WAL 时间线和主库不一致,导致回放失败。
解决办法:把旧主库的数据目录清空,重新用 pg_basebackup 或者 repmgr 的 clone 功能从新主库拉取全量数据。如果你不想丢失旧主库在故障期间产生的 WAL,也可以尝试用 pg_rewind 工具,它会基于新主库的时间线,把旧主库的差异数据追平。
pg_rewind 的操作要非常小心,它只支持在旧的物理备节点上执行,且要求旧主库配置了 wal_log_hints 或 checksums。执行前一定确认旧主库的数据不能被业务写入,否则会把新主库也带坏。
6.6 问题速查表
| 症状 | 可能原因 | 排查命令/思路 |
|---|---|---|
| 从库不追数据 | 长事务阻塞回放 | 查看 pg_stat_activity 中 idle in transaction 进程 |
| 主库宕机不自动切换 | 候选从库不足/witness 配置错误 | 检查 repmgr 日志、repmgr cluster show |
| 健康检查误判 | 超时时间过短/查询权限不足 | 调大 health_check_timeout,改为 SELECT 1 查询 |
| 复制延迟高 | WAL 回收、IO 瓶颈、读查询压力大 | 对比 sent_lsn 和 replay_lsn,查 IO 指标 |
| 旧主库无法回归 | timeline 不一致 | 用 pg_rewind 或全量 repmgr clust 重建 |
| 应用写入失败 | 连接池未自动重连新主库 | 调整连接池 keepalive,清理空闲连接 |
7. 一些值得再想一层的细节
主从复制和负载均衡这套架构,做到能跑并不难,难的是把边界想清楚。
一个容易被忽略的点是监控。没有监控的主从架构等于没有安全网。我推荐至少采集以下指标:主从之间的复制延迟、WAL 生成速率、从库回放速率、复制槽状态、连接数使用率、健康检查结果。这些指标每隔 5~10 秒采集一次,预警阈值要结合业务对延迟的接受度设定,而不是等出事了才看。
另一个点是备份策略。主从复制不等于备份。如果主库上执行了一条误删除语句,这条语句也会被复制到从库,从库数据同样被删。所以备份仍然是必要环节。建议保留至少一个不在复制拓扑内的独立备份节点,周期性做全量备份和 PITR(时间点恢复)。这个独立的备份节点可以承担一些非实时的报表查询,但一定不要把它当作正常的从库来承载读流量。
还有一个细节是安全。复制账号虽然只需要 REPLICATION 权限,但它能读取 WAL 里的所有数据变更,所以必须严格管理。复制流量最好走内网或者加密通道,不要暴露在公网。pg_hba.conf 里控制复制账号的来源 IP 尽量精确到具体网段,并且定期轮换密码。
我负责的模拟项目 X 在运行过程中,还遇到过一个问题:主库的 max_wal_senders 设置太小,从库数量增加后,新从库一直无法建立连接。排查后发现,历史遗留了多个断开的 walsender 进程没清理,把连接数占满了。这类问题很难靠表面状态发现,一定要配合监控和定期的连接数巡检。
我个人在实际操作中的体会是:架构方案本身并不难,真正决定成败的是细节管理。流复制、切换工具、负载均衡,每块都有成熟的开源方案,但如果没有经过充分的切换演练、没有监控覆盖、没有连接池重连机制,方案部署得再漂亮,故障来临时还是会手忙脚乱。
最后再分享一个小技巧:无论用哪种方案,定期做“故障注入式演练”非常有用。我每个月会在测试环境随机停掉某个数据库节点,然后观察整个集群的自动恢复流程是否符合预期。演练不仅验证了工具配置,还能逼着团队把操作文档、责任分工、应急预案都完善起来。这套架构值不值得上、怎么上最合适,最终还是要回归到你业务的连续性要求来权衡。