PgBouncer in action
原本我是 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 里配置的信息需要著重解釋,下面讓我們挑重點說明一下。
- listen_addr 和 listen_port:監聽的地址和端口,listen_addr 如果設置為星號表示監聽所有地址,不過我強烈建議你不要這樣做,因為服務一旦暴露在外網之上,便再無安全可言,相對安全的做法是只監聽本地或內網地址。
- auth_type 和 auth_file:認證的類型及文件,通常 auth_type 使用 md5 的方式,至于 auth_file 可以通過源代碼里提供的 mkauth.py 腳本來生成。
- pool_mode 和 server_reset_query:連接池有三種模式,當設置為 session 模式的時候,在會話結束的時候連接會放回連接池;當設置為 transaction 的時候,在結束一個事務的時候連接會放回連接池;當設置為 statement 的時候,在每條查詢完成的時候連接會放回連接池。可見 session 的方式最安全,但是連接的利用率相對比較低;statement 的方式最激進,但是連接的利用率相對比較高;至于 transaction 則介于二者之間,當然具體細節還有一些詫異,如果你不確定就用 session 模式。當使用 session 模式的時候,記得配置 server_reset_query 初始化連接環境。
- max_client_conn 和 default_pool_size:它們配置的都是連接數,max_client_conn 配置的是全局的連接數,default_pool_size 配置的是單個池的連接數。
- log_connections 和 log_disconnections:缺省配置下,系統會記錄每一次連接和關閉日志,對一個繁忙的服務器來說,干擾太大了,所以建議關閉。
我在實際使用的過程中,遇到一個案例,一并說說,具體表現:訪問突發高峰,系統高負載,接著報錯:「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,如果你的訪問量比較大,那么加大它。