Nginx accept_mute 驚羣問題

 

驚羣的定義


首先,來看驚羣的定義:

The thundering herd problem occurs when a large number of processes waiting for an event are awoken when that event occurs, but only one process is able to proceed at a time. After the processes wake up, they all demand the resource and a decision must be made as to which process can continue. After the decision is made, the remaining processes are put back to sleep, only to all wake up again to request access to the resource.

This occurs repeatedly, until there are no more processes to be woken up. Because all the processes use system resources upon waking, it is more efficient if only one process was woken up at a time.

This may render the computer unusable, but it can also be used as a technique if there is no other way to decide which process should continue (for example when programming with semaphores).

簡而言之,驚羣現象就是當多個進程/線程等待同一個事件,如果這個事件發生,會喚醒所有的進程/線程,但最終只可能有一個進程/線程能對該事件進行處理,其他進程/線程會在獲取事件失敗後重新休眠。

驚羣現象非常像把食物丟進雞羣,引起所有的雞一起鬨搶食物。如果這個食物只是一粒米的話,喚醒所有的雞一起來搶的話則非常沒有必要。

驚羣通常發生在網絡服務器上。父進程首先綁定一個端口監聽socket,然後fork出多個子進程,子進程們開始循環等待處理(比如accept)這個socket。每當用戶發起一個TCP連接時,多個子進程同時被喚醒,然後其中一個子進程accept新連接成功,餘者皆失敗,重新休眠。

 

如何解決驚羣問題呢?


另:高版本的Linux中,accept不存在驚羣問題,不過epoll_wait等操作還有。

解決的驚羣的方法也很簡單,每次把進程(雞)排個序,來了新的請求(食物)只喚醒排在第一位的就好。

其實早在linux2.6,accept系統調用的驚羣問題已經被解決了:http://citi.umich.edu/projects/linux-scalability/reports/accept.html

但我們的網絡程序不會單獨阻塞在accept調用上,我們還有許多其他網絡事件要處理。在accept之前,我們依然還得處理驚羣現象。拿nginx來說,master進程監聽端口號,所有的nginx worker進程開始用epoll_wait來處理新事件(使用epoll模型),如果不加任何保護,一個新連接來臨時,會有多個worker進程在epoll_wait後被喚醒。也就是說,我們還要解決epoll的驚羣問題。

 Nginx解決驚羣問題的配置是accept_mute。開啓 accept_mutex,只有一個子進程會將監聽套接字添加到epoll中,這樣當一個新的連接來到時,就只有一個 worker 子進程會被喚醒了。Nginx在1.11.3版本以前是默認開啓accept_mutex的。

Linux4.5以後的版本中增加了EPOLLEXCLUSIVE支持以解決epoll的驚羣問題(https://github.com/torvalds/linux/commit/df0108c5da561c66c333bb46bfe3c1fc65905898),Nginx從1.11.3版本之後也增加了對EPOLLEXCLUSIVE的支持,轉而由操作系統自己解決epoll的驚羣,從此之後accept_mutex從默認的on變成了默認off。驚羣問題完美解決。

https://github.com/nginx/nginx/commit/5c2dd3913aad5c4bf7d9056e1336025c2703586b

https://github.com/alibaba/tengine/issues/768

https://nginx.org/en/CHANGES

 

總結


假設你養了一百隻小雞,現在你有一粒糧食,那麼有兩種餵食方法:

  • 你把這粒糧食直接扔到小雞中間,一百隻小雞一起上來搶,最終只有一隻小雞能得手,其它九十九隻小雞隻能鎩羽而歸。這就相當於關閉了accept_mutex。
  • 你主動抓一隻小雞過來,把這粒糧食塞到它嘴裏,其它九十九隻小雞對此渾然不知,該睡覺睡覺。這就相當於激活了accept_mutex。

可以看到此場景下,激活accept_mutex相對更好一些,讓我們修改一下問題的場景,我不再只有一粒糧食,而是一盆糧食,怎麼辦?

此時如果仍然採用主動抓小雞過來塞糧食的做法就太低效了,一盆糧食不知何年何月才能喂完,大家可以設想一下幾十只小雞排隊等着餵食時那種翹首以盼的情景。此時更好的方法是把這盆糧食直接撒到小雞中間,讓它們自己去搶,雖然這可能會造成一定程度的混亂,但是整體的效率無疑大大增強了。

簡單點說:Apache動輒就會啓動成百上千的進程,如果發生驚羣問題的話,影響相對較大;但是對Nginx而言,一般來說,worker_processes會設置成CPU個數,所以最多也就幾十個,即便發生驚羣問題的話,影響相對也較小。

Nginx缺省激活了accept_mutex,是一種保守的選擇。如果關閉了它,可能會引起一定程度的驚羣問題,表現爲上下文切換增多(sar -w)或者負載上升,但是如果你的網站訪問量比較大,爲了系統的吞吐量,我還是建議大家關閉它。

 

最後附上一張圖: 章亦春,OpenResty Inc. 創始人兼 CEO,OpenResty 開源項目創建者

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章