百萬併發下 Nginx 的優化之道

分享:陶輝

講師介紹:陶輝,曾在華為、騰訊公司做底層數據相關的工作,寫過一本書叫《深入理解Nginx:模塊開發與架構解析》,目前在杭州智鏈達作為聯合創始人擔任技術總監一職,目前專注於使用互聯網技術助力建築行業實現轉型升級。

今天的分享主要在Nginx的性能方面,希望能給大家帶來一些系統化思考,幫助大家更有效地去做Nginx。

1. 優化方法論

今天我的分享重點會看兩個問題:

  • 第一,保持併發連接數,怎麼樣做到內存有效使用
  • 第二,在高併發的同時保持高吞吐量的重要要點


百萬併發下 Nginx 的優化之道


實現層面主要是三方面優化,主要聚焦在應用、框架、內核。

百萬併發下 Nginx 的優化之道


硬件限制剛剛也都講過,可能有的同學也都聽過,把網卡調到萬兆、10G 或者40G 是最好的,磁盤會根據成本的預算和應用場景來選擇固態硬盤或者機械式硬盤,關注IOPS 或者 BPS。CPU 是我們重點看的一個指標。

百萬併發下 Nginx 的優化之道


這一頁重點來說下,實際上它是把操作系統的切換代價換到了進程內部,所以它從一個連接器到另外一個連接器的切換成本非常低,它性能很好,協程 Openresty 其實是一樣的。

資源的高效使用,降低內存是對我們增大併發性是有幫助的,減少RTT、提升容量。

reuseport都是圍繞著提升CPU的機核性。還有fastsocket,因為我之前在阿里雲的時候還做過阿里雲的網絡,所以它其實能夠帶來很大的性能提升,但是它問題也很明顯,把內核本身的那套東西繞過去了。

百萬併發下 Nginx 的優化之道


2. 請求的“一生”

下面我首先會去聊一下怎麼看“請求”,瞭解完這個以後再去看怎麼優化就會很清楚了。

說這個之前必須再說一下Nginx的模塊結構,像Nginx以外,任何一個外部框架都有個特點,如果想形成整個生態必須允許第三方的代碼接進來,構成一個序列,讓一個請求挨個被模塊共同處理。

那Nginx也一樣,這些模塊會串成一個序列,一個請求會被挨個的處理。在核心模塊裡有兩個,一個是steam 和 NGX。

百萬併發下 Nginx 的優化之道


2.1 請求到來

一個連接開始剛剛建立請求到來的時候會發生怎麼樣的事情,先是操作系統內核中有一個隊列,等著我們的進程去系統調用,這時候因為有很多工作進程,誰會去調用呢,這有個負載均衡策略,下面有個PPT會專門說這個事情。

現在有一個事件模塊,調用了epoll wait 這樣的接口,accept 建立好一個新連接,這時會分配到連接內存池,這個內存池不同於所有的內存池,它在連接剛創建的時候會分配,什麼時候會釋放呢?

只有這個連接關閉的時候才會去釋放。接下來就到了ngx模塊,這時候會加一個定時器,60秒,就是在建立好連接以後60秒之內沒有接到客戶端發來的就自動關閉,如果60秒過來之後會去分配內存,讀緩衝區。什麼意思呢?

現在操作系統內核已經收到這個請求了,但是我的應用程序處理不了,因為沒有給它讀到用戶態的內存裡去,所以這時候要分配內存,從連接內存池這裡分配,那要分配多大呢?會擴到1K。

百萬併發下 Nginx 的優化之道


2.2 收到請求

當收到請求以後,接收uri 和 header,分配請求內存池,這時候 request pool size是4K,大家發現是不是和剛才的有一個8倍的差距,這是因為利用態的內存是非常消耗資源。

再看為什麼會消耗資源,首先會用狀態機解去形容,所謂狀態機解就是把它當做一個序列,一個支節一個支節往下解,如果發現換行了那就是請求行解完了;

但如果這個請求特別長的時候,就會去再分配更大的,剛剛1K不夠用了,為什麼是4乘8K呢?就是因為當1K不夠了不會一次性分配 32K,而是一次性分配8K。如果 8K以後還沒有解析到剛才的標識符,就會分配第二個8K。

我之前收到的所有東西都不會釋放,只是放一個指針,指到 url 或者指到那個協議,標識它有多長就可以了。

接下來解決header,這個流程一模一樣的沒有什麼區別,這時候還會有一個不夠用的情況,當我接收完所有的header以後,會把剛剛的定時器給移除,移除後接下來做11個階段的處理,也就是說剛剛所有的外部服務器都是通過很多的模塊串成在一起處理一個請求的。

百萬併發下 Nginx 的優化之道


像剛剛兩頁PPT都在說藍色的區域,那麼請求接下來11個階段是什麼意思呢?這個黃色的、綠色的,還有右邊這個都是在11階段之中。

這11個階段大家也不用記,非常簡單,只要掌握三個關鍵詞就可以。

剛剛讀完 header 要做處理,所以這時候第一階段是 post-read。接下來會有rewrite,還有access和preaccess。

百萬併發下 Nginx 的優化之道


