ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PostgreSQL 18实战:Pgpool-II负载均衡配置与生产避坑指南

PostgreSQL 18实战:Pgpool-II负载均衡配置与生产避坑指南 做PostgreSQL生产环境运维的同学大概率迟早会碰上一个场景业务量上来之后读请求把主库CPU打到90%以上慢查询一条接一条监控告警响个不停。这时候很多人第一反应是加内存、加SSD但硬件堆完没几天瓶颈又回来了。真正治本的做法是在数据库前面加一层流量路由把读请求均匀分流到只读节点上。这篇实战指南要聊的就是PostgreSQL 18环境下用Pgpool-II实现负载均衡的完整方案从原理拆解到配置落地再到生产环境常见坑的排查方法一次讲透。如果你正在维护一个PostgreSQL集群或者准备从单机往主从架构演进这篇内容值得仔细看。Pgpool-II是目前PostgreSQL生态里最常用的中间件之一它同时承担连接池、负载均衡、读写分离、自动故障转移等职责相比HAProxy纯四层转发或者应用层自己实现读写分离Pgpool-II的集成度更高对PostgreSQL协议的理解也更深入。下面直接进入正题。1. 为什么需要负载均衡先搞清楚要解决什么问题1.1 读多写少场景下的典型瓶颈大部分业务系统的访问模型都是读多写少比如内容管理系统、电商前台、报表查询平台读请求占比往往在80%以上。在没有负载均衡的情况下所有查询都打到主库主库的CPU和IO很快成为瓶颈。我在实际项目里见过一个很典型的案例一个日活十万左右的业务系统数据库单表数据量在两千万级别高峰期每秒查询量大概3000左右写请求只有两百。主库CPU持续跑在85%以上大量查询排队页面响应时间从50毫秒飙升到800毫秒。后来通过部署两个只读从库用Pgpool-II做读写分离和负载均衡主库CPU直接降到30%以下整体吞吐提升了接近三倍。这就是负载均衡带来的直观收益。那为什么不直接用物理主从复制加上应用层自己判断读写路由呢当然可以但问题在于业务代码侵入性太强。你需要在每个数据访问层写死数据源路由逻辑而且还难以应对从库故障的自动摘除。一旦某个从库挂了应用不知道还是会往那个节点发送查询导致大量报错。Pgpool-II的价值就在于把这些事情从应用层剥离出来统一在数据库中间件层面解决。1.2 负载均衡方案选型对比PostgreSQL生态里做负载均衡的方案有好几种很多人纠结选哪个下面用表格直接对比一下常见的三个方向。方案工作层级读写分离能力连接池自动故障切换适用场景Pgpool-IIPostgreSQL协议层原生支持基于SQL解析内置内置中小型集群一体化方案HAProxyTCP四层不支持只做连接分发无需配合外部脚本纯流量分发应用层多数据源应用代码可自行实现依赖连接池组件需自行开发高度定制化需求从表格能看到Pgpool-II最核心的优势是它工作在PostgreSQL协议层能够识别客户端发过来的SQL是SELECT还是写操作从而做到真正的读写分离。HAProxy只能做到连接层面的分发要么把连接全部分到主库要么全部分到从库无法做到同一个连接内根据SQL类型自动路由。不过要注意Pgpool-II也有它的局限。它本身是一个独立进程如果部署不当会成为新的单点所以生产环境通常需要部署两个Pgpool-II节点配合keepalived做VIP漂移。另外它对复杂事务和临时表场景需要额外的配置处理这些细节后面会展开讲。1.3 Pgpool-II的核心能力边界在实际落地之前我觉得有必要把Pgpool-II的能力边界讲清楚这样你才知道哪些场景适合它哪些场景要用别的工具配合。先说它能做的事情。第一连接池复用客户端不再频繁创建和销毁数据库连接应用侧连接数可以大幅降低第二负载均衡读请求按权重分发到各个只读节点第三读写分离通过解析SQL语义把INSERT、UPDATE、DELETE路由到主库SELECT路由到从库第四自动故障转移检测到主库故障时自动把某个从库提升为新的主库第五在线恢复配合流复制可以把新节点随时纳入集群。它不能做什么呢最典型的是跨节点分布式事务如果业务要求一个事务里同时更新主库和从库的数据Pgpool-II做不到关系型数据库的分布式事务扩展本身就是个复杂课题。另外它不解决数据同步延迟问题从库的数据始终存在一个微小的复制延迟这是物理复制的硬特性任何中间件都掩盖不了。清楚了能力边界之后你就能判断Pgpool-II适合的是主从架构下的读写分离与连接管理而不是分布式数据库替代品。接下来我们深入它内部的工作机制。2. Pgpool-II核心机制拆解负载均衡到底怎么工作2.1 读写分离与负载均衡的路由规则Pgpool-II之所以能区分读写请求靠的是对前端发过来的SQL进行解析和判断。它在内部维护了一套规则凡是SELECT开头的语句默认走从库其他语句走主库。但这里有几个细节值得关注。第一个是事务内的SELECT。如果一个事务里先执行了写操作然后执行SELECT那么这条SELECT必须走主库否则会读到不一致的数据因为从库此时可能还没同步到这个事务的更新。Pgpool-II的默认策略是一旦事务内出现写语句整个事务后续的所有语句都路由到主库。这个行为用参数write_through_query可以控制默认是关闭的表示遇到写操作后事务内的读也全部走主库这个默认值在生产环境是合理的。第二个是隐式事务。很多ORM框架会把单个SQL包在BEGIN和COMMIT之间如果应用开启事务后第一条语句就是SELECTPgpool-II会把这个事务的路由结果保持住后续如果出现写操作它会在事务级别做切换把后面的语句全部转到主库执行。第三个是无FROM的SELECT。像SELECT 1这种不涉及表查询的语句走哪个库都无所谓Pgpool-II默认也会参与负载均衡。负载均衡的权重通过backend_weight参数控制。比如你有两台从库一台机器性能强另一台是老机器可以给它们设置不同的权重例如主库权重为0不参与读从库A为2从库B为1那么读请求会按2:1的比例分发。这个权重是概率加权不是严格每三个请求里精确分配两个给A一个给B而是长期趋近这个比例。2.2 连接池与会话状态管理Pgpool-II的连接池机制说白了就是在客户端和后端数据库之间再加一层连接复用层。客户端连接到Pgpool-IIPgpool-II从后端连接池里取一个空闲连接处理请求处理完再把连接放回池子。这样做的好处非常明显应用侧不需要维护大量真实数据库连接连接的建立和释放开销被大幅降低。在配置层面num_init_children控制Pgpool-II预创建的连接数可以理解为同时能处理多少个客户端会话max_pool表示每个Pgpool-II子进程最多缓存多少个后端连接。这个地方很多人容易配错。我见过一个生产事故num_init_children配了4结果业务高峰期同时在线会话超过4个后面所有请求全部排队等待数据库CPU反而没跑满因为流量都堵在Pgpool-II的连接池外面。所以这个值必须根据业务并发量实际压测来定而不是套用默认值。连接池还有一个隐藏的坑就是会话级临时对象。如果应用使用了CREATE TEMP TABLE或者自定义GUC参数比如SET search_path这些状态是绑定在具体数据库连接上的。当连接被池化复用后下一个客户端拿到这个连接时残留的临时表或参数可能造成数据错乱。Pgpool-II针对这个问题提供了connection_life_time参数定期重建连接同时应用侧要尽量避免在长连接里大量使用临时表。2.3 主备监控与自动故障转移逻辑自动故障转移是Pgpool-II最吸引人的特性之一但它也是配置最复杂、最容易踩坑的部分生产环境里因为故障转移误触发导致集群抖动的事故我见过多次。Pgpool-II通过定期发送探活请求来监控后端节点状态health_check_period控制检查周期默认是10秒health_check_timeout控制单次检查超时默认是20秒。如果连续health_check_max_retries次检查失败节点就被标记为宕机。主库宕机后的切换逻辑严格依赖failover_command脚本。这个脚本在Pgpool-II判定主库故障时执行核心动作一般是调用pg_ctl promote提升一个从库为新主库然后更新Pgpool-II的节点状态信息。这里最大的坑在于promote操作是异步的如果脚本写得不够健壮可能会出现旧主库其实还没彻底宕掉的情况双主写同一份数据后果就是数据分裂。我个人的建议是Pgpool-II的自动故障切换可以作为第一道防线但生产环境最好还是配合Patroni这类专门的集群管理工具来实现更严谨的failover决策。Pgpool-II负责流量路由和读写分离Patroni负责仲裁和选主两者分工是业界比较成熟的做法。不过本文聚焦的是Pgpool-II本身所以后面还是以它独立运作的视角来展开。3. 实操配置在PostgreSQL 18集群上部署Pgpool-II3.1 环境准备与安装方式先讲一下我这次演示的集群环境。三台机器分别对应主库、从库1、从库2另外单独一台机器装Pgpool-II做负载均衡入口。操作系统是CentOS 7.9PostgreSQL版本为18 Beta版Pgpool-II使用4.5版本。实际生产可能还在用PostgreSQL 15或16但配置逻辑完全一致不用太纠结版本数字。Pgpool-II的安装方式有两种主流方案一是通过各个发行版的软件仓库直接安装比如CentOS下可以配置Pgpool-II官方YUM源后执行yum install pgpool-II二是从源码编译安装适合需要定制化编译参数或者官方仓库没有对应版本的场景。这里贴一下源码编译的常规流程。我使用的是官方提供的tar包版本为pgpool-II 4.5。tar -zxvf pgpool-II-4.5.tar.gz cd pgpool-II-4.5 ./configure --prefix/usr/local/pgpool --with-pgsql/usr/local/pgsql make -j4 make installconfigure阶段有两个参数比较关键。--with-pgsql必须指向你PostgreSQL的安装目录因为Pgpool-II编译时需要借助PostgreSQL的头文件来解析SQL语义路径写错直接编译失败。--prefix指定安装路径建议单独放在一个目录不要和PostgreSQL混装后期升级维护会麻烦。编译完成后可执行文件pgpool、pcp_*系列管理命令、psql扩展工具都会生成在bin目录下。3.2 pgpool.conf核心配置逐项解析装好之后最重要的就是配置pgpool.conf。这个配置文件是整个Pgpool-II的灵魂参数非常多但生产环境常用的核心参数也就二十个左右。下面我按配置模块拆开讲每项参数都会说明含义和推荐值。# 连接监听配置 listen_addresses 0.0.0.0 port 9999 socket_dir /tmp # 后端节点配置 backend_hostname0 192.168.1.10 backend_port0 5432 backend_weight0 0 backend_data_directory0 /data/pgsql backend_flag0 ALLOW_TO_FAILOVER backend_hostname1 192.168.1.11 backend_port1 5432 backend_weight1 1 backend_data_directory1 /data/pgsql backend_flag1 ALLOW_TO_FAILOVER backend_hostname2 192.168.1.12 backend_port2 5432 backend_weight2 1 backend_data_directory2 /data/pgsql backend_flag2 ALLOW_TO_FAILOVER节点配置有几个细节要强调。backend_weight设为主库为0、从库为1意味着所有读请求都均匀分布到两台从库主库不参与读负载backend_data_directory必须准确填写数据目录填错会导致Pgpool-II无法判断节点角色的目录信息backend_flag的ALLOW_TO_FAILOVER允许该节点参与failover如果某个节点只是临时加进来测试可以去掉这个标记。再看连接池和进程相关参数。num_init_children 32 max_pool 4 child_life_time 300 child_max_connections 0 connection_life_time 300 client_idle_limit 0num_init_children 32表示Pgpool-II最多启动32个子进程同时处理客户端请求每个子进程是一个独立的后端连接管理者。这个值我前面说过必须结合业务压测来定可以先从32起调观察连接池排队情况再慢慢升。child_max_connections 0表示单个子进程处理的客户端连接数不设上限生产环境建议设为0让生命周期控制由child_life_time负责避免连接长期占用造成内存碎片。然后是负载均衡和读写分离配置。load_balance_mode on ignore_leading_white_space on read_only_function_list pg_is_in_recovery write_function_list nextval,setval primary_routing_query_pattern ^SELECT\s.*FOR\sUPDATEread_only_function_list这个参数很容易被忽略。它的作用是声明哪些函数是只读函数Pgpool-II会把包含这些函数的SELECT语句都路由到从库。pg_is_in_recovery通常会配置进去否则有些ORM启动时会调用它做连通性检查结果被路由到从库后返回false应用误以为当前连接的是主库逻辑就乱了。write_function_list很重要nextval、setval这两个序列函数本质上是写操作必须强制走主库否则在从库上执行nextval会直接报错因为从库只允许只读操作。primary_routing_query_pattern则是把带FOR UPDATE锁定的SELECT识别为主库操作这些语句读的是最新数据还要加锁绝不能走从库。3.3 启动集群并验证负载均衡效果配置完成后先启动后端PostgreSQL集群确保流复制状态正常。在主库上执行SELECT pg_is_in_recovery(); -- 返回 false 表示当前为主库在两台从库上执行同样命令应该返回true。然后启动Pgpool-IIpgpool -f /usr/local/pgpool/etc/pgpool.conf -n-n参数表示前台运行日志直接打到标准输出方便观察启动过程有没有报错。启动后可以用pcp_node_info命令查看Pgpool-II视角下的节点状态pcp_node_info -h 127.0.0.1 -U pgpool -p 9898 -w输出类似下面这样192.168.1.10:5432 PRIMARY 192.168.1.11:5432 STANDBY 192.168.1.12:5432 STANDBY节点角色识别正确说明配置基本没问题。接下来验证负载均衡效果。用psql连上Pgpool-II的9999端口执行几条查询SELECT * FROM t_user WHERE id 1; -- 在主库的日志里观察连接来源还有一种更直观的验证方式通过show pool_nodes命令查看当前会话的后端分配情况。Pgpool-II会在内部记录客户端连接实际使用了哪个后端节点。我在实际调优时习惯做一轮并发压测用pgbench生成300并发持续十分钟的读流量然后在三台数据库上分别看查询数量正常情况下两台从库的SELECT数量基本是持平的主库几乎只有少量写操作这就说明负载均衡真正生效了。4. 生产环境避坑常见问题与排查实录4.1 读请求全部走在主库从库成了摆设这是最常被问到的问题配置看起来没问题load_balance_mode on也开启了但监控显示主库的查询量还是非常高从库基本没流量。排查思路从三个方向入手。第一确认后端节点角色是否被正确识别。如果Pgpool-II把从库当成了主库或者全都当成主库负载均衡就不会生效执行pcp_node_info逐一确认。第二检查是否所有业务会话都走了事务导致路由失效。前面提到过如果应用框架自动开启事务且事务内先发生了写操作那么整个事务的读都会在主库执行。这种场景从Pgpool-II的日志里能看到特征大量查询都是事务内路由。第三检查配置里的ignore_leading_white_space参数如果设置为offSQL前面多一个空格就会被当成普通语句处理甚至导致路由判断失效。建议保持为on。除此之外还有一个比较隐蔽的原因是应用使用了带注释的SQL比如MyBatis生成的/* index */ SELECT ...Pgpool-II对注释的处理策略会在某些版本中影响解析结果。遇到这种情况可以临时开启Pgpool-II的调试日志把解析后的SQL打印出来看路由决策结果。4.2 健康检查误判导致频繁故障切换故障切换是保护机制但误触发就是灾难了。有一次生产环境集群里的从库被Pgpool-II频繁标记为宕机然后马上又恢复反复切换导致应用侧出现了大量连接中断报错。排查后发现问题出在健康检查参数配置太激进。health_check_timeout我配成了5秒health_check_max_retries配了1当时从库正好在做全量vacuum资源占用较高健康检查请求在5秒内没等来响应就被判定为宕机触发failover脚本把一个健康的从库给切掉了。后来我把health_check_timeout调整到10秒health_check_max_retries调整为3情况就再没出现过。这个案例想说明的是健康检查参数要结合数据库实际负载来配置不能为了追求快速感知故障而把超时设得太短。PostgreSQL在跑大事务、vacuum、索引重建时短时间内对轻量查询的响应确实可能变慢这时候误判的代价比延迟发现故障大得多。建议生产配置至少满足health_check_timeout * health_check_max_retries大于数据库业务高峰期一条慢查询可能持续的最长时间。4.3 读从库读到旧数据业务层出现一致性问题读写分离最常见的矛盾就是复制延迟。主库刚插入一条记录客户端立刻通过从库去读如果从库还没应用完这条WAL日志查询结果就是空的。这个问题的解决思路有三个层次。第一层是业务容忍很多读多写少场景对秒级延迟并不敏感比如资讯列表、商品详情这种无需处理。第二层是会话级读一致性Pgpool-II提供了delay_threshold参数如果从库的复制延迟超过这个阈值Pgpool-II会自动把该从库的读请求临时转发到主库保证用户读到的数据足够新。第三层是应用主动规避对强一致要求的查询在SQL里加注释强制走主库Pgpool-II支持/*NO LOAD BALANCE*/这种注释指令SQL前加上这个注释该查询就会被强制路由到主库。/*NO LOAD BALANCE*/ SELECT balance FROM accounts WHERE account_id 1001;这种方式虽然需要开发配合但效果最精准。我在实际项目中是三层组合使用默认延迟阈值设500毫秒业务上明确要求强一致的查询代码里加注释两者互相兜底基本没有出现过因延迟导致的投诉。4.4 常见问题速查表最后把我在多次部署和运维中遇到的高频问题整理成一张速查表方便你遇到同类问题时快速定位。故障现象常见原因排查动作连接被拒绝9999端口未监听或防火墙拦截ss -lntp确认监听检查iptables所有请求都走主库从库角色未被识别pcp_node_info节点状态检查backend配置频繁failover健康检查超时配置过短调大health_check_timeout和重试次数从库查询报序列不存在nextval被路由到从库在write_function_list中加入nextval连接池耗尽请求排队num_init_children偏小压测后按并发量调大该值切换后写不进去failover脚本未正确提升新主库检查脚本执行日志和promote命令是否生效负载比例不均衡后端权重配置不合理按硬件性能调整backend_weight这张表里的每一个问题都是我或者朋友在生产环境真实踩过的不是从文档里抄出来的理论。尤其是健康检查误判和nextval路由问题几乎每个刚上Pgpool-II的团队都会遇到一次。写在最后的实战心得从我个人的体会来看Pgpool-II是一个功能非常强但细节也很多的组件它的难点不在安装而在路由规则的细致理解。很多人上来就配一通能用就以为完事了等到事务内读取错乱、序列报错、从库负载不均这些问题冒出来才回头翻配置文档这时候排查成本就高了。我习惯的做法是每次改完Pgpool-II的配置都先用pgpool -f pgpool.conf --config-test做一次配置语法检查然后故意停掉一个从库观察应用侧是否能自动避开故障节点再恢复观察自动纳入整套验证通过之后才让配置生效到生产。这个流程虽然多花十分钟但能规避绝大部分低级失误。另外如果你想在现有集群上平滑引入Pgpool-II可以先把Pgpool-II挂到集群前面配置成直通模式也就是暂时不开启读写分离只做连接池观察一段时间确认不引入额外问题之后再打开load_balance_mode。这样分阶段上线风险会小很多也方便定位问题到底出在Pgpool-II还是业务层。
返回列表