最近在nginx1.9.1中支持了reuse_port這個功能 是准許多個socket監聽同一個埠?
最近在nginx1.9.1中支持了reuse_port這個功能 ,是准許多個socket監聽同一個埠的選項。(在很多情況下就是允許多個進程/線程監聽同一埠)。
但這個對於nginx這樣的單master多worker的伺服器來說,已經通過master的fork調用,讓多個worker都監聽了同一個埠,這樣的話,這個選項還有意義么?或者說它在哪些情況下,對nginx有意義呢?
這個我剛好知道,這是Linux內核新支持得一個TCP選項,這樣不需要fork也可以在不同進程中監聽同個埠,對nginx來說有什麼用我就不清楚了,也的確想不出什麼實際的例子……對其他應用來說,可以不需要實現fork-server就能支持多進程模式了。
能想到的就是在需要服務重啟的時候,可以先把新的實例啟動起來,再退出舊實例,保證服務不中斷?但是這個以前也只要通過某種方式通知舊的實例停止listen就行了。
我來回答一下自己的問題,拋磚引玉吧
先描述下Nginx的網路模型
1 nginx的master進程創建一系列的監聽套接字(比如需要監聽不同的埠,80,443等),下面都以監聽80埠為例來說明。
2 fork出多個worker進程,這些worker進程繼承了監聽套接字。
3 而這種多進程監聽同一埠的模型會有驚群現象(先不討論內核是否解決了驚群,nginx作為一個跨平台的server,從自身解決了驚群),即監聽套接字上有請求到來時,內核會喚醒所有的進程。
4 在linux平台下採用epoll網路事件驅動,每個worker創建自己的epoll。
5 Nginx的驚群解決手段: 如果有多個worker進程,且開啟了accept_mutux 鎖(默認開啟)
這時候每個worker不會將監聽套接字加入到自己的epoll中,而是會去搶一把自旋鎖,即對監聽套接字「權力」,搶到的worker進程會將監聽套接字加入自己的epoll中,accept新請求,然後釋放鎖。
所以,如果沒有這種強鎖機制,每個worker的epoll中都會監視監聽套接字,這樣每次請求到來時,每個worker都會被喚醒,而最終accept這個請求的只能有一個,其它的worker的喚醒是浪費的。
應該說在低並發情況下,這種處理機制會很好地提升cpu效率。
現在很多實例證明:在並發很高的情況下,nginx這種處理驚群的機制會導致處理效率的下降,所以現在很多建議是關閉accept_mutux鎖,這樣每個worker中的epoll中都會監視監聽套接字。
幸運的是,從nginx的高版本開始,這個鎖默認是已經關閉的了。
為什麼在高並發的情況下,關閉鎖導致使驚群現象產生,也會提升性能呢?網上有個很形象的例子,這裡我借用下:
試想,有一群雞(是真正的雞),你撒穀粒給這群雞吃。
a,一粒粒撒的時候,如果不加處理,每個雞都會跳起來,但最終只有一隻雞能夠吃到這粒米,
所以在一粒粒撒的時候,需要有鎖,不能讓每個雞都跳起來,這樣浪費它們的精力,必須要讓它們遵守秩序,一個個來(加鎖)
b,然而,如果你撒了一大把穀粒,這時候還讓它們一個個來,豈不是很不合理,所以,在撒大把穀粒的情況下,這些雞全部跳起來搶食才是科學的,這樣才能更加快速地消耗掉這些穀粒。(不加鎖)。
上面的例子雖然有些粗糙,但是很形象。
6 回到我們的話題SO_REUSEPORT這個選項,官方在nginx.19.1中支持這個,且經過測試,開啟這個選項,會使得的nginx的性能提升3倍。具體測試可見:Socket Sharding in NGINX OSS Release 1.9.1
他們比較了三種情況,常規情況(開啟鎖),關閉鎖,和開啟SO_REUSEPORT。
對於關閉鎖比開啟鎖提升性能,這個好理解。
7 下面解釋開啟reuseport後,nginx的表現是如何的:
先解釋下reuseport的意思:內核支持同一個埠可以有多個socket同時進行監聽而不報錯誤。
nginx是這樣利用這個特徵的:master在啟動階段,根據配置的worker的數據n,針對同一個listen埠,比如80,創建了n個socket,然後同時bind到80埠。
在進行fork worker時,此時的場景如下所示:
然後,每個worker會根據自己的id號,將和自己id相對應的sockfd加入自己的epoll中:如下:
這樣就達到了每個worker都有「自己「的socketfd在epoll中,而不用和其他worker進行」共享「而造成驚群。
其實,在reuseport場景下,相當於把負載均衡的步驟下放到內核來做了:
內核會均衡地把同一個埠上的連接,均衡地分發到監聽這個埠的socket上。
至此,reuseport分析完畢。
部署 reuse port 主要好處是防負載不均衡。對於多線程 accept() 的方法,協議棧/調度器中的一些 gotcha 導致不同 CPU 能 accept 到的連接分布不良,可能出現幾個 CPU 利用率撐死幾個 CPU 餓死的情況,吃不滿單機的負載上限。能繞開協議棧上下文中共享到的鎖的話,負載均衡就能好多了。
The problem with this technique, as Tom pointed out, is that when multiple threads are waiting in the accept() call, wake-ups are not fair, so that, under high load, incoming connections may be distributed across threads in a very unbalanced fashion. At Google, they have seen a factor-of-three difference between the thread accepting the most connections and the thread accepting the fewest connections; that sort of imbalance can lead to underutilization of CPU cores. By contrast, the SO_REUSEPORT implementation distributes connections evenly across all of the threads (or processes) that are blocked in accept() on the same port.
The SO_REUSEPORT socket option
與網卡多隊列一起用 英特爾有些網卡一個插口支持十六個隊列 綁定到十六個核 十六個work進程也綁定核 不同核之間沒有競爭 性能好很多
多線程處理同一埠而不會產生太嚴重競爭 可參見谷歌 how to receive 100w packets per second(傳送門待會兒補)
nginx主要用在服務重啟避免報埠已被佔用錯誤。之前重啟時在某些有連接還沒釋放的情況,這樣新啟動的進城會報錯,埠被佔用,所以在Linux內核支持這個選擇項後,nginx就在新版支持了。
PS:跟多個worker同時監控一個埠關係不大。
推薦閱讀:
※在 TCP/IP 協議中,客戶端發出請求,服務端回復響應,客戶端在數據鏈路層會校驗目的 MAC 地址嗎???
※為什麼http下載不是直接下載而是一點一點地加快速度?如果直接下載會有什麼後果?
※既然 BBR 想要解決的是 bufferbloat 問題,那麼為什麼路由器 buffer 要做那麼多?
※TCP連接中a連b和b連a是一碼事嗎?
※QQ 為什麼以 UDP 協議為主,以 TCP 協議為輔?