先看左手邊,當我們下載完 Nginx 源碼編以後會有一個referer,所有的第三方數據都會在這裡呈現有序排列,這些序列中並不是簡單的一個請求給它再給它,先是分為11個階段,每個階段之內大家是有序一個個往後來的,但在11個階段中是按階段來的。

我把它分解一下,第一個referer這階段有很多模塊,後面這是有序的。

百萬併發下 Nginx 的優化之道


這個圖比剛剛的圖多了兩個關鍵點,第一到了某一個模塊可以決定繼續向這序列後的模塊執行,也可以說直接跳到下個階段,但不能說跳多個階段。

第二是生成了向客戶端反映的響應,這時候要對響應做些處理,這裡是有序的,先做縮略圖再做壓縮,所以它是有嚴格順序的。

百萬併發下 Nginx 的優化之道


2.3 請求的反向代理

請求的反向代理,反向代理這塊是我們Nginx的重點應用場景,因為Nginx會考慮一種場景,我不知道大家有沒有用過,客戶端走的是公網,所以網絡環境非常差,網速非常慢,如果簡單用一個緩衝區從客戶端收一點發給上游服務器,那上游服務器的壓力會很大,因為上游服務器往往它的效率高,所以都是一個請求被處理完之前不會再處理下一個請求。

Nginx考慮到這個場景,它會先把整個請求全部收完以後,再向上游服務器建立連接,所以是默認第一個配置,就是proxy request buffering on,存放包體至文件,默認size是8K。那建立上游連接的時候會放timeout,60秒,添加超時定時器,也是60秒的。

發出請求(讀取文體包件),如果向上遊傳一個很大的包體的話,那sizk就是8K。默認proxy limit rate是打開的,我們會先把這個請求全部緩存到端來,所以這時候有個8×8K,如果關掉的話,也就是從上游發一點就往下游發一點。知道這個流程以後,再說這裡的話大家可以感覺到這裡的內存消耗還是蠻大的。

百萬併發下 Nginx 的優化之道


2.4 返回響應

返回響應,這裡面其實內容蠻多的,我給大家簡化一下,還是剛剛官方的那個包,這也是有順的從下往上看,如果有大量第三方模塊進來的話,數量會非常高。

百萬併發下 Nginx 的優化之道


第一個關鍵點是上面的header filter,上面是write filter,下面是postpone filter,這裡還有一個copy filter,它又分為兩類,一類是需要處理,一類是不需要處理的。openresty的指令,第一代碼是在哪裡執行的,第二個是SDK。

百萬併發下 Nginx 的優化之道


3. 應用層優化

3.1 協議

做應用層的優化我們會先看協議層有沒有什麼優化,比如說編碼方式、header每次都去傳用Nginx的架構,以至於浪費了很多的流量。我們可以改善http2,有很多這樣的協議會有大幅度提升它的性能。

當然如果你改善http2了,會帶來其他的問題,比如說http2必須走這條路線。這條路線又是一個很大的話題,它涉及到安全性和性能,是互相沖突的東西。

百萬併發下 Nginx 的優化之道


3.2 壓縮

我們希望“商”越大越好,壓縮這裡會有一個重點提出來的動態和靜態,比如說我們用了拷貝,比如說可以從磁盤中直接由內核來發網卡,但一旦做壓縮的話就不得不先把這個文件讀到Nginx,交給後面的極內核去做一下處理。

keepalive長連接也是一樣的,它也涉及到很多東西,簡單來看這也就是附用連接。因為連接有一個慢啟動的過程,一開始它的窗口是比較小,一次可能只傳送很小的1K的,但後面可能會傳送幾十K,所以你每次新建連接它都會重新開始,這是很慢的。

當然這裡還涉及到一個問題,因為Nginx內核它默認打開了一個連接空閒的時候,長連接產生的作用也會下降。

提高內存使用率

剛剛在說具體的請求處理過程中已經比較詳細的把這問題說清楚了,這裡再總結一下,在我看來有一個角度,Nginx對下游只是必須要有的這些模塊,client header、buffer size:1K,上游網絡http包頭和包體。

CPU通過緩存去取儲存上東西的時候,它是一批一批取的,每一批目前是64字節,所以默認的是8K,如果你配了32它會給你上升到64,如果你配了65會升到128,因為它是一個一個序列化重組的,所以瞭解這個東西以後自己再配的時候就不會再犯問題。紅黑樹這裡用的非常多,因為是和具體的模塊相關。

百萬併發下 Nginx 的優化之道


3.4 限速

大部分我們在做分公司流控的時候,主要在限什麼呢?主要限Nginx向客戶端發送響應的速度。這東西它非常好用,因為可以和Nginx定量連接在一起。這不是限上游發請求的速度,而是在限從上游接響應的速度。

百萬併發下 Nginx 的優化之道


3.5 Worker間負載均衡

當時我在用0.6版本的時候那時候都在默認用這個,這個“鎖”它是在用進程間同步方式去實現負載均衡,這個負載均衡怎麼實現呢?就是保證所有的Worker進程,同一時刻只有一個Worker進程在處理距離,這裡就會有好幾個問題,綠色的框代表它的吞吐量,吞吐量不高,所以會導致第二個問題requests,也是比較長的,這個方差就非常的大。

