四種框架分別實現百萬websocket常連線的伺服器

發表於2016-07-31

事實上,最近我又增加了幾個框架,現在包括 Netty, Undertow, Jetty, Spray, Vert.x, Grizzly 和 Node.js七種框架。
測試資料可以看下一篇文章: 七種WebSocket框架的效能比較

著名的 C10K 問題提出的時候, 正是 2001 年。這篇文章可以說是高效能伺服器開發的一個標誌性文件,它討論的就是單機為1萬個連線提供服務這個問題,當時因為硬體和軟體的限制,單機1萬還是一個非常值得挑戰的目標。但是時光荏苒,隨著硬體和軟體的飛速發展,單機1萬的目標已經變成了最簡單不過的事情。

現在用任何一種主流語言都能提供單機1萬的併發處理的能力。所以現在目標早已提高了100倍,變成C1000k,也就是一臺伺服器為100萬連線提供服務。在2010年,2011年已經看到一些實現C1000K的文章了,所以在2015年,實現C1000K應該不是一件困難的事情。

本文是我在實踐過程中的記錄,我的目標是使用spran-websocket,netty, undertow和node.js四種框架分別實現C1000K的伺服器,看看這幾個框架實現的難以程度,效能如何。開發語言為Scala和Javascript。

當然,談起效能,我們還必須談到每秒每個連線有多少個請求,也就是RPS數,還要考慮每條訊息的大小。
一般來說,我們會選取一個百分比,比如每秒20%的連線會收發訊息。我的需求是伺服器只是push,客戶端不會主動傳送訊息。 一般每一分鐘會為這一百萬群發一條訊息。

所以實現的測試工具每個client建立60000個websocket連線,一共二十個client。實際不可能使用20臺機器,我使用了兩臺AWS C3.2xlarge(8核16G)伺服器作為客戶端機。每臺機器10個客戶端。
伺服器每1分鐘群發一條訊息。訊息內容很簡單,只是伺服器的當天時間。

最近看到360用Go實現的訊息推送系統,下面是他們的資料:

目前360訊息推送系統服務於50+內部產品,萬款開發平臺App,實時長連線數億量級,日獨數十億量級,1分鐘內可以實現億量級廣播,日下發峰值百億量級,400臺物理機,3000多個例項分佈在9個獨立叢集中,每個叢集跨國內外近10個IDC。

四個伺服器的程式碼和Client測試工具程式碼可以在github上下載。 (其實不止四種框架了,現在包括Netty, Undertow, Jetty, Spray-websocket, Vert.x, Grizzly 和 Node.js 七種框架的實現)

測試下來可以看到每種伺服器都能輕鬆達到同時120萬的websocket活動連線,只是資源佔用和事務處理時間有差別。120萬隻是保守資料,在這麼多連線情況下伺服器依然很輕鬆,下一步我會進行C2000K的測試。

在測試之前我們需要對伺服器/客戶機的一些引數進行調優。

伺服器的引數調優

一般會修改兩個檔案,/etc/sysctl.conf/etc/security/limits.conf, 用來配置TCP/IP引數和最大檔案描述符。

TCP/IP引數配置

修改檔案/etc/sysctl.conf,配置網路引數。

數值根據需求進行調整。更多的引數可以看以前整理的一篇文章: Linux TCP/IP 協議棧調優
執行/sbin/sysctl -p即時生效。

最大檔案描述符

Linux核心本身有檔案描述符最大值的限制,你可以根據需要更改:

  • 系統最大開啟檔案描述符數:/proc/sys/fs/file-max
    1. 臨時性設定:echo 1000000 > /proc/sys/fs/file-max
    2. 永久設定:修改/etc/sysctl.conf檔案,增加fs.file-max = 1000000
  • 程式最大開啟檔案描述符數
    使用ulimit -n檢視當前設定。使用ulimit -n 1000000進行臨時性設定。
    要想永久生效,你可以修改/etc/security/limits.conf檔案,增加下面的行:

還有一點要注意的就是hard limit不能大於/proc/sys/fs/nr_open,因此有時你也需要修改nr_open的值。
執行echo 2000000 > /proc/sys/fs/nr_open

檢視當前系統使用的開啟檔案描述符數,可以使用下面的命令:

其中第一個數表示當前系統已分配使用的開啟檔案描述符數,第二個數為分配後已釋放的(目前已不再使用),第三個數等於file-max。

總結一下:

  • 所有程式開啟的檔案描述符數不能超過/proc/sys/fs/file-max
  • 單個程式開啟的檔案描述符數不能超過user limit中nofile的soft limit
  • nofile的soft limit不能超過其hard limit
  • nofile的hard limit不能超過/proc/sys/fs/nr_open

應用執行時調優

  1. Java 應用記憶體調優
    伺服器使用12G記憶體,吞吐率優先的垃圾回收器:

2. V8引擎

OutOfMemory Killer

如果伺服器本身記憶體不大,比如8G,在不到100萬連線的情況下,你的伺服器程式有可能出現”Killed”的問題。 執行dmesg可以看到

