對象池、連接池的意義
- 2019 年 12 月 15 日
- 筆記
這次我們來講講對象池、連接池的意義,在此之前我們先了解學習一些其他的基礎知識,以便我們結合理解池的意義。
nginx與php-fpm的進程模型
nginx採用多進程模型,啟動之後的進程將包含一個master和多個worker進程。
master是worker的父進程,主要職責是用來管理worker進程的。
- 向worker進程發送信號,如通知退出
- 監控worker狀態,當worker退出後(無論正常異常),可以重新啟動新的worker。
可以實現
從容重啟:master進程在接收到信號後,會先重新加載配置,然後再啟動新進程開始接收新請求,並向所有老進程發送信號告知不再接收新請求並在處理完所有未處理完的請求後自動退出。
worker進程負責處理請求,如果是靜態文件則可以直接處理完,如果是php程序還需要調用php來處理,當php處理完成時獲取php的返回,並返回給客戶端。
採用的是異步非堵塞,當調用php的時候不會堵塞等待,會抽空處理下一個請求,當php處理完成時恢復之前的請求並返回給客戶端。
php-fpm是php-cgi的管理器,在php >= 5.3.3就已經集成在php中了。
它的出現提供了更好的php管理方式
- 可以平滑停止/啟動php進程(重載配置生效)
- 可以配置監控多個端口和使用不同的配置
php腳本的解釋器是php-cgi
php-fpm是一個管理器,管理對象是php-cgi
php-fpm實現了fastcgi協議,當php-fpm啟動時,會啟動多個cgi解釋器進程。
web服務器可以發送數據給php-fpm,php-fpm再把數據發給php-cgi處理。(跟nginx發送數據給php-fpm類似)
常駐內存下程序的對象回收
常駐內存程序是指把自己裝入內存後將控制返回給操作系統,直到運行結束、異常、用戶手動退出才會中斷運行的程序。
當程序運行時,對象和變量將會一直存在。除非在程序中釋放銷毀。
高並發下頻繁new對象的資源佔用
當我們new一個對象的時候,需要先經過這幾個步驟:類加載檢查、分配內存空間、設置類的基本信息、調用初始化構造函數。
首先我們看看構造函數這一塊,這是在代碼中按我們的需求和意願編寫的。 在這一塊中我們經常會做一些配置檢測、數據初始化、數據庫連接(網絡io)等。 接下來是分配內存空間
OS的內存分配器一般是預先向OS申請一大段內存。然後每次分配時,再將裏面的一小段標記為已分配,釋放的時候再標記成未分配。
由於是有很多程序在運行,所以分配和釋放會交替存在,得到的結果可能是 分配1段-未分配1段-分配2段-未分配2段
一個一個的未分配就是內存碎片,會佔用額外的內存,碎片不一定可以馬上被重複使用(當分配不出連續內存時,需要向OS申請更多的內存)
同時,創建和銷毀對象時,OS都需要做一些處理工作,也會產生資源佔用。
new太多對象,然後導致cpu負載上線讓全站死機的概念
若程序未產生IO(網絡請求、讀寫文件等),執行時間等於cpu的佔用時間。
頻繁地創建銷毀對象將會佔用更多cpu資源,高並發時容易導致cpu長期處於高負載運行狀態。
什麼是對象池
對象池就是一個在程序啟動的時候先創建好若干個可以重複使用的對象。
當程序其他地方需要使用該類型對象時,不再是向系統申請創建,而是向池發出請求。
池將會從池內發配出一個對象提供使用,當程序使用完畢後,需要將對象歸還給對象池做管理。
對象池服務可以減少從頭創建每個對象的系統開銷。
大並發下多個mysql連接導致mysql繁忙全站崩潰
<?php function db(){ return mysqli_connect("localhost","root","root"); } for ($i=0; $i < 10000; $i++) { $name = "db{$i}"; $$name = db(); }
這一個demo將會產生報錯:Warning: mysqli_connect(): (08004/1040): Too many connections
我們習慣性地在PHP腳本中不會主動關閉mysql連接,而是等到腳本運行完畢之後再由gc自動回收。在這個期間將會繼續佔用連接資源,而連接資源的數量又是有限制的,所以會更快出現連接不夠用的情況。
處理會影響程序的運行,同時還將可能導致全站崩潰。
- mysql是一個連接創建一個線程處理。
- 創建銷毀mysql線程需要的內存等性能消耗、線程緩存命中率下降
- mysql底層幾乎在同時需要處理幾百個線程提交的查詢請求,而cpu一次只能處理一條指令,並且數據庫查詢需要產生IO,在IO期間cpu將會切換上下文處理其他的請求,當cpu頻繁切換上下文,性能抖動,發生性能下降甚至宕機的情況。
連接池 保護mysql不崩潰
連接池是將已經創建好的連接保存在池中,當有請求來時,直接使用已經創建好的連接對數據庫進行訪問。
<?php class Pool{ private $pool = []; private $min = 5; private $max = 100; private $now; public function __construct() { // 在池創建的時候就先創建好一些連接 for ($i = 0 ; $i < $this->min; $i++){ $this->pool[] = mysqli_connect("localhost","root","root"); $this->now++; } } public function get() { // 這裡要判斷當前池還有沒有空閑的 // 若沒有,則判斷當前已經提供的服務數量大不大於最大數量 如果還沒有達到最大數量 可以向系統再申請一個資源到池中 // 如果已經達到最大數量,並且池內沒有服務了,則進行短暫等等看看有沒有 // 需要銷毀避免同一個連接多處使用,會衝突 $connect = array_shift($this->pool); return $connect;//偽代碼 } public function recovery($connect) { $this->pool[] = $connect; } }
因為連接池需要長期保持在線,在傳統的php腳本中不支持,在
swoole中可以常駐內存運行,即可使用連接池
這樣省略了創建連接和銷毀連接的過程。這樣性能上得到了提高。
然而除了性能上的提高外,還有一個意義也很重要:保護服務穩定運行,不發生全站崩潰。
在上面一點我們已經提到,更多的鏈接將會導致cpu頻繁切換上下文,性能抖動,嚴重情況時將會全站崩潰。
假設本來我們的服務器配置是可以保證1000個連接同時穩定運行,突然某一時刻有3000個人並發,導致連接不夠用,那麼是保證原有1000人都正常運行好,還是讓這3000人爭搶資源最終導致機器響應不了全站崩潰好呢?
連接池的意義此時才得以體現,我們設置連接池的最大數量為機器能承受並且穩定運行的最大數量。
當已經有這麼多的數量在服務的時候,後面的請求申請連接資源時需要進行短暫的等待,若時間到了還是沒有空餘連接提供,則需要熔斷服務,返回給客戶端失敗。
這樣子可以保證機器長期穩定服務。若是越來越多的客戶端申請不到資源,則需要提高機器配置。(因為我們的連接池最大數量已經是機器的瓶頸,只能通過硬件配置來提升能服務的數量)
nginx – php fpm在大並發下504
在最開始的時候已經介紹過nginx和php的運行進程模型,php-fpm就是一個池管理器,內部裝了若干個php-cgi程序,當nginx申請解析php腳本時,php-fpm則分配一個php-cgi出去處理,處理完則收回管理。
在高並發下,nginx會產生504錯誤,這就是我們上面介紹到的,客戶端進行了短暫的 等待 後,仍然申請不到資源,則只能告訴客戶端失敗。
(在京東、淘寶的大活動期間很有機會碰到504錯誤哦! 這種情況下我們一般只需要刷新頁面即可。 因為再刷新時大幾率已經有連接資源空閑了!)
- Nginx 504 Gateway Time-out的含義是沒有請求到可以執行的PHP-CGI。
總結
連接池、對象池的意義不僅僅是可以減少頻繁創建銷毀對象連接的性能開銷
更大的意義是可以保證應有服務客戶端的穩定運行。