原本我是 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,如果你的通路量比較大,那麼加大它。