問題發生環境:
- Nginx
- PHP 5.3.10 as php-fpm extension to nginx
- mongodb-php-driver 1.2.12
- MongoDB 2.2
此問題是 MongoDB PHP Driver 1.2.x 的官方特性致使的,描述請看
PHP-202 和
PHP-347 。簡單地說,PHP-FPM模式下,每個 PHP Worker 進程都有本身獨立的 mongodb 鏈接池,從而致使鏈接數極易超標,內存數也隨之倍增。
一,PHP服務背景:
某個 Web 應用是經過
Nginx+factcgi
運行的 PHP 程序提供服務的。
PHP-FPM的最大子進程數,是經過 php-fpm.conf 的
max_children
參數設置的(或pm=dynamic時由 spare_servers+start_servers 參數綜合決定)。這個值曾被設置爲
512。
二,MongoDB服務背景:
mongodb 實例的最大鏈接數限制能夠經過啓動參數中的
maxConns
設置:
- maxConns:默認值取決於系統的限制(如 ulimit 和 file descriptor)。若是沒設置這個參數, mongodb 本身不會限制鏈接數。但,你不能設置超過 20,000 。
通常不刻意設置 maxConns 參數。
三,MongoDB PHP Driver 的可怕鏈接池特性(BUG?)
MongoDB 官方提供的
mongodb-php-driver
在 1.3.0 如下版本(1.2.0~1.2.1x),擁有一個可怕的鏈接池實現方案,在執行任何查詢時,都會從鏈接池中請求一個鏈接,完成以後再歸還給鏈接池。這裏的完成是指持有該鏈接的變量離開了它的做用域。
PHP-FPM模式下,一個 PHP Web 應用能對 MongoDB instance 創建的併發鏈接數計算方式以下:
- 進程數:max-children = 512 ,那麼是 512 個進程;
- 一個MongoDB實例對應一個鏈接池:主站配置了165和166兩個副本集實例;
- 鏈接池中的鏈接數:mongodb-php-driver 對此不作任何限制,能夠無限增長直到句柄耗盡爲止。
根據 mongodb 官方文檔說明,
雖然鏈接數無限增加理論上是有可能的,但實際觀測發現,一個 Web Server 與一個 mongodb 實例的鏈接數一般會穩定在一個值上,不會有太大的起伏。
那麼,假設
一個 PHP Web 應用向 mongodb-165 發起的鏈接數爲:
750 個,
該 MongoDB 實例爲此須要維護的內存數至少爲:
750 × 默認10MB =
7.5 GB
五,解決辦法
迅速升級到 mongodb-php driver 1.3.2。
參考文檔:
1)2012-12-9,Connection Handling with the MongoDB PHP driver,英文稿,中文翻譯稿;
2)李丹的測試結果:
「再測試一下驅動升級到1.3.2穩定版後的ab結果,發現close效果很明顯,很快的鏈接數就降低到測試以前的數量了。雖然在峯值上大於老的驅動,可是應該能夠解決現有線上的高鏈接持續的問題。」
4)mongodb-java-driver 定義了一個應用與 mongodb 實例能創建的最大鏈接數,即 (connectionsPerHost × threadsAllowedToBlockForConnectionMultiplier)個鏈接:
- mongo.options.connectionsPerHost:每一個Application與 MongoDB 實例能創建的最大物理鏈接數,默認是10;
- mongo.options.threadsAllowedToBlockForConnectionMultiplier:能夠等待池中有鏈接可用的最大線程數,默認是5。
5)crazyshell,2012,MongoDB maxConns參數;