免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

PgBouncer 详解:从部署配置到连接池调优与运维实践

PgBouncer 详解:从部署配置到连接池调优与运维实践 相信不少做 PostgreSQL 的人都被这句报错砸过脸FATAL: sorry, too many clients already。我第一次遇到时应用刚从 5 个实例扩容到 30 个每个实例的连接池配了 50瞬间涌过来 1500 个连接请求而数据库的max_connections只有 100。都不用多想整个服务直接雪崩。这个问题的根源不在于哪一行配置写错了而在于 PostgreSQL 的连接模型本身它是一个多进程架构每一个客户端连接都会对应一个 backend 进程。连接数一多光进程调度和内存开销就够数据库喝一壶。而 pgbouncer 就是专门解决海量客户端连接如何收敛成少量数据库连接这种供需矛盾的中间件。这篇内容打算把 pgbouncer 从部署、配置、容量规划到日常维护和实战踩坑完整过一遍。适合刚接手 PG 运维的同学、后端开发以及想搞清楚到底要不要上连接池、池子该开多大的人。标题里写了详解那我们就从原理讲到参数从排错讲到调优把我踩过的坑和总结出来的经验一并放进去。1. 为什么需要 pgbouncerPostgreSQL 连接模型的背后代价1.1 多进程架构与连接数的爆炸效应PostgreSQL 和 MySQL 最大的不同之一就是它的连接模型。MySQL 是线程模型新来一个连接在线程池里分配一个线程开销相对小PostgreSQL 则是进程模型每来一个连接主进程postmaster就 fork 出一个 backend 进程。这个 fork 的开销不只是多开一个进程这么简单。每个 backend 进程都有独立的私有内存区域包括 work_mem、还有各类缓存结构。虽然一个空闲连接只占几 MB 到十几 MB但一旦连接开始执行复杂查询、排序、哈希连接内存消耗会急剧上升。100 个连接也许只有几百 MB 开销调到 500、1000 的时候光连接本身就能吃掉好几个 GB 内存。更麻烦的是连接风暴。应用扩容、定时任务集中启动、服务重启后流量瞬间涌入这一秒里可能有几百个连接同时在握手、做认证协商、fork 进程。数据库在这段时间内 CPU 打满、load 飙升完全没法正常处理业务查询。我在生产环境见过一个比较典型的场景某个报表系统每天早上 8 点定时跑任务直接连数据库三百多个并发任务瞬间建立连接。数据库连接数从 30 跳到 400然后 SELECT 全部开始排队。加了 pgbouncer 之后连接收敛到 60 个任务跑得更快数据库也安静了。这就是中间件连接池的核心价值把数据库从连接数爆炸里解放出来。1.2 应用内连接池与中间件连接池的两级分工有同学会问我代码里已经有连接池了比如 Java 的 HikariCP、DruidPython 的 psycopg2 pool为什么还要在数据库前面再放一层 pgbouncer这两者的职责是不一样的。应用内连接池解决的是应用与数据库之间的连接复用它让单个应用实例不用每来一个请求就新建连接、断连。但它的作用是局部的它管不到其他实例。你有 20 个 Java 实例每个实例内连接池配 100整体就是 2000 个连接候选。数据库端的max_connections不可能陪你无限涨。pgbouncer 所在的位置是应用层连接池和数据库之间的一道收敛闸门。它把上游几千个应用连接根据配置收敛成下游几十个真实数据库连接。应用内连接池负责复用自己到 pgbouncer 的连接pgbouncer 负责复用自己到 PostgreSQL 的连接。两层各有各的池子中间件解决的是全局连接收敛问题应用连接池解决的是局部连接复用问题。这两者是配合关系不是替代关系。同类工具中PostgreSQL 生态里还有一个 pgpool-II功能比 pgbouncer 多可以做读写分离、负载均衡、自动故障转移但它的架构更重配置复杂度也高不少。如果只是单纯解决连接数问题我几乎都推荐 pgbouncer它单一职责、部署简单、内存占用极小一个实例通常只占几十 MB 内存却能管住成千上万个客户端连接。2. 三种连接池模式session、transaction、statement 的本质区别2.1 transaction 模式为什么是默认选择pgbouncer 最核心的配置是pool_mode它决定了连接到底以什么粒度进行复用。模式有三种session、transaction、statement。默认推荐生产环境用 transaction 模式。transaction 模式的语义是连接在事务开始时被分配事务结束时被归还。对大多数 Web 业务来说请求就是开启事务、执行若干 SQL、提交或回滚、结束事务生命周期很短通常几毫秒到几百毫秒。事务一结束这个数据库连接就回到池子里可以被下一个客户端事务继续使用。这样做的效果非常明显。比如你有 100 个应用连接每个连接每秒执行 10 个短事务pgbouncer 只需要一个很小的后端连接池就能满足吞吐例如 20 个。因为任意时刻真正同时在跑的活跃事务可能只有 15 个剩下 5 个连接在池子里待命。后端连接数从 100 降到了 20PG 的压力瞬间缓解。transaction 模式适合绝大多数 OLTP 场景订单查询、库存扣减、登录鉴权、内容列表这些业务的事务普遍很短而且没有跨事务的会话状态依赖。2.2 session 模式与 statement 模式各自适用场景session 模式的语义是最笨的客户端连上来pgbouncer 就给它分配一个后端连接直到客户端主动断开这个后端连接才会被回收。此时客户端和 PostgreSQL 后端进程几乎是一一对应的。它省下的只是反复建立 TCP 连接、做认证、fork 进程的握手开销并没有真正收敛数据库连接数。什么场景必须用 session 模式凡是事务之间存在会话状态依赖的比如 SQL 里用了临时表、SET会话变量、LISTEN/NOTIFY、服务端PREPARE、会话级 advisory lock。这些状态绑在 backend 进程上如果连接被其他客户端复用状态就丢了。管理后台、需要跑复杂报表的 psql 脚本、某些特殊应用适合单独指定为 session 模式。statement 模式就更特殊了它以单条 SQL 语句为粒度分配连接语句结束就归还。它的复用粒度最细后端连接数可以压得非常低但代价是如果你在一个显式事务里发多条 SQL这几条 SQL 可能会被分配到不同的后端连接上执行。事务语义就直接被拆碎了。所以 statement 模式基本上只适合客户端每次请求都是 autocommit、且不在乎事务完整性的场景。生产环境里我很少见到把它作为默认模式用的偶尔会拿来服务某个固定跑SELECT的只读接口。2.3 一个请求在三种模式下的连接流转用一个简单的例子来理解三种模式的差异。假设有 100 个 Web 请求到达每个请求内部是一条UPDATE加一个COMMIT。session 模式100 个客户端连接pgbouncer 会尝试拉起 100 个后端连接即使同一时刻只有 10 个请求在真正执行 SQL其余 90 个连接也在池里占着茅坑。池的大小只受max_client_conn和数据库连接数上限约束。transaction 模式100 个客户端连接进来但真正需要后端连接执行的只有当前活跃的事务。假设同一个时刻最多 10 个事务在跑那么后端连接只需要 10 到 15 个左右其余客户端连接在 pgbouncer 内部排队等待空闲连接。statement 模式同样 100 个请求如果每条语句都自动提交那后端连接的占用时间会更短可能只需要 5 到 10 个连接就够用。一旦有人绕过 autocommit 写了事务就会出问题。理解了这三种模式的区别再看这些参数就会明白为什么default_pool_size这个参数的语义会受模式影响在 session 模式下它管不住连接数因为每个客户端必须绑一个后端连接在 transaction 和 statement 模式下它才是真正发挥收敛作用的池容量。3. 部署与基础配置从源码编译到 systemd 托管3.1 前置依赖与源码编译安装pgbouncer 的安装方式主要有三种系统包管理器安装、官方源安装、源码编译。生产环境里我的习惯是源码编译因为不少发行版自带的 pgbouncer 版本偏旧而旧版本对 PostgreSQL 新默认认证方式的支持是硬伤这个后面讲踩坑时会具体说。源码编译前需要准备几个依赖libeventpgbouncer 的核心事件库必装开发包。openssl及头文件用于 TLS 支持和部分认证算法。pkg-config、gcc、make这些基础工具。以 Ubuntu 为例先安装依赖apt update apt install -y build-essential pkg-config libevent-dev libssl-dev然后下载源码编译。pgbouncer 的源码可以从官网或 GitHub 获取wget https://www.pgbouncer.org/downloads/pgbouncer-1.22.1.tar.gz tar xzf pgbouncer-1.22.1.tar.gz cd pgbouncer-1.22.1 ./configure --prefix/usr/local/pgbouncer --with-libeventyes --with-opensslyes make -j4 make install编译完成后二进制文件在/usr/local/pgbouncer/bin/pgbouncer。执行pgbouncer --version能看到版本号和编译特性。这里要强调一下新版本编译时pkg-config是必须的如果你在 configure 阶段看到找不到 libevent 的报错先检查pkg-config --libs libevent能不能输出内容。建议用独立系统用户运行 pgbouncer不要让它直接以 root 身份持有数据库连接。创建一个专用用户useradd -r -s /bin/false pgbouncer mkdir -p /etc/pgbouncer /var/log/pgbouncer /var/run/pgbouncer chown -R pgbouncer:pgbouncer /etc/pgbouncer /var/log/pgbouncer /var/run/pgbouncer3.2 pgbouncer.ini 最小可用配置pgbouncer 的配置集中在/etc/pgbouncer/pgbouncer.ini。我给出一个最小可用的基础配置然后把每一项的作用解释一遍[databases] appdb host127.0.0.1 port5432 dbnameappdb [pgbouncer] listen_addr 0.0.0.0 listen_port 6432 unix_socket_dir /var/run/pgbouncer auth_type scram-sha-256 auth_file /etc/pgbouncer/userlist.txt admin_users pgbouncer pool_mode transaction max_client_conn 1000 default_pool_size 50 min_pool_size 5 reserve_pool_size 5 reserve_pool_timeout 3 server_lifetime 3600 server_idle_timeout 300 idle_transaction_timeout 0 query_timeout 0 pidfile /var/run/pgbouncer/pgbouncer.pid logfile /var/log/pgbouncer/pgbouncer.log[databases]段定义的是pgbouncer 这边的数据库名与上游 PostgreSQL 连接串的映射。appdb是客户端连接 pgbouncer 时使用的库名dbnameappdb是它实际转发到 PostgreSQL 时的库名。listen_addr和listen_port决定 pgbouncer 监听在哪里。默认端口是 6432故意和 PostgreSQL 的 5432 区分开。生产环境建议监听在内网地址不要直接暴露公网。auth_type是比较关键的一个配置。它表示 pgbouncer 自己这层用什么方式做客户端认证。如果业务全在内网有人会图省事配trust但我不推荐至少要保证连接需要密码或密钥。scram-sha-256是 PostgreSQL 新版本默认的密码认证方式也是 pgbouncer 新版推荐的方式。admin_users是允许登录 pgbouncer 控制台的管理用户。后面讲监控的时候需要用到这个用户。pool_mode transaction是生产环境的默认推荐第二节已经专门讲过模式区别。3.3 userlist.txt 认证文件的生成与格式陷阱auth_file指向的文件是 pgbouncer 自己做客户端认证时读取的用户名和密码清单。格式很简单username password但这里的password不是明文。当你把auth_type配成scram-sha-256时第二项要填能从 PostgreSQL 那边复制过来的 SCRAM 验证串当你配成md5时第二项是md5开头的 MD5 哈希串。很多初次接触的人在这里栽跟头以为直接放数据库用户的明文密码就行结果一直报认证失败。最稳妥的生成方式是从 PostgreSQL 本身导出。在 PostgreSQL 主库上执行pg_dumpall --globals-only -U postgres这个命令会输出所有角色的定义包括带加密密码的CREATE ROLE语句。以postgres用户为例输出大概是这样的CREATE ROLE postgres LOGIN SUPERUSER PASSWORD md5xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;把其中的用户名和密码串整理成 pgbouncer 的 userlist.txt 格式即可postgres md5xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx如果你的 PostgreSQL 用的是 SCRAM 认证pg_dumpall输出里的密码串会是SCRAM-SHA-256$4096:...这种格式直接放到第二项就行。注意这里不能截断要整串复制。还有一个细节改了 userlist.txt 之后不需要 RELOAD新连接会在认证时实时读取文件内容。这也是排查认证问题时候的一个判断依据。3.4 systemd 服务文件与启动验证源码编译安装后pgbouncer 没有自带 systemd service 文件需要自己写一个。我一般放在/etc/systemd/system/pgbouncer.service[Unit] DescriptionPgbouncer - PostgreSQL Connection Pooler Afternetwork.target postgresql.service [Service] Typeforking Userpgbouncer Grouppgbouncer ExecStart/usr/local/pgbouncer/bin/pgbouncer -d /etc/pgbouncer/pgbouncer.ini ExecReload/bin/kill -HUP $MAINPID ExecStop/bin/kill -INT $MAINPID LimitNOFILE65535 Restarton-failure [Install] WantedBymulti-user.target注意ExecStart里的-d参数表示 daemon 模式就是让 pgbouncer 启动后转入后台运行。systemd 管理这种守护进程时配上Typeforking才能正确跟踪主进程 PID。启动和验证systemctl daemon-reload systemctl enable --now pgbouncer systemctl status pgbouncer然后检查端口和数据连接ss -lntp | grep 6432 psql -h 127.0.0.1 -p 6432 -U appuser -d appdb能正常进出 SELECT 就说明最基础的链路已经通了。此时到 PostgreSQL 那边看一眼pg_stat_activity你会看到一个名为pgbouncer的会话这就是 pgbouncer 建立并持有的后端连接。4. 连接池容量规划关键参数只有理解了才会调4.1 与连接数相关的参数族default_pool_size是最常见的调整对象但它不是孤立的。和连接数相关的参数应该看作一个家族参数默认值作用max_client_conn100pgbouncer 允许的最大客户端连接数入站闸门default_pool_size20每个 user/database 组合的最大后端连接数min_pool_size0后端连接池的最小预创建连接数建连慢时很有用reserve_pool_size0当池子不够用时临时追加的连接数上限reserve_pool_timeout5客户端等待超过该秒数后启用 reserve 连接max_db_connections0单个 database 的总连接数上限0 表示不限制max_user_connections0单个 user 的总连接数上限0 表示不限制max_client_conn管的是多少个应用连接可以连到 pgbouncer它不等同于数据库连接。应用实例多了这个值要跟着调大同时要注意系统文件描述符限制也就是ulimit -nsystemd 单位文件里的LimitNOFILE也要同步调整。默认的 100 在稍微有点规模的应用面前根本不够用。default_pool_size管理的是同一时刻最多有多少个真实 PostgreSQL 连接服务于某组 user/database。它是最关键的收敛参数。注意作用域是每个 user 和 database 的组合。如果你有 3 个库、3 个用户理论上最多可能建立 9 个组合每个组合池 50那后端连接可能就是 450算总量时必须把这个乘法关系算进去否则很容易把 PostgreSQL 打爆。min_pool_size设置预创建连接作用是在流量低谷时保持少量后端连接存活这样当突发流量来临时新事务不用等 TCP 建连和认证。数据库的连接建立本来就慢加上 TLS 握手一次新的后端连接可能要几十毫秒如果连接风暴集中发生这个耗时还会放大。对延迟敏感的业务min_pool_size建议设置成default_pool_size的 10% 到 20%。reserve_pool_size和reserve_pool_timeout是保护机制当请求量超过default_pool_size新事务先进入等待队列如果等待超过reserve_pool_timeout秒pgbouncer 会临时启用 reserve 连接来消化积压。这能在瞬时流量高峰时多撑一会儿但 reserve 连接也是数据库连接总量上要留好余量。4.2 一种实用的容量估算方法容量规划不能拍脑袋。我的建议是把两端都算清楚一端是应用侧需要多少客户端连接另一端是数据库侧能承受多少后端连接。第一步确认 PostgreSQL 的max_connections。假设是 200。第二步统计业务里实际要用到的 user/database 组合。假设只有一个主要业务库 appdb一个应用用户 appuser那么后端连接池就是这一个组合的池子。第三步计算 default_pool_size 的上限。需要给 PostgreSQL 留出其他用途的连接比如运维连接、备份工具、pg_dump、临时手工查询。我习惯预留 20%app 可用连接 200 * 0.8 160如果一个组合承载绝大多数流量default_pool_size可以设在 100 到 120 之间reserve_pool_size 再放 10 到 20。但如果同时有 4 个库每个库都要池子那每个库的池子就要除以接近 4不能每个都配 100。第四步算 max_client_conn。假设有 20 个应用实例每个实例内连接池上限 50总量是 1000。加上 pgbouncer 控制台连接和管理人员的连接max_client_conn至少要配 1200否则应用实例只要一启全量连接pgbouncer 会先报no more connections allowed。这里容易忽略的问题是应用连接池通常不会全部占满尤其 HikariCP 这类工具是按需增长、空闲回收的。max_client_conn 不需要等于所有应用连接池上限的总和但要留一定的冗余来应对实例滚动发布的场景。4.3 生命周期与超时参数除了连接数还有几个和时间相关的参数直接决定连接池的稳定性。server_lifetime是后端连接的最大存活时间默认 3600 秒。也就是说不管业务是否繁忙一个后端连接最多存活一小时后会被 pgbouncer 主动断开重建。这是为了清理长期连接在 PostgreSQL 端累积的事务快照状态防止老事务影响 VACUUM 回收垃圾。不要把这个参数调得过大有些人图省事改成 86400长期来看会埋下性能隐患。server_idle_timeout是后端连接空闲多久后被回收默认 600 秒。和server_lifetime的区别在于它针对的是空闲连接不是所有连接。如果业务有明显的波峰波谷这个值可以适当调大避免高峰期频繁重建连接。idle_transaction_timeout是对付事务开启后一直不提交也不回滚的会话。默认是 0即不限制。生产环境里应用层连接泄漏、事务超时设置不当经常会出现一堆idle in transaction的会话占着连接。如果业务方确认没问题可以考虑设一个较大的值比如 300 秒超过 5 分钟的事务强制断开给数据库一个兜底。但设置之前一定要确认没有长事务依赖否则会误杀正常任务。query_timeout同理限制单条 SQL 执行总时长。默认 0 表示不限。很多连接池假死问题的最终根因就是某条慢 SQL 霸占连接导致池子耗尽。在 DBA 无法快速介入的情况下一个合理的 query_timeout 能救命。5. 运行监控与日常运维检查5.1 控制台与 SHOW 命令pgbouncer 自带一个控制台连接方式非常直接psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer注意这里连的数据库名写pgbouncer用户写admin_users里配置的用户。登录后可以用SHOW命令查看各类状态常用的是这几个SHOW CONFIG当前生效的配置排查参数是否真的生效很有用。SHOW POOLS每个 user/database 池子的实时状态。SHOW STATS累计统计包含事务数、查询数、等待时间等。SHOW CLIENTS/SHOW SERVERS客户端连接和服务端连接的明细。SHOW DATABASES数据库级别的连接状态。控制台里还能执行运维操作比如RELOAD重新加载配置、PAUSE暂停接受新查询、RESUME恢复、SHUTDOWN关闭 pgbouncer。PAUSE在维护数据库时非常有用先把连接池暂停让存量事务自然结束然后再切数据库业务影响会比较小。5.2 从监控数据判断健康状态SHOW POOLS输出里我最关心的是cl_waiting和sv_active这两个字段。cl_waiting表示有多少客户端连接正在排队等待后端连接。如果这个值持续大于 0说明池子的容量不够或者后端连接被长时间占用事务执行不过来。偶发的一两个等待是正常的但长期积压就需要扩容或者排查慢事务。sv_active是当前正在执行事务或语句的后端连接数。如果接近default_pool_size说明池子在满负荷运行。此时要结合SHOW STATS看total_wait_time和avg_xact_time。total_wait_time占比高说明客户端在 pgbouncer 入口排队的时间多瓶颈在池容量avg_xact_time远超avg_query_time说明事务里有大量时间消耗在非查询环节比如锁等待、应用逻辑耗时单纯扩容可能治标不治本。SHOW STATS里的avg_query_time可以看作数据库连接质量的直观指标。如果这个数字突然下降但应用侧响应变慢往往是连接泄露或者后端连接重建过于频繁导致的。日常巡检可以写个小脚本把SHOW POOLS的输出拉下来判断cl_waiting 0的持续时间超过阈值就报警psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c SHOW POOLS | \ awk NR2 {if ($5 0) print $1, clients waiting:, $5}如果要接 Prometheus生态里也有 pgbouncer_exporter 这类现成方案。但我的经验是先把SHOW命令这些基础数据看明白再上监控系统否则只会收集一堆看不懂的指标。6. 实战排坑我遇到的几个影响很大的问题6.1 认证失败PG 默认 scram-sha-256 与旧版 pgbouncer 的冲突这是我从 PostgreSQL 12 升到 15 后踩过的最典型一个坑。现象是客户端连 pgbouncer 能到认证环节但执行psql -h 127.0.0.1 -p 6432 -U appuser -d appdb时一直报FATAL: password authentication failed当时连接串、密码、权限全检查了一遍都没问题。最后发现根因在两点一是 PostgreSQL 15 开始password_encryption默认是scram-sha-256而旧版本 pgbouncer1.16 之前对 SCRAM 的支持不完整二是 userlist.txt 里放的是旧格式的 md5 串pgbouncer 拿去和客户端握手时对不上。排查链路其实很清晰。先在 pgbouncer 侧把log_connections 1打开看认证日志里有没有具体的失败原因。然后在 PostgreSQL 侧用pg_dumpall --globals-only重新导出最新的密码串更新 userlist.txt。最后如果 pgbouncer 版本太旧直接升级到新版本我这边现在统一用 1.22 以上的版本auth_type scram-sha-256配合新版 userlist 里的SCRAM-SHA-256$...串基本没有再出过认证问题。还有一个容易被忽略的细节pgbouncer 做的是代理层认证客户端发给它的密码格式取决于auth_type。如果auth_type md5客户端给的响应只能按 md5 来验证如果auth_type scram-sha-256客户端则用 SCRAM 流程。你需要在 pgbouncer 这一层选一个与 userlist 内容匹配的方式不能数据库端用 scram、pgbouncer 端却写auth_type md5那样必然失败。6.2 长事务让连接池假死池化并非万能有段时间线上一个订单服务频繁出现数据库连接池耗尽的报警。检查 pgbouncer 的SHOW POOLS发现sv_active长时间顶满cl_waiting一直在涨但数据库侧pg_stat_activity里真正在跑查询的会话非常少。绝大部分连接都处于idle in transaction状态。这就是 transaction 模式最容易踩的坑它确实能复用连接但前提是事务要尽快结束。只要有一个客户端开启事务后迟迟不提交、不回滚这条后端连接就被它锁定了即使这个连接此刻没有执行任何 SQL也不能分配给其他客户端。一堆idle in transaction连接堆积起来效果和 session 模式占用连接没区别。这种问题靠调大default_pool_size只能缓解一时。根治办法一是应用层保证事务要么 commit 要么 rollback设置事务超时二是把 pgbouncer 的idle_transaction_timeout配置上比如 300 秒超过 5 分钟的 idle transaction 会被强制断掉给其他请求腾出连接。但要注意idle_transaction_timeout是强杀动作如果业务里有合法的长事务误杀会导致应用层抛异常上线前必须和业务方确认清楚。当时我们排查的顺序是先看SHOW POOLS确认sv_active是否异常再上数据库查pg_stat_activity里state idle in transaction的会话有多少最后定位到是某个服务的数据库连接池配置里transactionTimeout设成了 0等于关掉了事务超时。改完之后问题消失。6.3 服务端预备语句在 transaction 模式下失效还有一个比较隐蔽的兼容性问题和应用程序的 prepared statement 有关。PostgreSQL 支持两种预备语句服务端PREPARE和协议层的扩展查询预备。很多 JDBC 驱动默认开启了服务端预处理比如 PostgreSQL JDBC 驱动里有一个prepareThreshold参数默认是 5意思是同一条 SQL 执行 5 次后驱动会发PREPARE到服务端。这在直连 PostgreSQL 时没有问题但一旦前面挂了 pgbouncer 并启用 transaction 模式服务端预备语句就会出问题。原因在于PREPARE 的语句对象绑定在某个后端连接上而 transaction 模式下客户端的下一次事务可能被分配到另一个后端连接。此时再执行EXECUTE新连接上根本没有这个预备语句直接报错prepared statement does not exist。解决办法要看应用端。对 JDBC 应用可以显式配置驱动将prepareThreshold设为 0强制走客户端解析或每条语句都用简单查询协议。或者如果你必须依赖服务端 PREPARE那就只能把这条连接放到 session 模式里让它长期绑定同一个后端连接。同样的道理也适用于临时表、SET会话参数和LISTEN/NOTIFY。凡是依赖后端连接上的会话状态的功能在 transaction 模式下都可能出现这次事务能用、下次事务就不见了的问题。评估应用是否适合用 pgbouncer这个兼容性检查一定要做在前面。6.4 运维死角DNS、FD 限制和证书更新最后分享几个零散但影响很大的运维细节。后端数据库地址我建议直接写 IP而不是域名。pgbouncer 对配置里 host 的处理和很多应用驱动不同它不会那么积极地做 DNS 重解析。我遇到过数据库域名解析记录变更后pgbouncer 新建连接还是打到旧 IP 的情况排查起来很费劲。如果写 IP就完全绕开了这个坑。文件描述符限制容易被忽略。max_client_conn调到 5000但 systemd 的LimitNOFILE还是默认的 1024连接数一旦上去就会出现setrlimit failed或者莫名其妙地拒接新连接。一个经验值LimitNOFILE至少是max_client_conn的 2 倍因为每个客户端连接还可能伴随内部 socket、日志 fd 等额外开销。最后是证书更新。如果 pgbouncer 开了 TLS上下游证书更新后旧的连接不一定感知到但新连接的握手依赖新证书的加载时机。我这边证书自动部署脚本更新完证书文件之后会顺手执行一次 pgbouncer RELOAD让配置和证书重新加载避免新连接握手时读到不完整的证书文件。这个步骤看起来简单但漏掉一次线上就会出现间歇性连接失败。这些坑一个接一个踩下来我的体会是pgbouncer 本身是个很成熟的工具大多数问题都不是它自身有 bug而是连接池模式和环境习惯之间的冲突。上线前把三个问题问清楚就够了业务会话是否干净有没有临时表、PREPARE、长事务、数据库连接预算够不够、pgbouncer 版本是否支持 PostgreSQL 的认证方式。这三个问题想清楚pgbouncer 部署和调优的基本盘就稳了。
返回列表