1. 程式人生 > >用MarioTCP庫實現一個單機10億級的百萬併發長連線

用MarioTCP庫實現一個單機10億級的百萬併發長連線

注:如果用此伺服器做變長data的傳輸,請在業務處理函式中為input buffer增加清空功能(一行memset搞定;也可以在mariotcp核心程式碼mario_network.c的read功能中增加,mariotcp為了追求效能極限沒做此設定)。

MrioTCP,超級馬里奧,顧名思義,他不僅高效,而且超級簡易和好玩。同時他可以是一個很簡潔的Linux C 開發學習工程。毫不誇張的說,如果全部掌握這一個工程,你會成為一個Linux C的牛人;當然,你也可以通過原始碼包的mario.c(maritcp伺服器示例程式)來學習,可以很快入門上手進行Linux C開發。

經過兩個多月的測試

(編寫c++客戶端測試及調優系統引數),測試結果得到單機最大頻寬吞吐1000M,測試最高TCP長連線100萬,每秒處理連線數達4萬,此時系統壓力load值很低。總之,它可以發揮一臺伺服器的最大極限以提供最高效能的服務;而且經過完備測試,執行穩定且佔用系統資源非常少。

他是建立在Sourceforge上的一個開源專案,由原始碼的作者馮建華(JohnFong)發起。原始碼可以在Sourceforge上下載。

Getting Started

用MarioTCP來建立一個性能強大的TCP伺服器非常簡易!

  工程原始碼包就是一個非常簡潔的例子,生成了一個tcp伺服器程式:maritcp。
  原始碼包中:

  mario.c是簡易例子的main程式,直接make可以編譯出maritcp,一個tcp伺服器,業務邏輯只有一個功能:統計同時線上socket數、每隔1分鐘輸出一次。
  mario資料夾,MarioTCP的核心程式碼,make可以直接編譯出靜態的libmario.a。MarioTCP核心架構後續會介紹。
  test資料夾,是一個稍顯簡陋的客戶端測試程式,通過與伺服器建立連線、傳送LOGIN包登陸伺服器,此時maritcp伺服器會使同時線上加1,客戶端斷開時伺服器線上數減1。

現在講一下如何定製一個自己業務邏輯的tcp伺服器,只需五步:
  1、初始化SERVER
  SERVER *server = init_server(conf->port, conf->workernum, conf->connnum, conf->timeout, conf->timeout);

  傳入引數分別是:
  伺服器Listen埠,工作執行緒數,每個執行緒支援的連線數,讀超時時間,寫超時時間。
  workernum * connum 就是伺服器支援的長連線數,一個worker可以輕鬆支援10萬長連線。

2、實現業務邏輯函式並註冊
  具體業務邏輯函式請見Function模組。可通過mario.h中定義的名為“regist_*”的函式來註冊。

