原本我是 MySQL 的忠实粉丝,后来命运使然,接手了一个 PostgreSQL 项目,一边用一边学,遇到了不少问题,本文说说其中的连接池问题。
有人曾经问我「为什么 PostgreSQL 没有 MySQL 流行呢?」,我说是因为 PostgreSQL 没有服侍好 PHP。当然话有戏谑的成分在,不过仔细想想也不无道理,开发那些简单的增删改查程序,PHP 和 MySQL 是绝配,而 PHP 和 PostgreSQL 则明显不搭,其中最重要的一点是:PostgreSQL 连接是进程级的,而不是像 MySQL 那样是线程级的,也就是说,当一个请求过来时,PostgreSQL 需要 fork 出一个进程!
于是乎,当你使用 PHP 和 PostgreSQL 的双象组合时,数据库连接池是终将面对的一个问题,目前主流的解决方案有 Pgpool 和 PgBouncer,选择哪个好呢?不妨从它们各自的定位来判断,Pgpool 是一个整体解决方案,它不仅实现了连接池,还实现了负载均衡等等高级功能,而 PgBouncer 则仅仅专注于连接池。所以说,如果你除了连接池还需要负载均衡等功能,那么 Pgpool 通常是不错的选择,如果你只想要连接池功能,那么就和我一样使用小而美的 PgBouncer 吧。
在安装前需要考虑的到底在哪安装 PgBouncer:Web 服务器还是 DB 服务器?通常安装在 Web 服务器上是一个更好的选择,因为这样会把连接造成的延迟最小化;不过如果你有很多的 Web 服务器,如果挨个安装 PgBouncer 的话,很明显不利于集中管理,此时安装在 DB 服务器上是更明智的选择;还有些时候,两者都安装也不错。
实际安装很简单,以从源码安装为例,不过需要注意先安装好 libevent-devel 库:
shell> yum install libevent-devel
shell> tar zxvf pgbouncer-1.6.1.tar.gz
shell> cd pgbouncer-1.6.1
shell> ./configure
shell> make
shell> make install
安装完毕后注意看,会告诉你稍后会用到的关键文件的安装路径:
INSTALL pgbouncer /usr/local/bin
INSTALL etc/pgbouncer.ini /usr/local/share/doc/pgbouncer
INSTALL etc/userlist.txt /usr/local/share/doc/pgbouncer
一个比较简单的可运行配置(pgbouncer.ini)大概如下所示:
[databases]
* = host=... port=... user=... password=...
[pgbouncer]
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /usr/local/share/doc/pgbouncer/userlist.txt
pool_mode = session
server_reset_query = DISCARD ALL
max_client_conn = 100
default_pool_size = 20
log_connections = 0
log_disconnections = 0
在 databases 里配置的是数据库的信息,星号表示匹配所有数据库,其它无需多言。在 pgbouncer 里配置的信息需要着重解释,下面让我们挑重点说明一下。
我在实际使用的过程中,遇到一个案例,一并说说,具体表现:访问突发高峰,系统高负载,接着报错:「ERROR: no more connections allowed (max_client_conn)」,然后就一直缓不过来,访问都没有了也缓不过来,此时「show clients」能看到 PgBouncer 还保持着大量连接没有释放,所以我怀疑是某些 timeout 设置不当导致的,查了一下:
;; Dangerous. Server connection is closed if query does not return ;; in this time. Should be used to survive network problems, ;; _not_ as statement_timeout. (default: 0) ;query_timeout = 0
;; Dangerous. Client connection is closed if the query is not assigned ;; to a server in this time. Should be used to limit the number of queued ;; queries in case of a database or network failure. (default: 0) ;query_wait_timeout = 0
;; Dangerous. Client connection is closed if no activity in this time. ;; Should be used to survive network problems. (default: 0) ;client_idle_timeout = 0
虽然配置文件提示着 Dangerous,但是我设置了合理的 timeout 后,问题解决。为什么会这样?我们不妨设想一下:一个请求到达了连接池,然后关闭连接,但是因为某些原因,比如说网络故障或者服务器本身原因,导致连接池没有收到关闭,此时如果没有设置一个合理的超时时间的话,那么连接池将永远不会释放这个连接。
有意思的是,在新版 1.7 的 Changelog 里,我们能看到一个配置的变化:
Set query_wait_timeout to 120s by default. Current default (0) causes infinite queueing, which is not useful. That means if client has pending query and has not been assigned to server connection, the client connection will be dropped.
此外,还有一些参数也需要注意,比如 listen_backlog,表示连接池被沾满后系统还能 hold 住多少个连接,缺省 128,如果你的访问量比较大,那么加大它。