PostgreSQL的高可用方案里,Patreon负责自动化故障转移,HAProxy负责流量分发,两者组合是目前比较主流的做法。但不少人在整合时会遇到一个关键问题:HAProxy怎么知道哪个节点是主库?如果健康检查配置不对,读写请求可能被发到只读副本上,直接报错。这篇文章围绕这个核心问题,把整合的原理、配置和注意事项讲清楚。

一、Patroni的健康检查接口:整合的基础
Patroni为每个节点提供了REST API,默认监听在8008端口,这是HAProxy能够动态感知节点角色的关键。不同的HTTP端点返回不同的信息,HAProxy正是通过访问这些端点的返回码来判断节点的身份和状态。
最常用的几个端点如下:/master只在当前节点是主库(leader)时返回200,否则返回503;/replica只在节点是正常副本时返回200;/replica?lag=16MB additionally会检查复制延迟,延迟超过16MB就认为不健康,这个参数对读一致性要求高的场景很有用;/read-only则主库和副本都会返回200,适合做只读连接池的后端检测。
理解这些端点非常重要,因为HAProxy的配置本质上就是把HTTP检查规则映射到不同的监听端口上。一个常见的错误是用TCP端口5432做健康检查,这只能确认PostgreSQL进程活着,无法区分主从角色,切换之后流量依然会打到已经降级为副本的旧主库上,导致写请求全部失败。
二、HAProxy配置:读写分离的端口规划
整合的常规做法是给HAProxy规划多个监听端口,比如5000端口只转发到主库,5001端口转发到所有副本,5002端口读写都可以。应用端根据业务性质选择连接哪个端口,写请求连5000,读请求连5002或5001,这样就实现了透明的读写分离。
下面是一份可以直接参考的配置,假设三个节点分别为pg1、pg2、pg3:
global
maxconn 10000
log /dev/log local0
defaults
mode tcp
timeout connect 5s
timeout client 30m
timeout server 30m
listen postgres_master
bind *:5000
option httpchk
http-check expect status 200
default-server inter 3s fall 2 rise 2
server pg1 192.168.1.11:5432 check port 8008 check-http-GET /master
server pg2 192.168.1.12:5432 check port 8008 check-http-GET /master
server pg3 192.168.1.13:5432 check port 8008 check-http-GET /master
listen postgres_replicas
bind *:5001
option httpchk
http-check expect status 200
balance roundrobin
default-server inter 3s fall 2 rise 2
server pg1 192.168.1.11:5432 check port 8008 check-http-GET /replica
server pg2 192.168.1.12:5432 check port 8008 check-http-GET /replica
server pg3 192.168.1.13:5432 check port 8008 check-http-GET /replica
listen postgres_read_only
bind *:5002
option httpchk
http-check expect status 200
balance roundrobin
default-server inter 3s fall 2 rise 2
server pg1 192.168.1.11:5432 check port 8008 check-http-GET /read-only
server pg2 192.168.1.12:5432 check port 8008 check-http-GET /read-only
server pg3 192.168.1.13:5432 check port 8008 check-http-GET /read-only几个配置细节值得注意。首先,代理数据库流量必须用mode tcp,HAProxy不做协议解析,只是四层转发。其次,check port 8008指定健康检查走Patroni的REST API端口,而实际转发的连接仍然指向5432,检查端口和数据端口分开是这套方案的精髓。再次,http-check expect status 200配合check-http-GET /master保证了只有主库返回200时才会被标记为可用。
关于检测频率,inter 3s fall 2 rise 2表示每3秒检测一次,连续两次失败就摘除节点,连续两次成功再恢复。这个时间要和Patroni的loop_wait与ttl参数配合调整。如果检测太频繁,主从切换的瞬间可能出现短暂的双主视图;如果太稀疏,切换完成后应用要等更久才能恢复写入。一般建议failover总收敛时间控制在10到15秒以内。
三、HAProxy自身的单点问题:结合keepalived
上面的架构解决了数据库层的故障转移,但HAProxy本身成了新的单点。一旦HAProxy所在机器宕机,整个集群对应用来说就不可用了。标准解法是部署两台HAProxy,配合keepalived浮动一个虚拟IP,应用统一连这个VIP。
keepalived的核心配置是在两台机器上配置相同的VIP,优先级不同,通过VRRP协议协商由谁持有VIP。同时给keepalived加一个检测脚本,定期确认本机HAProxy进程存活,如果HAProxy挂了就主动降低优先级,把VIP漂移到另一台机器:
vrrp_script chk_haproxy {
script "killall -0 haproxy"
interval 2
weight 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
virtual_ipaddress {
192.168.1.100/24
}
track_script {
chk_haproxy
}
}备用节点上把state改为BACKUP、优先级设为90即可。这样最终架构是:应用连VIP的5000或5001端口,VIP落在其中一台HAProxy上,HAProxy通过Patroni API感知主从角色并转发到正确的PostgreSQL节点。任何一层发生故障,整个链路都能自动恢复,无需人工干预。
四、常见踩坑点与优化建议
第一, Patroni的REST API开启了认证时,HAProxy的健康检查也要带上认证头,可以用http-check send meth GET uri /master hdr Authorization\ Basic\ xxx的写法(较新版本语法),否则所有节点都会被判定为不健康。第二,应用连接池要注意tcp模式下长连接的角色漂移问题:客户端通过5000端口建立的连接,在切换后旧连接可能还挂在旧主库上,建议应用侧开启target_session_attrs=read-write(libpq驱动)或在连接出错时强制重建连接。第三,option http-keep-alive与option httpchk的组合在老版本HAProxy上有兼容性问题,建议使用1.8以上版本。
另外,Patroni配合pgbouncer做连接池时,HAProxy应该放在pgbouncer前面,而不是直接怼到PostgreSQL上,这样能避免故障切换瞬间连接风暴打满数据库的max_connections。监控方面,可以在HAProxy的listen段加上stats enable开启状态页,观察后端节点的健康状态变化,排查切换问题时非常有用。
总结一下,Patroni加HAProxy的整合核心就一句话:让HAProxy用Patroni的REST API做七层健康检查,用不同端点区分主从角色,再用多端口规划实现读写分离,最后用keepalived补齐HAProxy自身的高可用。把这套架构理顺之后,PostgreSQL集群的自动化运维会轻松很多。
PatroniHAProxyPostgreSQL高可用修改时间:2026-09-12 23:22:38