第三方文檔
節(jié)選自上文鏈接的思路
- Ngnix+PHP-FPM的工作方式包个,似乎是最節(jié)省系統(tǒng)資源的web系統(tǒng)工作方式。
- 當然冤留,具體的技術(shù)選型更多的應(yīng)該參考自己想買的業(yè)務(wù)需求碧囊。
一 原因:越來越多的并發(fā)連接數(shù)
- 頁面的元素增多,交互復雜
- 主流瀏覽器的連接數(shù)在增加
二 輔助方案:通過WEB前端優(yōu)化纤怒,降低服務(wù)器的壓力
- 減少web請求
- 減輕web請求
- 合并頁面請求
三 推薦方案:節(jié)約web服務(wù)器的內(nèi)存
- prefork MPM(Apache服務(wù)器多道處理器模塊),多進程工作模式:
- 主進程生成后糯而,他先完成初始化的工作,通過fork的方式預(yù)先產(chǎn)生一批子進程(子進程復制父進程的內(nèi)存空間泊窘,不需要再做初始化的工作)
- 多進程的好處:進程之間的內(nèi)存數(shù)據(jù)是互不干擾的熄驼。
- 優(yōu)點:成熟穩(wěn)定,兼容新老版本州既,不用擔心線程安全的問題
- 缺點:不適合高并發(fā)的業(yè)務(wù)場景谜洽,一個服務(wù)進程占很多內(nèi)存
- worker MPM, 多線程和多進程的混合模式
- 和prework一樣,也預(yù)先fork了幾個子進程(數(shù)量少)吴叶,然后每個子進程創(chuàng)建幾個線程(包含一個監(jiān)聽線程)阐虚。每個請求過來,會交給一個線程來服務(wù)蚌卤。(線程比進程輕量級实束,所以內(nèi)存占用更少)。
- 注意:并沒有解決“keep-alive”長連接的問題逊彭,只是把對象編程了更輕量級的線程
- 疑問:既然多線程輕量級咸灿,為什么不完全采用多線程的方式呢?因為多進程能確保程序的穩(wěn)定性侮叮,如果采用單進程多線程的方式避矢,有一個線程掛了,那么該進程下的其他線程也掛了,會導致全軍覆沒审胸。
- 優(yōu)點:更優(yōu)秀的內(nèi)存管理亥宿,高并發(fā)場景下表現(xiàn)更優(yōu)秀。
- 缺點:需要考慮線程安全問題砂沛,需要引入鎖烫扼,加大cpu的消耗。
- event MPM
- 和worker的方式很像碍庵,最大的差別在于解決了“keep-alive”場景下映企,長期被占用的線程資源問題。
- 注意:event MPM遇到不兼容的模塊時會失效静浴,將回退到worker模式堰氓,一個工作線程處理一個請求。
- Apache的3種模式中马绝,event MPM是最節(jié)約內(nèi)存的豆赏。(需要Linux系統(tǒng)對Epoll的支持才能啟用)
- 使用輕量級的Ngnix作為web服務(wù)器
- Ngnix本身就是一個輕量級的web服務(wù)器,天生蘿莉富稻,比Apache要輕量掷邦。
- Ngnix通過一個進程來服務(wù)N個鏈接,采用的方式不同于Apache的方式椭赋。
- sendfile節(jié)約內(nèi)存(這個概念非常重要)
- sendfile可以減少數(shù)據(jù)到“用戶態(tài)內(nèi)存空間”(用戶緩存區(qū))的拷貝抚岗,進而減少對內(nèi)存的占用。
- 為了更好的理解上面所說的原理哪怔。筆者先引入下面的概念:內(nèi)核態(tài)和用戶態(tài)的區(qū)別宣蔚,內(nèi)核態(tài)的優(yōu)先級高Ring0,而用戶態(tài)(運行態(tài))的優(yōu)先級低Ring3;并且當執(zhí)行用戶程序是突然中斷认境,運行狀態(tài)會從“用戶態(tài)”切換到“內(nèi)核態(tài)”胚委。
- 一般情況下,用戶態(tài)(程序所在的內(nèi)存空間)不能直接操作(讀寫等)各種設(shè)備(磁盤叉信,網(wǎng)絡(luò)亩冬,終端等)的。需要使用內(nèi)核作為中間人來完成對設(shè)備的操作硼身。
- 來吃個栗子(例子)吧:以最簡單的磁盤讀寫為例硅急,從磁盤A讀取文件到磁盤B,其過程是這樣的:A文件數(shù)據(jù)從磁盤開始,然后載入到“內(nèi)核緩沖區(qū)”佳遂,然后拷貝到“用戶緩存區(qū)”营袜,這完成了讀操作。寫操作也是一樣的丑罪,從“用戶緩存區(qū)”拷貝到“內(nèi)核緩沖區(qū)”荚板,最后寫入到磁盤B中凤壁。
- 這樣讀寫文件很累吧。有大神提出了要刪繁就簡啸驯,取消“用戶緩存區(qū)”那部分拷貝工作客扎,引入了MMP(Memory-Mapping,內(nèi)存映射)的概念罚斗。實現(xiàn)原理是這樣的:建立一個磁盤空間和內(nèi)存的直接映射,數(shù)據(jù)不再拷貝到“用戶緩存區(qū)”宅楞,而是返回一個指向內(nèi)存空間的指針针姿。這樣我們之前的文件拷貝就變成了如下步驟:A磁盤中文件將數(shù)據(jù)載入到“內(nèi)核緩沖區(qū)”,B磁盤從“內(nèi)核緩沖區(qū)”拷貝寫入文件厌衙。減少了一次拷貝過程距淫,減少了內(nèi)存的占用。
- 回到正題:簡單來說婶希,sendfile的原理和mmp的方式類似榕暇,核心也是減少了“內(nèi)核緩沖區(qū)”到“用戶緩沖區(qū)”的拷貝。
- 優(yōu)點: 不僅節(jié)省了內(nèi)存喻杈,還節(jié)省了CPU的開銷彤枢。
四 節(jié)約web服務(wù)器的CPU
- 對于web服務(wù)器而言,CPU是另一個非常核心的系統(tǒng)資源筒饰。就web服務(wù)器而言缴啡,除了業(yè)務(wù)程序消耗CPU外,多線程/多進程的上下文切換瓷们,也是比較消耗CPU資源的业栅。
- 一個進程/線程無法長時間占用CPU,當發(fā)生阻塞或者時間片用完時谬晕,就無法繼續(xù)占用CPU碘裕,這時會發(fā)生時間上下文的切換,即老的時間片切換到新的時間片攒钳,也是耗CPU的帮孔。
- 在并發(fā)連接數(shù)目很高的情況下,去輪詢檢測用戶建立的連接狀態(tài)(socket文件描述符)夕玩,也是消耗CPU的你弦。
- 筆者在這里只介紹一下終極問題及解決辦法:
多線程下的鎖對CPU的開銷
- Apache中的worker和event模式,都有采用多線程燎孟。多線程因為共享父進程的內(nèi)存空間禽作,在訪問共享數(shù)據(jù)的時候,就會產(chǎn)生競爭揩页,也就是線程安全問題旷偿。因此通常會引入鎖(Linux下比較常用的線程相關(guān)的鎖有互斥量metux,讀寫鎖rwlock等),成功獲取鎖的線程可以繼續(xù)執(zhí)行萍程,獲取失敗的通常選擇阻塞等待幢妄。引入鎖的機制,程序的復雜度往往增加不少茫负,同時還有線程“死鎖”或者“餓死”的風險(多進程在訪問進程間共享資源的時候蕉鸳,也有同樣的問題)。
- 死鎖現(xiàn)象(兩個線程彼此鎖住對方想要獲取的資源忍法,相互阻塞等待潮尝,永遠無法達不到滿足條件)
- 餓死現(xiàn)象(某個線程,一直獲取不到它想要鎖資源饿序,永遠無法執(zhí)行下一步)
- 為了避免這些鎖導致的問題勉失,就不得不加大程序的復雜度,解決方案一般有:
- 對資源的加鎖原探,根據(jù)約定好的順序乱凿,大家都先對共享資源X加鎖,加鎖成功之后才能加鎖共享資源Y咽弦。
- 如果線程占有資源X徒蟆,卻加鎖資源Y失敗,則放棄加鎖离唬,同時也釋放掉之前占有的資源X后专。
- 在使用PHP的時候,在Apache的worker和event模式下输莺,也必須兼容線程安全戚哎。通常,新版本的PHP官方庫是沒有線程安全方面的問題嫂用,需要關(guān)注的是第三方擴展型凳。PHP實現(xiàn)線程安全,不是通過鎖的方式實現(xiàn)的嘱函。而是為每個線程獨立申請一份全局變量的副本甘畅,相當于線程的私人內(nèi)存空間,但是這樣做相對多消耗一些內(nèi)存往弓。這樣的好處:不需要引入復雜的鎖機制實現(xiàn)疏唾,也避免了鎖機制對CPU的開銷。
這里順便提到一下函似,經(jīng)常和Nginx搭配工作的PHP-FPM(FastCGI)使用的是多進程槐脏,因此不會有線程安全的問題。
筆者:王中陽
微信(QQ):425772719
公司:北京麥芽田網(wǎng)絡(luò)科技有限公司
郵箱:wangzhong.yang@foxmail.com
本篇文章整理自==徐漢彬==老師的《高并發(fā)Web服務(wù)的演變——節(jié)約系統(tǒng)內(nèi)存和CPU》
本篇文稿初步整理,如有不妥之處敬請諒解
歡迎在下方評論處交流討論撇寞,歡迎糾正問題
一個敲代碼顿天,愛分享的人堂氯,我在這里!
來玩啊