epoll編程,如何實現高並發伺服器開發?
1、本系統處理的業務為多客戶端接入,一旦接入基本超過8個小時的長連接,但是登陸以後客戶端基本不怎麼活動,只有客戶端觸發相關設定事件才會產生活躍通信。
2、查了很多資料,單單多進程是不現實的,但是多線程開發linux系統在線程的數量上是有上限的,如何解決?3、QQ、SKYPE等的多客戶端登陸軟體,伺服器一般是如何設計開發的?4、客戶端接入時間隨機,系統運行初期不會存在同一時間成千上萬用戶登陸的情況,但是用戶一旦接入伺服器就會長時間不會斷開。5、能否使用epoll技術跟多線程技術配合開發?HOW?6、系統開發使用TCP協議。
7、希望大家能給個詳細開發框架。
~~~ 2017-09-24 增加~~~
評論區里有不少乾貨,有興趣的同學可以看看。鑒於我很懶,就不再編輯更新原始回答了。
看了一下,上次編輯是15年7月份,沒想到這麼長時間了,還有人關注這個問題
以下是原始回答。
~~~ 15年最初的原始回答 ~~~
閑答。
按重要性和基礎程度,打亂下順序吧~~
先回問題,然後說怎麼實現。註:以下數據,均基於亞馬遜AWS c3.xlarge 機型。
虛擬CPU:4
內存:7.5 GBc3.xlarge 配置和價格: AWS | Amazon EC2~~~~~~~~~~ 分割線 ~~~~~~~~~~
2、查了很多資料,單單多進程是不現實的,但是多線程開發linux系統在線程的數量上是有上限的,如何解決?
多線程數量限制?有!但這重要嗎?想著開著無限的線程,每個線程都在跑?
線程調度是有系統資源開銷的!!!!
線程調度是有系統資源開銷的!!!!線程調度是有系統資源開銷的!!!!重要的事情說三遍~~!!!
理論上,如果沒有I/O等待等讓CPU idle的事情,線程數最好等於CPU核心數目。
線程數最好等於CPU核心數目!!!線程數最好等於CPU核心數目!!!線程數最好等於CPU核心數目!!!重要的事情再說三遍。
舉個實際的栗子:
本人負責公司網路框架的架構設計和開發。在4核的 c3.xlarge 虛擬機上,壓測框架時,2000 worker 線程的QPS(這裡理解為TPS問題也不大)遠遠低於4個worker線程的QPS!4個worker線程,再加其他輔助線程,QPS最大每秒35萬。2000 worker線程,加同樣的輔助線程,QPS最大每秒15萬~~!大部分的時間和其他系統資源都消耗在了線程的切換上~~!這就是為什麼單線程的程序在某些情況下比多線程的程序要快~~!(單線程模擬多線程的線程庫,不妨參考 state thread:http://state-threads.sourceforge.net。足夠簡單。)
1、本系統處理的業務為多客戶端接入,一旦接入基本超過8個小時的長連接,但是登陸以後客戶端基本不怎麼活動,只有客戶端觸發相關設定事件才會產生活躍通信。
6、系統開發使用TCP協議。
目前開的框架,TCP長鏈接,最大壓測鏈接108萬,總 QPS 6萬。
伺服器也是c3.xlarge。開了18台機器做客戶端壓,每台機器6萬鏈接(記得要修改 /proc/sys/net/ipv4/ip_local_port_range,不然一台機器出不了這麼多鏈接)。4、客戶端接入時間隨機,系統運行初期不會存在同一時間成千上萬用戶登陸的情況,但是用戶一旦接入伺服器就會長時間不會斷開。
壓測,每批次新加6萬鏈接。
5、能否使用epoll技術跟多線程技術配合開發?HOW?
Linux上,必須的!!!
3、QQ、SKYPE等的多客戶端登陸軟體,伺服器一般是如何設計開發的?
沒有一般,都是根據具體需求實際定製!!!
沒有一般,都是根據具體需求實際定製!!!沒有一般,都是根據具體需求實際定製!!!但分散式、去中心化、無狀態化、一致性哈希、……,都是必須的~~~!
但分散式、去中心化、無狀態化、一致性哈希、……,都是必須的~~~!但分散式、去中心化、無狀態化、一致性哈希、……,都是必須的~~~!(說了好幾個三遍,累死了~~~喝點水~~)
不妨上網搜搜,將網路架構,伺服器架構的演化。中小型公司按那些套路來,基本都能搞定。圖太多,說起來太羅嗦。都是重複勞動,我就不寫了,請自己搜。你問大公司?請參考第一句話:沒有一般,都是根據具體需求實際定製!!!
補充一點:網上搜的,都會對memcached、redis 等有很大的依賴。但我上家公司,也用,很重要,但只是給PHP端用。後端服務集群全用專用緩存~!!專用緩存~~!含邏輯~~!自己開發~~!專用緩存~~!含邏輯~~!自己開發~~!專用緩存~~!含邏輯~~!自己開發~~!又說三遍。
撐住1.6億註冊用戶。7、希望大家能給個詳細開發框架。
框架?是參考架構設計吧?
框架有很多,比如ICE,Facebook Thrift、Apache Thrift 等~~
注意,後兩個thrift不完全相同~~!!!然後怎麼做:
沒有固定的套路!!!只有要注意的要點!!!沒有固定的套路!!!只有要注意的要點!!!沒有固定的套路!!!只有要注意的要點!!!又是三遍。
要點:
盡量少的線程切換盡量少的共享衝突盡量無鎖工作中的線程數盡量等於CPU核心數盡量沒有等待時間片的線程邏輯盡量簡化,避免不必要的封裝和轉發工程不是學術,OOP要給簡單易用高性能好維護讓道~~!(就是ICE概念多,太複雜,上家公司才開發了自己的框架。就是因為Facebook Thrift 太羅嗦,一個非同步都要繞好幾道彎,鏈接還和CPU核心綁定(如果就一個鏈接,10萬QPS,你會發現就一個核忙得要死,其他核心都在吃乾飯~),現在公司才決定自己開發網路框架。)EPOLL:Edge Trigger、OneShot!!!
不要在epoll_wait()線程中用太長時間處理非epoll_wait()的事情。能用atomic的就不要用mutex(這是C++11的事了)上面就不三遍了,太多太啰嗦~~~
~~~~~~~~~~~~~ 7月3日追加 ~~~~~~~~~~~~~~
感謝 Irons Du曾凌恆每天不吃冰淇淋 的評論,涉及到一些昨天忘了的事情。
PS:今次不三遍了~~:)追加要點:
A. 資料庫
資料庫一定要分布。如果有好的數據路由中間層服務,或者好的集群管理器,或者好的Sharding服務,是MongoDB還是MySQL完全不重要~~~!
如果使用MongoDB,需要注意查詢 API 的where處理(自定義Javascript查詢條件)。C API的 where 處理非常非常低效(至少去年還是這樣。今年因項目的關係,沒有跟進)資料庫一定要分庫分表。分表強烈建議使用Hash分表,盡量避免按區段分表。Hash分表設計好了,要擴容也非常容易。區段分表貌似擴容很容易,但時間長了,你的熱點數據分布就極其不均勻了。分庫分表,盡量非同步並發查詢(靠你的數據路由中間件了。當前兩家公司都是自己開發的,外面的吹得太凶,不實用。)除支付業務外,嚴格禁止聯合查詢、複合查詢、事務操作!!!!(支付一會單說)分庫分表,表都不在一台機器上了,聯合查詢、複合查詢、事務操作必然失敗~~~!聯合查詢、複合查詢、事務相關的操作請由中間層服務配合完成!!!PS:MySQL等,聯合查詢、複合查詢、事務操作,效率極低~~極低~~~!!!還會因為鎖表時間過長而阻塞其他查詢~~!!支付的邏輯設計並分解好了,可以不用事務完成。要用事務,請確定相關的表均部署在同一台資料庫實例上!!!B. 去中心化、無狀態:
高彈性架構這是必須的。盡量將狀態剝離成一個單獨的狀態服務(也是分散式集群),其他業務邏輯全部變成無狀態的。狀態信息請通過狀態服務處理。無狀態的一般去中心化都很簡單。無外乎一致性哈希、隨機、輪轉等等。去中心化也是確保無單點故障!!!如果非要有中心,請盡量選折以下兩個方案:1. 中心如果很簡單,請確保在崩潰/殺死後,1秒鐘內能立刻復活啟動~~~2. 中心改為管理集群,自動選舉主核心。當原主核心失效後,新的核心自動接管當前集群網路。C. 協議:
不要使用XML!!!這都不想說了,讓我看到就是千萬隻神獸在胸中蹦騰~~!!!如果文本,請使用JSON。如果二進位,請使用JSON對應的二進位化協議。BSON嘛~~持保留態度。如果需要協議的灰度升級,如果在協議灰度升級時不想實現兩套不同版本的介面,請遠離使用IDL的協議/框架!!!這也是這兩家公司棄用 ICE和Facebook Thrift 的原因之一。最後,如果你是初入行者,不妨多看看前面其他大拿推薦的資料,看看Reactor模式,看看Proactor。多看看其他框架,服務集群的設計。
但是!!當你成長後,一切模式、設計,都是扯淡~~!模式是死的,需求是變動的,人和思維是活的!只有根據實際需求,具體設計!具體定製!!!(還記得無字真經嗎~~)(貌似扯遠了,已經不是伺服器高並發了,而是高並發後端系統集群了。。。-_-! )
nginx 多進程網路編程的巔峰
memcached 多線程網路編程的巔峰redis單進程網路編程的巔峰~~@陳碩 陳大大說的, loop per thread 是正解~~一個成熟的高性能伺服器,epoll相關的代碼,不到萬分之一。
而往往入門服務端的人,都天真的人為:高性能服務端開發 == EPOLL,真好笑,之所以會出現 epoll這種被捧上天的垃圾,明明就是 posix 或者最早版本的 unix/bsd/systemv 的設計考慮不完善。按今天的眼光反思 posix 和 unix/bsd/systemv 當年的設計,epoll 這種補丁就不應該實現。非同步 reactor 框架應該就只有一個簡單而統一的 selector 就足夠了,所有系統都相同,提供:- register: 註冊
- unregister:刪除
- set:設置
- wait:等待事件
- read:讀取事件
- wake:將等待中的 wait 無條件喚醒
別以為這些 poll / epoll / kevent / pollset / devpoll / select / rtsig
是些什麼 「高性能伺服器」 的 「關鍵技術」,它們只是一個 API,而且是對原有系統 API設計不完善打的補丁,各個內核實現了一套自己的補丁方式,它們的存在,見證了服務端技術碎片化的遺憾結果。之所以會有這些亂七八糟的東西,就是早期的 posix / unix/ bsd /systemv 設計不周全,或者不作為留下的惡果。並非什麼 「關鍵技術」。---------不用提 windows 的 iocp了,proactor 會來強姦你代碼結構,遭到大家唾棄是有原因的。不像 reactor那樣優雅,所以 java nio 選擇 reactor 是正確的。即便在 reactor 中,epoll 也是一個失敗的例子,調用最頻繁的 epoll_ctl 的系統佔用估計大家都感受過吧,這方面 epoll 真該象 kevent / pollset 學習一下。前端介面機,後端UDP,還要啥戰鬥機。
高性能伺服器說白了就一點 妥善的利用CPU 和IO。把時間都用在CPU計算上,IO等待的時間都去做計算即可。弱弱的推薦 github c1000kpracticeguide 。8年前的機器做個單機百萬也木有問題。
C++用boost.asio, libevent都可以如果確實在乎線程限制, 使用golang的虛擬線程也是不錯的選擇.
伺服器後端框架其中大致差不多,可從以下幾個部分去綜合考慮設計:一、套接字IO模型(Linux)1 應用場景:a.登陸低並發,大量長連接,少量活動連接,TCP;b.業務未知,對事件的響應時間未知。2 進(線)程模型設計:1.a中的場景是最適合使用epollIO復用模型,這種情況下,epoll的效率比其他IO復用模型(select,poll)要高效。如果業務簡單(不耗時,不阻塞):單進程+eppll足以應付,在epoll的主循環中依次處理事件即可。業務比較耗時:單進程多線程+epoll或多進程+epoll,主進(線)程負責監聽和分發事件,工作進程(線程)處理事件。
題主的問題各種跳躍我也不知道從哪個說起,我就簡單就一般的高性能伺服器的最基礎的幾點隨便說說吧,歡迎拍磚
我覺得高並發這個,明白道理是主要的,並不需要記住是否epoll/iocp什麼的吧,只需要知道做高並發需要做到非同步非阻塞就可以了,至於工具的使用,epoll也好iocp也好什麼都好,能實現理論上的高並發模型就ok
其實所謂高並發,就是服務對於每次的請求和響應的時間足夠短,一次請求20ms,和一次請求10ms,肯定是後者的並發更高,我們開發所需要做到的,就是縮短請求和響應這種交互的時間長度
那麼服務在哪些方面是耗時間的呢?做到哪些是省時間呢?
先從單機來說:
我們的服務都是運行在伺服器上的,程序運行中需要用到的硬體大概有四樣就是cpu,內存,網卡,還有磁碟,這四樣的速度依次遞減,而且速度差距都是N多倍的
程序一旦啟動肯定是運行在內存中這跑不了,從磁碟載入到內存,然後再從內存一坨一坨的輸送指令和運算需要的數據到L3三級緩存L2二級緩存L1一級緩存再到cpu,可以想像,越慢的設備你越少用它,就越不會被拖後腿,不拖後腿了,速度肯定就快了啊,所以高並發服務開發中這些是要考慮的一個必要點,比如業務必須用到磁碟怎麼辦啊?那就把磁碟當磁帶,磁碟的順序寫是很快的,磁針不需要來回跳的尋道嘛。所以要做到,少用磁碟,盡量順序寫。
內存的使用,主要是代碼中不要太多的臨界資源爭搶,就是多個線程一起讀寫同一塊內存,這樣本來並行的運算變成串列了,導致性能下降。還有網路,網路的使用通常是rpc形式的,比如讀資料庫,讀外部緩存,一個請求發出去,然後阻塞等待,等數據返回後再解除阻塞。那如果改變下方式,一個請求發出去你不阻塞等待,而是直接回頭去做別的事,等請求的響應返回了提醒你一聲,你再回來處理這個響應,這效率是不是就高了呢?epoll的作用就是做這個的,就是用來提高利用率的,阻塞傻等著沒活干肯定是浪費啊再說cpu,cpu的繁忙程度代表你執行指令的密集度,一般都是在並發強壓下cpu的load就應該很高了,如果怎麼壓也不高就代表有io在拖後腿,就考慮怎麼樣能減少io的頻率,如果並發不大cpu還很繁忙,那肯定是代碼業務邏輯需要的運算太多,開始考慮優化邏輯,這方面的優化是無止境的,我感覺這也是技術含量最高的部分,各種演算法的特點要很熟悉,然後判斷是不是適合你的這個業務場景,另外還有很多可以提升性能的trick,對所用操作系統的特性也要很了解,這需要很多年的實踐經驗的積累。多線程/多進程,這目的是利用多核,為了充分利用cpu,多線程的開發是很複雜的,不好管理,進程間能少通信就盡量少通信,否則就是互相拖後腿,線程數量也不能一味求多,線程間切換的開銷是很大的,鎖的使用要仔細考慮。還有多機集群的方式,很好理解,跟多核cpu的道理一樣,有更多的人同時幹活來分擔工作量,肯定單位時間內乾的活就更多,幹完活用的時間更少。多機集群的設計更加複雜。
想到哪兒寫到哪兒了,大概就這些。
所以你看,epoll在高並發伺服器的開發中,僅僅算是很小的一部分了。
以上說得比較簡單粗略,題主的問題全說明白可不容易,體力消耗也會很大,推薦看本書《構建高性能web站點》,淺顯易懂挺好看的,當然這本書不能解答qq伺服器是咋設計等問題,書的內容比較基礎。
=============
補充一下題主說的長鏈接,長鏈接多active鏈接少,這場景很適合epoll來做,epoll只處理active的鏈接,不活躍的就放在一邊不太需要操心,其實目前linux系統所有的網路鏈接管理都可以用epoll題主這個需求有點NSDAQ股票市場的使用場景。
美國NASDAQ股票交易市場使用Windows 2003Server上跑IOCP很完美解決了你這類問題。隨著現今計算機硬體成本的白菜價趨勢加上採用極其廉價的OEM操作系統,IOCP可望再度成為中國商家心目中高性能高品質的成熟解決方案。使用erlang..
EPOLL單線程處理事件,收到的數據放入QUEUE,多線程取出數據做業務邏輯。
epoll這種多路復用就緒通知技術確實適合海量並髮長鏈連接的場景下,與select/poll等相比,它去除了最大連接數的限制,也減少了在內核態和用戶態之間複製文件描述符的開銷。
看一看Nginx的代碼不就好了嗎?
--2016年1月29日01:02:57--有這麼難嗎?/*當然,對於這樣一句話也能碰碎了某些人的玻璃心,我想真誠地對你說,我是故意的。*/
用Nginx核心部分+改造下nginx_tcp_proxy就搞定啦
看到大家都挺牛逼的,小弟最近用epoll遇到一個問題,對於一個socket,後一個epoll_ctl事件會替換掉前一個事件,請問下,在註冊out事件時如何判斷是否需要帶上in事件?或者註冊in事件時怎麼判斷是否需要out事件?
不是大並發的情況不一定要用epoll的。目測你的情況基本款的select加worker線程池,中間插個隊列就可以搞定。需要長時間保持連接的話,內存搞大點吧。
如何使客戶端接入伺服器長時間不會斷開就需要大量的調優,對於你這麼大的問題我只能回答一句,以tcp為根基,自己設計協議
題主要解決的 問題是: 大量客戶端接入, 服務端維護很多TCP長連接。
我覺得 考慮用C++ 或者golang如果用C++, 成熟的網路庫不少誒。
libevent, facebook有個libPhenom 或者自行寫個 簡單點golang的話,可以自己開發, 用 goroutine實現。 框架也有, 但是不確定 是否成熟單線程epoll估計足以應付題主的場景,至於怎麼實現,網上例子很多,可以搜索下。
推薦閱讀:
※什麼人適合當程序員?
※如何提高自己的android編程能力?
※為什麼公司不給實習生看公司代碼?
※設計 MySQL 數據表的時候一般都有一列為自增 ID,這樣設計原因是什麼,有什麼好處?
※毫無基礎的人如何入門 Python ?