當前位置:
首頁 > 知識 > epoll事件驅動框架使用注意事項

epoll事件驅動框架使用注意事項

自己一直訂閱雲風大哥的blog,今天看到期博文《 epoll 的一個設計問題 》,再追蹤其連接看下去,著實讓自己驚出一陣冷汗。真可謂不知者無畏,epoll在多線程、多進程環境下想要用好,需要避過的坑點還是挺多的。

這篇博文主要是根據Marek的博客內容進行翻譯整理的。

epoll的坑點主要是其最初設計和實現的時候,沒有對多線程、多進程這種scale-up和load-balance問題進行考慮,所以隨著互聯網並發和流量越來越大,越來越多的epoll flag和kernel flag被引入來修補相關問題;而來epoll的用戶態空間操作介面是file descriptor,內核態管理介面是file descripton,有些情況下兩者不是對應關係,會導致程序的行為很奇怪。

一、多線程環境scale-up和load-balance1.1 多線程accept

在比如HTTP/1.0的類似應用中,TCP都採用短連接的方式工作,那麼accept()工作將會很重甚至成為整個系統的瓶頸所在,為了能夠利用多核的並行處理,通常需要在多個執行單元(此處考慮多線程)上面並行運行accept()服務。但是:

(1)簡單的電平觸發模式

這是最簡單的方式,在多個線程中共享同一個bound socket file descriptor,然後各個線程在自己的服務中將其和一個epoll-event相關聯,啟動accept()服務。

epoll默認情況下是和select一樣執行Level-Triggle,多線程模式下使用電平觸發模式會有「驚群」效應,所有偵聽這個套接字的線程都會被喚醒,而多個線程同時執行accept()只會有一個線程成功,其他線程全部返回EAGAIN。

(2)邊緣觸發模式

雖然epoll的邊緣觸發工作狀態下內核保證只會通知一次(一個線程),但是在邊緣觸發模式下,根據epoll的手冊告知我們,無論是讀還是寫操作都要直到底層返回EAGAIN的時候本輪操作才可以結束,否則可能事件的數據沒有操作完,但是在邊緣觸發情況下有不會繼續發送信號,那麼待處理數據會一直滯留下去。

所以在多線程即使使用邊緣觸發也會有競爭問題:線程A的epoll_wait返回後,線程A不斷的調用accept()處理連接請求,當內核的accept queue隊列中的請求恰好處理完時候,內核會重新將該socket置為不可讀狀態,以便可以重新被觸發;此時如果新來了一個連接,那麼另外一個線程B可能被喚醒,然後執行accept()操作,不過此時之前的線程A還需要重新再執行一次accept()以確認accept queue已經被處理完了,此時如果線程A成功accept的話,線程B就被驚醒了。

而且情況更為嚴重的是在考慮負載均衡的情況下,其他線程有可能被餓死,絕大多數情況的連接都被之前喚醒線程的最後一次確認性accept()給實際消費了。

(3)新內核的解決方式

在4.5+內核版本上,epoll提供了EPOLLEXCLUSIVE標識,該標識會保證一個事件發生時候只有一個線程會被喚醒,以避免多偵聽下的「驚群」問題。

如果不支持該標識,還可以使用Edge-Triggle + EPOLLONESHOT方式,啟用該標識的時候epoll會在epoll_wait返回的時候自動禁止該描述符對應的事件通知,然後調用accept()消費事件,然後用戶需要手動執行epoll_ctl(EPOLL_CTL_MOD)再次手動使能,等於會有一次額外epoll_ctl系統調用的開銷。這種方式會讓負載在多個執行單元上均衡的分布,不過任一時候只能有一個工作線程調用accept(),限制了真正並行的吞吐量。

根本性的解決方式是使用新內核的SO_REUSEPORT,可以使得應用程序在同一個埠上面創建多個socket,從而避免上面共享socket帶來的種種問題。

1.2 多線程read

如果為每個線程維持一個自己的工作隊列,在自己的隊列中獨自偵聽自己的socket,那麼數據的有序和完整性是很容保證的,畢竟socket只會在一個工作線程中出現並使用。不過這種做法的缺點是每個線程的工作負載可能是不均衡的,這種事先劃分隊列的情況可能導致某些線程的事件處理延遲,而某些線程空閑原地打轉。

多個線程使用同一個隊列自然是在負載均衡、處理效率上是最理想的,可以稱之為」combined queue」模型,他們共享同一個epoll set,然後大家都嘗試去獲取active socket,不過也可能產生問題:

電平觸發的驚群自然不必多說。而即使使用了EPOLLEXCLUSIVE保證每次只有一個線程獲得對應讀事件,但是如果第一個線程沒有讀完數據,那麼下一次被喚醒的就可能是其他的工作線程,這種情況處理起數據就很糟糕了,畢竟在電平觸發的情況下,我們不知道有多少數據可以讀。

