资讯动态

PostgreSQL流复制高可用架构实战:从原理到生产环境配置与故障切换

发布时间:2026/10/22 11:17:43 来源:尧图企业网站定制
1. 为什么流复制是 PostgreSQL 高可用架构的基石第一次接触 PostgreSQL 主从同步的人往往会被一堆名词绕晕物理复制、逻辑复制、流复制、归档恢复、同步提交、异步提交……其实把场景想清楚就简单了。假设你手头有一套业务系统数据库单点部署某天凌晨磁盘突然挂了或者机房网络抖了一下业务直接停摆。这时候你需要的不是“备份”而是“另一台随时能顶上的数据库”。流复制解决的正是这个问题主库写数据的同时把预写日志WAL实时推送到从库从库持续重放最终达到和主库几乎一致的状态。我最早在生产环境上流复制是在一套订单系统上当时主库压力不算大但业务方对数据丢失的容忍度极低要求故障切换后最多丢几秒的数据。这个需求直接决定了后面所有参数的选择方向。所以你在动手之前先问自己三个问题能接受多少数据丢失RPO能接受多长停机时间RTO从库是只做备份还是要承担读流量这三个答案会贯穿整篇文章的每一个配置决策。流复制的核心机制其实不复杂。PostgreSQL 把所有数据变更先写进 WAL 日志主库有一个 walsender 进程负责把 WAL 发给从库从库有一个 walreceiver 进程接收并写入本地再由 startup 进程重放。整个过程是“日志级”的复制不是 SQL 级所以从库拿到的是块级别的物理变更效率高、延迟低这也是它比逻辑复制更适合做整库热备的原因。逻辑复制虽然灵活可以按表订阅但延迟和一致性保障上不如物理流复制来得直接。适合读这篇文章的人我大致分三类一是刚接手 PostgreSQL 运维、需要搭一套主从的工程师二是已经在用但延迟忽高忽低、想搞清楚参数怎么调的人三是准备做故障切换演练、需要一套可复现方案的架构同学。不管你是哪一类下面的内容都会从原理讲到实操再讲到踩坑尽量让你看完就能上手。2. 主从架构设计与复制模式选型2.1 物理流复制和逻辑复制的取舍逻辑很多人一上来就问“用哪个好”其实这个问题没有标准答案得看你要解决什么。物理流复制复制的是整个集群的物理块变更从库是一个完整的、只读的副本适合做灾难恢复、读写分离的只读节点。逻辑复制复制的是行级变更可以跨版本、跨平台适合做数据汇聚、部分表同步、灰度升级。两者不是替代关系而是互补。我个人的经验是如果目标是“整库热备 快速切换”物理流复制是首选因为它简单、延迟低、切换后数据完整。如果目标是“把几张核心表同步到另一个业务库做分析”逻辑复制更合适。本文聚焦物理流复制因为它是绝大多数生产环境高可用方案的地基。2.2 同步提交与异步提交的权衡这是流复制里最关键的决策点没有之一。异步提交下主库提交事务后立刻返回成功WAL 异步发给从库如果主库在发送前崩溃这部分数据就丢了。同步提交下主库要等从库确认收到 WAL 才返回成功数据安全性高但主库的写入延迟会受从库影响。这里有个参数叫synchronous_commit取值有on、remote_apply、remote_write、local、off。我一般这么选取值含义数据安全性写入延迟适用场景off不等本地 WAL 落盘最低最低对丢失不敏感local等本地 WAL 落盘中低单机可靠性remote_write等从库写入 OS 缓存较高中多数生产环境on等从库 WAL 落盘高较高金融级要求remote_apply等从库重放完成最高最高强一致读实测下来remote_write是性价比最高的选择既保证了从库收到了数据又不会因为从库重放慢而拖累主库。如果你的业务能接受秒级数据丢失异步提交local或off配合监控告警也够用。但要注意同步提交如果配置了多个同步备库任何一个卡住都会让主库写入阻塞所以synchronous_standby_names的写法很讲究后面会细说。2.3 一主一从还是一主多从一主一从结构简单适合中小规模。一主多从可以分担读流量但同步提交模式下主库要等所有列在synchronous_standby_names里的从库确认任何一个从库网络抖动都会影响主库。所以我的建议是同步备库只选一个最稳定的节点其他从库用异步这样既保证了核心数据安全又不会因为某个从库拖垮整个集群。级联复制从库再往下挂从库也是常见做法能减轻主库的发送压力。但级联层数越多延迟越大故障排查也越复杂。我一般控制在两层以内再深就不如直接多挂几个一级从库了。3. 环境准备与主库核心配置实操3.1 基础环境与版本一致性检查动手之前先把两台机器的 PostgreSQL 版本对齐。主从版本不一致物理流复制基本跑不起来因为 WAL 格式可能不兼容。用SELECT version();确认两边大版本号一致小版本尽量也一致。操作系统层面建议用相同的发行版和架构避免字节序、文件路径差异带来的问题。网络方面主从之间要能互通 5432 端口或者你自定义的端口。我习惯在配置前先用telnet或nc测一下连通性别等到配置完了才发现防火墙没开。时间同步也要做NTP 对时能保证日志时间戳可比对排查延迟问题时特别有用。3.2 postgresql.conf 关键参数逐项拆解主库的postgresql.conf是流复制的核心下面这几个参数必须改# 监听地址允许从库连接 listen_addresses * # 开启 WAL 归档级别流复制至少需要 replica wal_level replica # 允许的最大 WAL 发送进程数至少留一个给从库 max_wal_senders 10 # 保留的 WAL 段数量防止从库断连后 WAL 被删 wal_keep_size 1GB # 同步备库列表先留空后面按需配置 synchronous_standby_names # 开启 WAL 归档可选但强烈建议 archive_mode on archive_command cp %p /var/lib/postgresql/archive/%fwal_level从minimal改成replica需要重启数据库这个不能省。max_wal_senders我一般设成从库数量加 5留点余量给临时连接。wal_keep_size是个容易踩坑的参数设太小的话从库一旦断连超过这个量主库就把 WAL 删了从库再连上就得重新做基础备份。1GB 是个保守值WAL 写入量大的系统可以调到 5GB 甚至 10GB。archive_command里的路径要提前建好权限给 postgres 用户。归档不是流复制的必需项但它能在从库彻底追不上时救你一命配合基础备份可以做时间点恢复。3.3 pg_hba.conf 复制权限配置从库要连主库拉 WAL必须在pg_hba.conf里放行。加一行# 允许从库 IP 以 replication 身份连接 host replication repl_user 192.168.1.0/24 scram-sha-256这里我专门建了一个repl_user账号而不是用超级用户。复制账号只需要REPLICATION权限最小权限原则能降低风险。创建命令CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD your_strong_password;密码认证方式建议用scram-sha-256比md5安全。改完pg_hba.conf记得reload不用重启。3.4 基础备份pg_basebackup 的正确用法从库的初始数据必须从主库来最标准的方式是pg_basebackup。在主库或从库上执行都行我习惯在从库上执行直接拉到本地pg_basebackup -h 192.168.1.10 -p 5432 -U repl_user \ -D /var/lib/postgresql/14/main \ -Fp -Xs -P -R参数逐个解释-h主库地址-D从库数据目录-Fp表示普通文件格式不是 tar-Xs表示用流复制方式传 WAL-P显示进度-R自动生成standby.signal和postgresql.auto.conf里的primary_conninfo。这个-R特别省事省得你手动写连接信息。注意执行pg_basebackup前从库的数据目录必须是空的否则会报错。如果之前有数据先清空再执行。备份完成后从库数据目录下会多一个standby.signal文件这个文件的存在告诉 PostgreSQL“我是一个从库启动后进入恢复模式”。没有它从库会以主库身份启动那就乱套了。4. 从库配置与复制链路验证4.1 standby.signal 与 primary_conninfo 配置pg_basebackup -R已经帮你生成了postgresql.auto.conf里的连接串内容大概是这样primary_conninfo host192.168.1.10 port5432 userrepl_user passwordyour_strong_password如果你没加-R就得手动创建standby.signal空文件即可并在postgresql.auto.conf里写上面这行。primary_conninfo里的密码是明文所以文件权限要控制好chmod 600是基本操作。从库的postgresql.conf里还有几个参数值得关注# 从库必须开启热备否则无法接受只读查询 hot_standby on # 从库反馈给主库的延迟信息级别 wal_receiver_status_interval 10s # 从库重放冲突时的处理策略 hot_standby_feedback onhot_standby_feedback on这个参数争议比较大。开启后从库会把正在执行的查询所需的最老快照告诉主库主库就不会清理这些行避免从库查询被取消。但代价是主库的垃圾回收会被拖慢表膨胀风险增加。我的建议是如果从库只做备份、不跑长查询关掉它如果从库要承担读流量且有长查询开启它但要监控主库的膨胀情况。4.2 启动从库并确认复制状态从库配置好后直接启动pg_ctl start -D /var/lib/postgresql/14/main启动后在从库执行SELECT pg_is_in_recovery();返回t说明处于恢复模式也就是从库身份正常。然后在主库执行SELECT client_addr, state, sync_state, sent_lsn, replay_lsn, pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes FROM pg_stat_replication;这条查询是流复制运维的“仪表盘”。state应该是streamingsync_state显示async或syncreplay_lag_bytes是从库还没重放的字节数。如果这个值持续增长说明从库追不上主库得查原因。在从库上也可以查延迟SELECT now() - pg_last_xact_replay_timestamp() AS replay_delay;这个返回的是从库最后重放事务的时间戳与当前时间的差单位是时间。如果主库没有新写入这个值会一直增大所以它只适合在主库有持续写入时参考。4.3 同步复制的启用与 synchronous_standby_names 写法确认异步复制跑通后如果业务需要同步复制就在主库改synchronous_standby_names FIRST 1 (standby1, standby2)这个写法的含义是在standby1和standby2中只要有一个确认收到 WAL主库就返回成功。FIRST 1表示按列表顺序取第一个可用的。还有ANY 1 (...)的写法语义类似但优先级规则不同。我一般用FIRST 1把最稳定的节点放前面。改完synchronous_standby_names后reload即可生效。然后在主库查pg_stat_replication对应的从库sync_state会变成sync。这时候你可以在主库做个写入测试观察从库是否同步收到。注意同步复制下如果所有同步备库都挂了主库的写入会阻塞。所以生产环境一定要配置synchronous_commit remote_write而不是on并且至少保留一个异步备库作为兜底。5. 延迟监控、故障切换与常见问题排查5.1 复制延迟的量化监控方法延迟分两种发送延迟和重放延迟。发送延迟是主库发了但从库没收到通常是网络问题重放延迟是从库收到了但没重放完通常是从库 IO 或 CPU 瓶颈。用pg_stat_replication里的sent_lsn、write_lsn、flush_lsn、replay_lsn四个值可以精确定位卡在哪一环。我习惯写一个简单的监控脚本每分钟采集一次SELECT application_name, pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn) AS send_lag, pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag FROM pg_stat_replication;send_lag持续大于 0 且增长查网络和主库 walsender 进程replay_lag持续增长查从库的磁盘 IO 和max_wal_size设置。从库重放是单进程的如果主库写入并发很高从库重放可能成为瓶颈这时候可以考虑开启recovery_parallel_workersPostgreSQL 15 及以上支持并行重放。5.2 主库故障后的从库提升操作主库挂了要把从库提成新主库操作很简单pg_ctl promote -D /var/lib/postgresql/14/main或者在从库执行SELECT pg_promote();。提升后从库的standby.signal会被删除pg_is_in_recovery()返回f它就成了一个可写的主库。这时候你要做的第一件事是检查数据一致性确认最后重放到的 LSN评估丢了多少数据。如果原主库还能救回来把它作为新主库的从库重新接入需要重新做pg_basebackup因为原主库的 WAL 分叉了不能直接续上。这是流复制的一个硬限制也是为什么我反复强调wal_keep_size和归档的重要性。5.3 常见问题速查表现象可能原因排查方向解决方式从库 state 一直 startup连接信息错误查从库日志 primary_conninfo修正连接串重启从库replay_lag 持续增长从库 IO 瓶颈查从库磁盘 IO、CPU升级磁盘开并行重放主库写入阻塞同步备库全挂查 pg_stat_replication临时改异步恢复备库从库查询被取消主库清理了旧行查主库 vacuum 日志开 hot_standby_feedback复制槽导致 WAL 堆积从库长期断连查 pg_replication_slots删无用槽加监控提升后无法写入未真正 promote查 pg_is_in_recovery确认 promote 成功复制槽是个双刃剑。它能防止主库删除从库还没收到的 WAL但如果从库长期不连WAL 会无限堆积撑爆磁盘。我一般会为每个从库建一个物理复制槽同时加磁盘告警超过阈值就人工介入。5.4 我踩过的几个坑第一个坑是wal_keep_size设太小。有次从库网络抖了半小时恢复后直接报“requested WAL segment has already been removed”只能重做基础备份。后来我把这个值调到 5GB再没出过类似问题。第二个坑是同步复制下从库磁盘满了。从库写不进去主库等确认等到超时整个集群写入卡死。教训是同步备库的磁盘监控要比主库还严因为它的故障会直接传导到主库。第三个坑是pg_basebackup时没加-XsWAL 传输用了fetch模式结果备份期间主库 WAL 被清理备份出来的数据不完整。-Xs用流复制传 WAL和备份并行基本不会出现这个问题。6. 生产环境加固与扩展思路6.1 复制槽与 WAL 保留策略生产环境我建议每个从库配一个物理复制槽SELECT pg_create_physical_replication_slot(standby1_slot);然后在从库的primary_conninfo里加slot_namestandby1_slot。这样主库就知道这个从库还没消费到哪不会提前删 WAL。但复制槽的监控必须做pg_replication_slots里的restart_lsn和当前 LSN 的差距就是堆积量超过阈值告警。6.2 读写分离场景下的从库调优如果从库要承担读流量hot_standby_feedback和max_standby_streaming_delay要配合调。前者防止查询被取消后者控制从库重放等待查询的最长时间。我一般设max_standby_streaming_delay 30s超过这个时间从库会取消冲突查询继续重放保证延迟不失控。从库的shared_buffers和work_mem可以按读业务特点调大因为从库不写数据内存更多用于查询缓存。但要注意从库重放也会用共享缓冲区别把内存全给查询了。6.3 多级级联与跨机房复制的注意点跨机房复制延迟天然比同机房高同步复制基本不可行只能用异步。这时候synchronous_commit设local或off配合应用层的补偿逻辑。级联复制能减少主库的发送压力但中间节点故障会影响下游所有节点所以中间节点最好也配一个备选路径。跨机房还要注意带宽和 WAL 生成速率的匹配。如果主库每秒生成 50MB WAL跨机房带宽只有 100Mbps约 12.5MB/s那从库永远追不上。这种场景下要么限流要么只同步核心表改用逻辑复制。6.4 定期演练与自动化检查清单流复制搭好只是开始定期演练才是保命的关键。我一般每季度做一次切换演练手动 promote 从库验证业务能连上新主库再把原主库重新接入。演练前做好备份演练后核对数据一致性。日常检查我列了个清单贴在运维手册里pg_stat_replication里所有从库 state 是否为 streamingreplay_lag是否在可接受范围内复制槽是否有堆积主库pg_wal目录大小是否正常从库磁盘剩余空间是否充足归档是否正常执行这套东西跑顺了流复制基本不会出大问题。真出问题的时候日志和监控数据就是你的救命稻草所以别省那点监控成本。我个人在实际操作中的体会是流复制的难点不在搭建而在长期运维中的细节把控。参数调优没有一劳永逸的答案得根据业务写入模式、网络质量、硬件配置不断调整。先把异步复制跑稳再逐步上同步最后补全监控和演练这个节奏最稳妥。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