如果把這個“鎖”關掉以後,可以看到吞吐量是上升的,方差也在下降,但是它的時間在上升,為什麼會出現這樣的情況?因為會導致一個Worker可能會非常忙,它的連接數已經非常高了,但是還有其他的worker進程是很閒的。

如果用了requests,它會在內核層面上做負載均衡。這是一個專用場景,如果在複雜應用場景下開requests和不開是能看到明顯變化的。

百萬併發下 Nginx 的優化之道


3.6 超時

這裡其實我剛剛都說了好多,它是一個紅黑樹在實現的。唯一要說的也就是這裡,Nginx現在做四層的反向代理也很成熟了。

像utp協議是可以做反向代理的,但要把有問題的連接迅速踢掉的話,要遵循這個原則,一個請求對一個響應。

百萬併發下 Nginx 的優化之道


3.7 緩存

只要想提升性能必須要在緩存上下工夫。比如說我以前在阿里雲做雲盤,雲盤緩存的時候就會有個概念叫空間維度緩存,在讀一塊內容的時候可能會把這內容周邊的其他內容也讀到緩存中,大家如果熟悉優化的話也會知道有分支預測先把代碼讀到那空間中,這個用的比較少,基於時間維度用的比較多了。

百萬併發下 Nginx 的優化之道


3.8 減少磁盤IO

其實要做的事也非常多,優化讀取,Sendfile零拷貝、內存盤、SSD盤。減少寫入,AIO,磁盤是遠大於內存的,當它把你內存消化完的時候還會退化成一個調用。像thread pool只用讀文件,當退化成這種模式變多線程可以防止它的主進程被阻塞住,這時候官方的博客上說是有9倍的性能提升。

百萬併發下 Nginx 的優化之道


4. 系統優化

第一就是提升容量配置,我們建連接的時候也有,還有些向客戶端發起連接的時候會有一個端口範圍,還有一些像對於網卡設備的。

百萬併發下 Nginx 的優化之道


第二CPU緩存的親和性,這看情況了,現在用L3緩存差不多也20兆的規模,CPU緩存的親和性是一個非常大的話題,這裡就不再展開了。

百萬併發下 Nginx 的優化之道


第三,NUMA架構的CPU親和性,把內存分成兩部分,一部分是靠近這個核,一部分靠近那個核,如果訪問本核的話就會很快,靠近另一邊大概會耗費三倍的損耗。對於多核CPU的使用對性能提升很大的話就不要在意這個事情。

百萬併發下 Nginx 的優化之道


第四,網絡的快速容錯,因為TCP的連接最麻煩的是在建立連接和關閉連接,這裡有很多參數都是在調,每個地方重發,多長時間重發,重發多少次。

百萬併發下 Nginx 的優化之道


這裡給大家展示的是快啟動,有好幾個概念在裡面,第一個概念在快速啟動的時候是以兩倍的速度。因為網的帶寬是有限的,當你超出網絡帶寬的時候其中的網絡設備是會被丟包的,也就是控制量在往下降,那再恢復就會比較慢。

TCP協議優化,本來可能差不多要四個來回才能達到每次的傳輸在網絡中有幾十K,那麼提前配好的話用增大初始窗口讓它一開始就達到最大流量。

提高資源效率,這一頁東西就挺多了,比如說先從CPU看,TCP defer accept,如果有這個的話,實際上會犧牲一些即時性,但帶來的好處是第一次建立好連接沒有內容過來的時候是不會激活Nginx做切換的。

百萬併發下 Nginx 的優化之道


內存在說的時候是系統態的內存,在內存大和小的時候操作系統做了一次優化,在壓力模式和非壓力模式下為每一個連接分配的系統內存可以動態調整。

網絡設備它們的核心只解決一個問題,變單個處理為批量處理,批量處理以後吞吐量一定是會上升的,因為消耗的資源變少了,切換次數變少了,它們的邏輯是一樣的,就這些邏輯和我一直在說的邏輯都是同一個邏輯,只是應用在不同層面會產生不同的效果。

端口複用,像reals是很好用的,因為它可以把端口用在上游服務連接的層面上,沒有帶來隱患。

百萬併發下 Nginx 的優化之道


提升多CPU使用效率,上面很多東西都說到了,重點就兩個,一是CPU綁定,綁定以後緩存更有效。多隊列網卡,從硬件層面上已經能夠做到了。

百萬併發下 Nginx 的優化之道


BDP,帶寬肯定是知道的,帶寬和時延就決定了帶寬時延積,那吞吐量等於窗口或者時延。

百萬併發下 Nginx 的優化之道


內存分配速度也是我們關注的重點,當併發量很大的時候內存的分配是比較糟糕的,大家可以看到有很多它的競品。

百萬併發下 Nginx 的優化之道


PCRE的優化,這用最新的版本就好。

百萬併發下 Nginx 的優化之道


本文根據陶輝老師在 GOPS 2018 · 上海站分享整理而成。轉自高效運維社區。


分享到:


相關文章: