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 GB

c3.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 ?

TAG:編程 | Linux | 伺服器 | epoll |