php fpm 進程數和並發數是什麼關係?
比如一個進程里有sleep1000是不是多刷新幾次整個php fpm進程數會不斷增加,直到達到最大,服務卡住,可以像nginx那樣處理嗎,幾個進程,然後高並發。
我的實驗結果是進程數一直在加直到跑滿,感覺這種模型很low
多進程同步阻塞的架構是經典的UNIX編程模型,何來low與不low之說?
包括現在的資料庫,PostgreSQL和MySQL,還在用這套模型,一個進程/線程處理一個資料庫連接.
Apache的prefork MPM和PHP的PHP-FPM,也是這種模型.
這種模型最大的優勢就是,穩定可靠,編程簡單.
明顯的缺點則是,處理大規模並發的長連接時不給力.
比如MySQL默認的最大連接數max_connections是151,
也就是MySQL默認最大只能開151個線程來維持151個資料庫連接.
因為磁碟的性能(IOPS)擺在那,就是低,非同步也不能改變這個現狀.
雖然MySQL的磁碟操作也用了Linux的AIO,但要實現MySQL穩定的最大化吞吐量,還得靠排隊.
淘寶AliSQL的測試,八條記錄更新:
Threads MySQL AliSQL
8 16737 17298
16 20538 24145
32 20349 24074
64 20139 23824
128 19030 23876
256 16376 23984
512 8981 23973
可見MySQL在16和32線程(跟CPU核心數或超線程數一樣)時表現是最好的.
正是因為AliSQL引入了排隊的機制,所以在線程數更多時,仍能保持穩定的吞吐量.
非同步和協程不是放之四海而皆準的銀彈.
非同步能解決IO阻塞的問題,提高並發,但回調的編程方式不利於複雜邏輯下的代碼維護.
協程也能提高並發,但協程的調度需要消耗CPU,協程切換時的上下文保存需要消耗內存,同步代碼寫非同步也是有代價,而且不完美的.比如Golang在協程goroutine之間同步數據(全局變數)需要使用channel甚至lock,而在PHP-FPM進程里讀寫PHP全局變數,根本不需要考慮同步和加鎖的問題.
同步程序裡面也是可以加入一些非同步功能來避免某些IO阻塞從而提高並發.
比如PHP里的Swoole就是這麼一種實現.
在Swoole里,你依然可以用PHP中同步的編程方式.
但在某些產生阻塞操作的上,你需要換成Swoole提供的東西.
比如:
https://wiki.swoole.com/wiki/page/p-async.html
PHP中用file_get_contents/file_put_contents讀寫文件系統會發生阻塞,這時你可以考慮換成Swoole內置的非同步文件讀寫客戶端.
PHP中用curl發起HTTP請求會發生阻塞,這時你可以考慮換成Swoole內置的非同步HTTP客戶端.
PHP中用phpredis發起的subscribe訂閱操作會阻塞,這時你可以考慮換成Swoole內置的非同步Redis客戶端.
PHP中用PDO/MySQLi發起的查詢操作會阻塞,這時你可以考慮換成Swoole內置的非同步MySQL客戶端,還能配上資料庫連接池.
PHP中用sleep配合while(1)能實現定時器,但會產生阻塞,這時你可以考慮換成Swoole內置的非同步定時器(swoole_timer_tick/swoole_timer_after).
注意:上面這些Swoole提供的非同步編程API,需要跑在Swoole Server,而不能跑在PHP-FPM里.
Swoole內置的HttpServer就是一個類似於Nginx的非同步處理HTTP連接的伺服器,你可以認為用了Swoole的HttpServer,就相當於給PHP裝上了一台Nginx發動機,輕鬆實現C10K並發完全不是問題.
@許懷遠
事件驅動的程序,可以去看看Nginx或者OpenResty(Nginx+Lua).
它們開的進程數就是CPU核心數,並且支持CPU親和性綁定,根本沒有線程池調度的問題.
請求到進程的分配,利用了Linux的reuseport機制解決驚群問題.
事件驅動EventLoop的實現,則利用了Linux的epoll機制.
至於用線程池模擬非同步IO,大多指的是用來模擬文件讀寫時的非同步IO,而不是網路IO.
比如Swoole的非同步文件系統IO實現就有2種方式,一種是Linux AIO,一種是線程池模擬.
Swoole也應用了Linux的reuseport機制,大幅提升短連接服務的性能.
Swoole也支持CPU親和性綁定(open_cpu_affinity),能將reactor線程/worker進程綁定到固定的一個核上,從而避免進程/線程運行時在多個核之間互相切換,提高CPU cache的命中率.
Swoole里的reactor,默認模式下雖然不是進程,但默認也是開啟CPU核心個數的線程數.所以要說調度那也只是操作系統對那幾個reactor線程調度,就像操作系統對SMP模式下的Erlang里的調度器(每個調度器都映射到一個操作系統線程)進行調度.
Erlang的調度器是搶佔式的,而Go的協程調度器不是完全搶佔式的.所以Go里一旦某個goroutine獨佔CPU核心,那其他goroutine就有可能餓死.Swoole中為了避免worker進程不獨佔CPU,引入了AsyncTask進程池,將需要耗時執行的邏輯放到task進程池裡面,執行完後非同步返回給worker進程.
另外,Go跟Java一樣,都是單進程多線程架構,都有全局GC,所以程序會發生周期性卡頓(stop-the-world).而Swoole里的reactor是C實現的,沒有GC的問題,而執行PHP代碼邏輯的是worker進程,是多進程架構,其中一個進程GC不會影響到其他進程,也沒有全局GC的問題.
而且epoll性能也不是一定就比傳統的select高,連接數不多(百級別的並發)並且連接都很活躍(如HTTP短連接,不停地請求/響應/結束)時,select性能的表現就不比epoll差.比如用PHP內置的單進程HTTP伺服器,ab並發100,進行Hello World測試,RPS也能過萬:
https://my.oschina.net/eechen/blog/369470
select的劣勢在於不適合維持萬級別的不活躍的長連接的場景,這種場景則是epoll的優勢,因為epoll是Linux實現的事件驅動的循環,突然間某個長連接有消息(活躍),epoll不用像select/poll那樣循環檢測全部連接就能定位到這個連接來處理.
是的,fpm就是這樣lowb的,不僅fpm,絕大部分Python和JAVA也是這麼做的,Ruby也是,.net也是。常見語言里,只有nodejs和Go默認就是1:N模型,每個線程能同時處理多個並發請求,其它都是跟PHP一樣的1:1模型,一個sleep1個小時的請求,就會霸佔一個線程一個小時。
大部分場景,這種模型並無不妥,少數極端情況,可以另闢蹊徑,換天生支持1:N的語言,或者用支持1:N的非同步庫。底層框架層面,非同步回調問題不大,業務邏輯中,回調會把你弄死,協程是應用層面高並發的最佳實踐,100萬個協程sleep也是吃得消的。
@eechen
nginx官網就有thread pool的介紹啊,Boosting NGINX Performance 9x with Thread Pools,為什麼要引入這個thrad pool呢?就是因為有些操作會block掉event loop。如果只是像nginx一樣光做點轉發不帶業務邏輯,block時間很短,不用線程池也沒多大影響。但是有業務邏輯了呢?所以swoole也引入了進程池啊,可以配置worker_num/task_worker_num的大小。
goroutine的調度的確是cooperative的,所以CPU密集型計算時,需要手動添加幾個runtime.Gosched()以讓出幾個時間片。然而遇到100%佔用CPU的task,扔到AsyncTask里就不會佔用一個CPU核心了嗎?定時殺掉進程嗎?
本身Go和JVM都已經是concurrent GC了,內存不吃緊的話,很少會有Full GC發生,周期性卡頓,那是Go 1.5之前和Java 1.7之前才比較常見的事情,隨便舉個例子,一個運行了18個小時的tomcat進程,內存上限設置為4GB,線程池裡有1000個線程,處理了將近1個億的HTTP請求,GC花了多少時間呢?
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
94.41 0.00 44.32 63.05 87.97 82.61 10660 231.216 6 3.301 234.517
94.41 0.00 92.71 63.05 87.97 82.61 10660 231.216 6 3.301 234.517
0.00 91.75 30.45 63.05 87.97 82.61 10661 231.235 6 3.301 234.536
0.00 91.75 50.10 63.05 87.97 82.61 10661 231.235 6 3.301 234.536
0.00 91.75 68.79 63.05 87.97 82.61 10661 231.235 6 3.301 234.536
0.00 91.75 87.28 63.05 87.97 82.61 10661 231.235 6 3.301 234.536
95.44 0.00 6.95 63.06 87.97 82.61 10662 231.257 6 3.301 234.558
18個小時只不過發生了6次Full GC,總共卡頓了3秒多(萬分之零點幾),不卡頓的GC也不過花了231秒(不到千分之5)多,平均每6秒一次。這還是沒做任何tuning的默認表現,因為實在沒有調優的必要,如果調一下,也僅僅是賬面再好看一些而已,對服務質量不會有任何影響。
Go 1.5之後,在low latency方面的進步非常大,以前動輒幾百毫秒的卡頓已經沒有了。不論是JVM還是Go,周期性的STW都是老黃曆了,如果負載大到集群里的JVM和Go要深度優化,早就不是PHP能做的場景了,要換也是換C++或者Rust。
@eechen Hello World這樣的mirco benchmark毫無意義,大部分邏輯都用在HTTP協議了。如果邏輯那麼簡單,直接做幾個nginx的模塊好了,還用的著高級語言么?
多好的架構,設計簡單易於實現,樓主sleep是故意破壞,你當企業里每個人都沒事閑的sleep玩么,雖然我是java碼農。但你這麼詆毀php那是不行的。。。。。。
推薦閱讀:
※驅動程序是不是進程?
※我一個朋友竟然告訴我計算機處理多進程是排隊處理,計算機無法同時處理多個進程。我應該怎麼說服他?
※線程和進程的區別是什麼?
※操作系統的系統開銷比率怎麼計算?
※為什麼要了解進程與線程的區別,了解二者的區別有什麼意義呢?