开门见山说一句:很多人看到“Queue Mode(队列模式)”这个词,第一反应是消息队列,第二反应是某个连接池的隐藏开关。但放到 PostgreSQL 高可用部署这个场景里,Queue Mode 其实不是一个独立组件,它更像是一整套“排队机制”的集合:连接层在排队,同步复制在排队,选主竞争也在排队。这些排队是否存在、排得深不深、会不会把业务卡死,直接决定你构建的“高可用”到底是真的可用,还是一个故障就原形毕露的摆设。
这篇文章我基于自己搭过的 Patroni + etcd + HAProxy + PgBouncer 生产级集群来写,从三个层面的 Queue Mode 讲起,把部署步骤、参数取舍、性能调优、故障演练全过一遍。适合已经有 PostgreSQL 基础、正准备上高可用的 DBA、运维和架构师。看完你至少能回答一个问题:当主库开始排队等确认的时候,你的集群到底发生了什么,以及该怎么应对。
1. Queue Mode 到底在排什么队?
1.1 连接层:请求在进数据库之前就排队
先聊最直观的一层排队:业务连接打进来,数据库连接来不及处理,请求只能在中间层等着。
我最早接触这个场景是在做 PgBouncer 连接池压测的时候。PgBouncer 的 pool_mode = transaction 模式,客户端连接建立后,后端 PostgreSQL 连接在事务结束时会归还给连接池。如果后端连接已经被占满,新的客户端请求就要排队等一个空闲连接。这个等待队列在 SHOW POOLS 里可以直接看到,字段叫 client_active、client_waiting、server_active 等等。当 client_waiting 数量持续飙升,说明后端连接池已经顶不住了,要么调大 default_pool_size,要么就是慢查询太多、事务太长把连接都霸占了。
在高可用架构里,HAProxy 那一层也存在排队。当 Patroni 执行主备切换,旧主库还没有完全被摘掉、新主库还没被健康检查确认时,探活的连接会短暂失败,客户端要么重试,要么在 LB 层积压。这个窗口期就是 RTO 的一部分,也是很多人容易忽略的“隐性排队”。
所以连接层的 Queue Mode,本质上是为了削峰和保护后端数据库。但它也是一把双刃剑:排得太深,前端超时;排不进去,后端被打爆。后面第 4 节我详细说监控和参数怎么配。
1.2 复制层:同步提交就是一个确认队列
PostgreSQL 从 9.1 引入同步流复制之后,主库在提交事务时可能会等待备库返回确认。这个“等待确认”的过程,从机制上看就是一个典型的队列:事务按顺序进入 WAL 发送队列,备库每处理完一个 WAL 记录就回报一个确认,主库收到确认后才释放对应事务。
当 synchronous_commit = on 时,每个写事务都必须等到备库把 WAL 刷到磁盘,才能向客户端返回成功。如果备库网络抖动、磁盘慢,或者备库直接挂了,主库上的写事务就会卡在等待状态。你在 pg_stat_activity 里能看到大量会话的 wait_event 是 SyncRep,这个时候的数据库“活着”但“写不进去”,业务侧表现就是数据写入超时、队列暴涨。
这个确认队列的存在,是为了换取 RPO = 0,也就是主备切换时一个事务都不丢。但代价就是:主库写入性能的上限,取决于同步备库的确认速度,而不是主库自己的处理速度。理解这一点,你才能理解为什么同一个数据库,异步模式下 TPS 能冲到几万,而开了同步复制可能只剩三分之一。
1.3 选主层:Patroni 分布式锁的竞争队列
还有一层排队藏得更深,发生在“谁当主库”这个决策过程中。
Patroni 用 etcd 的租约来选主:当前 leader 在 etcd 里持有一个带 TTL 的 key,每过 loop_wait 秒续约一次。leader 故障后,key 过期,所有备库同时尝试创建这个 key,谁创建成功谁就是新的主库,其余备库进入等待状态,等下一次可能的机会。这个“竞争-失败-等待”的过程,从宏观上看也是一个队列。
这层 Queue Mode 对应用透明,但对运维来说非常关键。它决定了故障切换的触发时间(ttl + loop_wait + 健康检查周期)和切换后多久才能恢复写入(备库晋升 + 老主库被隔离)。如果 etcd 本身出了问题,比如三个节点只剩一个能连上,Patroni 可能连选主都没法进行,整个集群进入只读或不可用状态。这就是为什么 etcd 集群的部署质量,直接决定了高可用系统的底限。
三个层面的排队,分别对应连接层、数据层、协调层。它们之间会互相影响:同步复制排队会拉长事务执行时间,事务执行时间长了连接池被占满,连接池排队又导致新请求进不来,最后调度层再一抖动,整个链路就像堵车一样彻底的死锁。下面我直接给出一套能跑通这些机制的部署方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 高可用部署选型:为什么是 Patroni + etcd + HAProxy + PgBouncer
2.1 组件选型及各自职责
PostgreSQL 高可用的方案我见过不少:Keepalived + 流复制、repmgr、Patroni、还有些云厂商自带的只读实例。我最终长期用的是 Patroni,原因很明确:
- Patroni 通过 DCS(我选 etcd)保存集群状态,所有节点对“谁是主”达成共识,避免双主脑裂。
- 它能把
synchronous_standby_names等复制参数自动管理起来,这正好命中 Queue Mode 的核心需求。 - 提供 REST API,HAProxy 可以基于它做精确健康检查,切换时流量自动跟随新主。
- 支持
pg_rewind,老主库重新接入集群时不需要手工重建,恢复速度非常快。
etcd 在方案里负责两件事:一是保存集群拓扑(哪些节点、谁在主、同步状态如何),二是提供 leader lease 锁。它的选主机制天然就是分布式一致性的,这比 Keepalived 那种 IP 漂移方式要可靠很多。
HAProxy 其实可以替换成 F5、LVS 或者云负载均衡,但我在这里选 HAProxy 是因为它配置简单、配合 Patroni 的 /patroni/master 和 /patroni/replica 健康检查接口非常顺滑,一个后端组走写流量,一个后端组走读流量,读写分离和故障转移可以同时完成。
PgBouncer 放在数据库前面,主要解决两个问题:一是 Postgres 每连接一个进程,连接多了内存和上下文切换开销巨大;二是高可用切换后的连接重建需要时间,连接池可以缓存连接,让切换对应用更透明。它的 transaction 模式本身也有队列缓冲作用。
2.2 节点规划与拓扑说明
我建议的最小生产配置是这样的:
| 角色 | 节点数 | 用途 |
|---|---|---|
| etcd | 3 | 分布式协调层,奇数节点保证 quorum |
| PostgreSQL + Patroni | 3 | 一主两备,其中至少一个作为同步备 |
| HAProxy + PgBounce |