這是Linux的OOM Killer主動殺死的。 開啟oom-killer的話,在/proc/pid下對每個程式都會多出3個與oom打分調節相關的檔案。臨時對某個程式可以忽略oom-killer可以使用下面的方式:
echo -17 > /proc/$(pidof java)/oom_adj
解決辦法有多種,可以參看文章最後的參考文章,最好是換一個記憶體更大的機器。

客戶端的引數調優

在一臺系統上,連線到一個遠端服務時的本地埠是有限的。根據TCP/IP協議,由於埠是16位整數,也就只能是0到 65535,而0到1023是預留埠,所以能分配的埠只是1024到65534,也就是64511個。也就是說,一臺機器一個IP只能建立六萬多個長連線。
要想達到更多的客戶端連線,可以用更多的機器或者網路卡,也可以使用虛擬IP來實現,比如下面的命令增加了19個IP地址,其中一個給伺服器用,其它18個給client,這樣
可以產生18 * 60000 = 1080000個連線。

修改/etc/sysctl.conf檔案:

執行/sbin/sysctl -p即時生效。

伺服器測試

實際測試中我使用一臺AWS C3.4xlarge (16 cores, 32G memory)作為應用伺服器,兩臺AWS C3.2xlarge (8 cores, 16G memory)伺服器作為客戶端。
這兩臺機器作為測試客戶端綽綽有餘,每臺客戶端機器建立了十個內網虛擬IP, 每個IP建立60000個websocket連線。

客戶端配置如下
/etc/sysctl.conf配置

/etc/security/limits.conf配置

服務端配置如下
/etc/sysctl.conf配置

/etc/security/limits.conf配置

Netty伺服器

  • 建立120萬個連線,不傳送訊息,輕輕鬆鬆達到。記憶體還剩14G未用。

  • 每分鐘給所有的120萬個websocket傳送一條訊息,訊息內容為當前的伺服器的時間。這裡傳送顯示是單執行緒傳送,伺服器傳送完120萬個總用時15秒左右。

傳送時CPU使用率並不高,網路頻寬佔用基本在10M左右。

客戶端(一共20個,這裡選取其中一個檢視它的指標)。每個客戶端保持6萬個連線。每個訊息從伺服器傳送到客戶端接收到總用時平均633毫秒,而且標準差很小,每個連線用時差不多。

平均每個client的RPS = 1000, 總的RPS大約為 20000 requests /seconds.
latency平均值為633 ms,最長735 ms,最短627ms。

Spray伺服器

  • 建立120萬個連線,不傳送訊息,輕輕鬆鬆達到。它的記憶體相對較高,記憶體還剩7G。

  • 每分鐘給所有的120萬個websocket傳送一條訊息,訊息內容為當前的伺服器的時間。
    CPU使用較高,傳送很快,頻寬可以達到46M。群發完一次大約需要8秒左右。


客戶端(一共20個,這裡選取其中一個檢視它的指標)。每個客戶端保持6萬個連線。每個訊息從伺服器傳送到客戶端接收到總用時平均1412毫秒,而且標準差較大,每個連線用時差別較大。

Undertow

  • 建立120萬個連線,不傳送訊息,輕輕鬆鬆達到。記憶體佔用較少,還剩餘11G記憶體。

    每分鐘給所有的120萬個websocket傳送一條訊息,訊息內容為當前的伺服器的時間。
    群發玩一次大約需要15秒。

客戶端(一共20個,這裡選取其中一個檢視它的指標)。每個客戶端保持6萬個連線。每個訊息從伺服器傳送到客戶端接收到總用時平均672毫秒,而且標準差較小,每個連線用時差別不大。

node.js

node.js不是我要考慮的框架,列在這裡只是作為參考。效能也不錯。

參考文件

  1. HTTP長連線200萬嘗試及調優
  2. Linux最大開啟檔案描述符數
  3. 100萬併發連線伺服器筆記之1M併發連線目標達成
  4. 知乎:如何實現單伺服器300萬個長連線的?
  5. 構建C1000K的伺服器
  6. 千萬級併發實現的祕密
  7. C1000k 新思路:使用者態 TCP/IP 協議棧
  8. https://github.com/xiaojiaqi/C1000kPracticeGuide
  9. 600k concurrent websocket connections on AWS using Node.js
  10. https://plumbr.eu/blog/memory-leaks/out-of-memory-kill-process-or-sacrifice-child
  11. http://it.deepinmind.com/java/2014/06/12/out-of-memory-kill-process-or-sacrifice-child.html
  12. https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/s-memory-captun.html
  13. http://www.nateware.com/linux-network-tuning-for-2013.html#.VV0s6kawqgQ
  14. http://warmjade.blogspot.jp/2014_03_22_archive.html
  15. http://mp.weixin.qq.com/s?__biz=MjM5NzAwNDI4Mg==&mid=209282398&idx=1&sn=9ffef32b3ab93d1e239c9dc753a3a9bb

相關文章