/*
  *  註冊業務處理函式
  /
  void regist_akg_func(uint16 id, FUNC_PTR func);
  id可以是0-65535的任意數,此id封裝在MarioTCP的協議中(見本文最後)。
  id的範圍,可以根據業務邏輯來定製,例如maritcp通過protocol.h中定義的CMD結構體來設定:
  typedef enum _CMD {
  CMD_FUNCTION_BASE = 0x6100,
  CMD_FUNCTION_LOGIN = 0x6101
  } CMD;

如果你想為maritcp增加一個"say_hello"的服務,可以這麼做:
  1)在CMD中增加:CMD_FUNCTION_SAY_HELLO = 0x602
  2) 在function中增加函式:
  sint32 say_hello(CONN c) {
  I)通過CONN來解析客戶端發過來請求的引數
  II)將“hello”設定到c->out_buf
  III)bufferevent_write(c->bufev, c->out_buf, hrsp->pkglen);
  IV)return 0;
  }
  3)在mario.c中增加:regist_akg_func(CMD_FUNCTION_SAY_HELLO, say_hello);

怎麼樣?自己定製業務邏輯,還是很簡單高效吧!

3、啟動日誌執行緒start_log_thread()
  MarioTCP的日誌功能封裝還不夠好,在“go on 1.0.0”頁面中繼續討論...

4、啟動伺服器start_server((void*) server);
  OK,一個可以支援100萬甚至更多長連線的TCP伺服器,誕生了!

Go On 1.0.0

第一個釋出版本為0.9.9,儘管用這個包,通過幾分鐘就可以實現一個定製了你的業務邏輯的、穩定高效的TCP伺服器,但是MarioTCP還有很多有待完善的地方,讓我們一起儘快解決如下問題,讓MarioTCP-1.0.0儘快釋出!


1、MarioTCP協議如何優化
  為了使MarioTCP足夠安全,規定了一個簡易的MarioTCP協議,經過三次握手連線到MarioTCP的client,接下來發的包要求格式必須是“HEAD+Data”的形式,而HEAD結構體定義在mario_akg.h中:
  typedef struct _HEAD {
  uint32 stx;
  uint16 pkglen;
  uint16 akg_id;
  } HEAD;
  pkglen是整個包的長度,即“HEAD+Data”。
  akg_id及自定義的業務邏輯函式對應的id,例如“Getting Started”頁面中的CMD_FUNCTION_SAY_HELLO
  stx是你自定義的協議密文,通過regist_stx(uint32 stx)來註冊(見mario.c)

儘管MarioTCP的協議足夠簡單了,而且協議最開頭的密文可以自定義,但是是否可以更簡單或者無協議,以最大程度的方便開發使用,需要大家的建議和幫助!

2、日誌系統過於死板
  MarioTCP有一套自成系統的日誌功能,但是比較晦澀難懂。
  接下來再展開...

3、業務邏輯穩定性支援
  MarioTCP對於網路連線和讀寫,非常高效和穩定。
  但是MarioTCP的執行緒池是固定個數的,且是全域性唯一初始化的,死掉的執行緒不可再重啟;分配網路任務的Master執行緒不具備監聽worker的功能,一個執行緒死掉了、任務卻還會一直分配過來,造成服務堆積且不處理。如果業務邏輯如果非常複雜和低效,就會出現這個問題。
  在大型線上專案中,用到MarioTCP的地方,都會通過業務邏輯模組的監聽、告警及程式自動處理來避免上述問題。由於時間問題還沒有把此功能抽象到MarioTCP中。

  這件事情,近期我會抓緊處理。也希望有朋友建議和幫助!!

Why Supper

一、為什麼超級高效
  1、網路服務用到的所有結構體和記憶體都是啟動程式時初始化的,無銷燬,無回收。
  無銷燬好理解,不解釋。
  無回收,是指所有記憶體單元拿來即用,用完及可,不用做reset操作。
  2、一個master執行緒進行accept
  經過測試發現多程序或執行緒進行accept和一個程序或執行緒accept,在極限壓力下區別不大。
  一個master比多個master好在不用再通過鎖來解決同步問題。
  3、master與worker時單一生產者消費者模式,完全無鎖通訊
  不光accept無鎖,分配connection、後續的conncetion處理都是無鎖的。
  甚至業務邏輯(見示例maritcp的統計線上數功能)、MarioTCP的日誌系統(這也是日誌系統抽象不夠的一個原因,之前的設計太依賴於整體架構了)都是無鎖處理的!
  4、一個worker一套libevent環境
  libevent處理10萬長連線的網路讀寫事件,其效能達到最大化了。
  每個worker都獨立一套libevent,這個結構經過測試,發現開銷很小、效能很高。

二、單機百萬長連線、四萬cps(連線每秒)如何做測試得來
  1、設定系統最大檔案數為unlimited
  2、設定系統的tcp記憶體核心引數到256M以上
  3、設定系統的ip到15個,那麼可服務的長連線數理論上最少15*(65535-1024)個
  4、用epoll或libevent開一個可同時連線5w的客戶端程式;程式還要實現每秒隨機挑選1000個連線斷掉,並再新建立1000個連線。另外在隨機挑選幾千連線發包。
  同時再多臺機器上開啟20個客戶端,那麼就是100w長連線,每秒2w個連線斷掉、2w個新連線加入進來,並且有若干包發過來。
  5、設定服務端可重用SYN_WAIT的連線;客戶端斷連線的方式是主動斷掉(防止客戶端程式埠堆積)
  總之很折騰的一個測試,前前後後大約2個多月才測試完畢。
  以上內容憑記憶寫的,怕有錯誤或疏漏,回頭為了公佈測試程式碼和測試結果給大家,會再次開發、測試並調整補過上述內容。