邊緣觸發也會導致數據完整性的競爭條件,其競爭的位置在於緩衝隊列讀完了,在內核將該事件置為可觸髮狀態,此時內核又收到數據了,那麼內核可能會喚醒其他線程來接收這個數據,而原本線程接收的數據就不完整了。

唯一解決的方式,就是採用EPOLLONESHOT的方式,在確認數據接收完全,本線程不需要再次使用socket的時候,再次手動觸發事件使能。

二、epoll中用戶態和內核態管理不一致的問題

這個問題被詬病了很久,在使用的時候必須特別的小心才可以。在通常情況下,用戶態調用close()關閉file descriptor的時候,內核的file description的引用計數會遞減,而當內核發現其沒有再被引用的時候,會清理該file description上面的epoll事件偵聽。

在通常的使用情況下,這確實沒有什麼問題,但是在遇到dup、fork、IO重定向等非常規操作的時候,file descriptor的生命周期和file description的生命周期就會不同,從而很容易發生問題:假設通過dup用戶態就有兩個fd共享同一個底層的file description,此時關閉原先註冊epoll event事件的fd而不調用epoll_ctl取消事件偵聽,那麼底層的epoll event事件訂閱就沒有真正被取消;此時上層的應用程序看來現象就是即便關閉了fd,但是epoll_wait()還是會不斷返回關閉了的fd的事件信息,更糟糕的是 fd已經關閉,我們無法通過epoll_ctl再次取消這個事件偵聽了 ,因為fd是epoll控制底層事件的唯一入口,即便相同引用底層的其他fd也不行,對此你無能為力。

作者的偽代碼很容易說明問題:

rfd, wfd =pipe()write(wfd,"a")# Make the "rfd" readableepfd = epoll_create()epoll_ctl(efpd, EPOLL_CTL_ADD, rfd, (EPOLLIN, rfd))rfd2 = dup(rfd)close(rfd)r = epoll_wait(epfd, -1ms)# still recv event!!!

所以,當你在epoll中關閉一個fd的時候,也定要事先調用epoll_ctl(EPOLL_CTL_DEL)取消事件偵聽,否則你唯一能補救的就是:通過epfd強制將整個epoll_set的事件都廢除掉,然後再從頭重新建立事件偵聽機制。

針對上面的epoll種種問題,Marek給出的建議就是:

如果可以不要在多線程中使用epoll的時候做負載均衡,因為往往實際得到的效率收穫甚微;不要在多線程中共享、同時操作socket。避免fork,如果必須的話:在execv之前請關閉所有註冊了epoll事件偵聽的file descriptor。在調用dup/dup2/dup3和close之前,必須使用epoll_ctl(EPOLL_CTL_DEL)取消事件偵聽!

關於上面的內容,前半部分其實已經在Nginx中有所涉及了,比如Nginx的accept_mutex、SO_REUSEPORT機制等,後半部分在以後使用epoll時候必須時刻提醒自己腦袋清醒,要麼就使用成熟的基於epoll封裝的事件庫!

本文完!

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 PHP技術大全 的精彩文章:

30歲跳進創業公司,我經歷了怎樣的「逆生長」?
Web 應用防護系統 OpenWAF 開源 CC 防護模塊
運維紀錄:遭遇CC攻擊,防禦與查水表
Nginx伺服器抵禦CC攻擊的相關配置講解

TAG:PHP技術大全 |

您可能感興趣

Nvidia停止Kepler架構筆記本顯卡驅動更新支持
技術驅動產品與服務,Talent Spot與i人事獲雙重大獎
技術驅動產品與服務,Talent Spot與i人事獲雙重大獎
取消煩人的Windows Update自動下載驅動
思科與聯想計劃將XPoint與Optane驅動器應用於超融合套件
如何使得支持OpenGL的Flatpak應用和遊戲在專有Nvidia驅動下工作
妙用Digital Twin虛實融合 驅動智能製造升級轉型
微軟開源驅動程序模塊框架 輕鬆編寫Windows驅動程序
電機驅動器模組方案如何搭配Arduino MICRO而運作?
驅動人生解決一鍵ghost升級Win10導致驅動異常問題
免驅之王 骨伽surpassion不用驅動也能微調DPI
vivo Lab推出vivo Play教育項目 以創造力驅動非凡生活體驗
Google與JBL 合作帶來由 Android TV 驅動的 Link Bar 條形音箱
智能舞台決策支撐系統MapReduce驅動模型初探
一位從事Linux設備驅動多年的嵌入式er教你理解嵌入式Wi-Fi的驅動架構
更容易驅動了:Stenheim Alumine Two SE書架音箱
以創造力驅動非凡生活體驗,vivo 推出vivo Play 教育項目
谷歌收購Android圖形驅動測試服務GraphicsFuzz
Labelhood第二日,需求驅動設計
Windows Core OS將成為雲驅動操作系統