使用四种框架分别实现百万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,配置網絡參數。
| 1 2 3 | net.ipv4.tcp_wmem =?4096?87380?4161536 net.ipv4.tcp_rmem =?4096?87380?4161536 net.ipv4.tcp_mem =?786432?2097152?3145728 |
數值根據需求進行調整。更多的參數可以看以前整理的一篇文章:?Linux TCP/IP 協議棧調優?。
執行/sbin/sysctl -p即時生效。
最大文件描述符
Linux內核本身有文件描述符最大值的**,你可以根據需要更改:
- 系統最大打開文件描述符數:/proc/sys/fs/file-max
- 臨時性設置:echo 1000000 > /proc/sys/fs/file-max
- 永久設置:修改/etc/sysctl.conf文件,增加fs.file-max = 1000000
- 進程最大打開文件描述符數
使用ulimit -n查看當前設置。使用ulimit -n 1000000進行臨時性設置。
要想永久生效,你可以修改/etc/security/limits.conf文件,增加下面的行:
| 1 2 3 4 | * hard nofile 1000000 * soft nofile 1000000 root hard nofile 1000000 root soft nofile 1000000 |
還有一點要注意的就是hard limit不能大于/proc/sys/fs/nr_open,因此有時你也需要修改nr_open的值。
執行echo 2000000 > /proc/sys/fs/nr_open
查看當前系統使用的打開文件描述符數,可以使用下面的命令:
| 1 2 | [root@localhost ~]# cat?/proc/sys/fs/file-nr 1632?0?1513506 |
其中第一個數表示當前系統已分配使用的打開文件描述符數,第二個數為分配后已釋放的(目前已不再使用),第三個數等于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
應用運行時調優
服務器使用12G內存,吞吐率優先的垃圾回收器:
| 1 | JAVA_OPTS="-Xms12G -Xmx12G -Xss1M -XX:+UseParallelGC" |
| 1 | node --nouse-idle-notification --expose-gc --max-new-space-size=1024 --max-new-space-size=2048 --max-old-space-size=8192 ./webserver.js |
OutOfMemory Killer
如果服務器本身內存不大,比如8G,在不到100萬連接的情況下,你的服務器進程有可能出現"Killed"的問題。 運行dmesg可以看到
| 1 | Out?of memory: Kill?process?10375 (java) score?59?or sacrifice child |
這是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個連接。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ifconfig eth0:0?192.168.77.10 netmask?255.255.255.0 up ifconfig eth0:1?192.168.77.11 netmask?255.255.255.0 up ifconfig eth0:2?192.168.77.12 netmask?255.255.255.0 up ifconfig eth0:3?192.168.77.13 netmask?255.255.255.0 up ifconfig eth0:4?192.168.77.14 netmask?255.255.255.0 up ifconfig eth0:5?192.168.77.15 netmask?255.255.255.0 up ifconfig eth0:6?192.168.77.16 netmask?255.255.255.0 up ifconfig eth0:7?192.168.77.17 netmask?255.255.255.0 up ifconfig eth0:8?192.168.77.18 netmask?255.255.255.0 up ifconfig eth0:9?192.168.77.19 netmask?255.255.255.0 up ifconfig eth0:10?192.168.77.20 netmask?255.255.255.0 up ifconfig eth0:11?192.168.77.21 netmask?255.255.255.0 up ifconfig eth0:12?192.168.77.22 netmask?255.255.255.0 up ifconfig eth0:13?192.168.77.23 netmask?255.255.255.0 up ifconfig eth0:14?192.168.77.24 netmask?255.255.255.0 up ifconfig eth0:15?192.168.77.25 netmask?255.255.255.0 up ifconfig eth0:16?192.168.77.26 netmask?255.255.255.0 up ifconfig eth0:17?192.168.77.27 netmask?255.255.255.0 up ifconfig eth0:18?192.168.77.28 netmask?255.255.255.0 up |
修改/etc/sysctl.conf文件:
| 1 | net.ipv4.ip_local_port_range =?1024 65535 |
執行/sbin/sysctl -p即時生效。
服務器測試
實際測試中我使用一臺AWS C3.4xlarge (16 cores, 32G memory)作為應用服務器,兩臺AWS C3.2xlarge (8 cores, 16G memory)服務器作為客戶端。
這兩臺機器作為測試客戶端綽綽有余,每臺客戶端機器創建了十個內網虛擬IP, 每個IP創建60000個websocket連接。
客戶端配置如下:
/etc/sysctl.conf配置
| 1 2 3 | fs.file-max =?2000000 fs.nr_open =?2000000 net.ipv4.ip_local_port_range =?1024?65535 |
/etc/security/limits.conf配置
| 1 2 3 4 5 | * soft nofile 2000000 * hard nofile 2000000 * soft nproc 2000000 * hard nproc 2000000 |
服務端配置如下:
/etc/sysctl.conf配置
| 1 2 3 | fs.file-max =?2000000 fs.nr_open =?2000000 net.ipv4.ip_local_port_range =?1024?65535 |
/etc/security/limits.conf配置
| 1 2 3 4 5 | * soft nofile 2000000 * hard nofile 2000000 * soft nproc 2000000 * hard nproc 2000000 |
Netty服務器
- 建立120萬個連接,不發送消息,輕輕松松達到。內存還剩14G未用。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [roocolobu ~]# ss -s; free -m Total:?1200231 (kernel?1200245) TCP:?1200006 (estab?1200002, closed?0, orphaned?0, synrecv?0, timewait?0/0), ports?4 Transport Total IP IPv6 *?1200245 - - RAW?0?0?0 UDP?1?1?0 TCP?1200006?1200006?0 INET?1200007?1200007?0 FRAG?0?0?0 total used free shared buffers cached Mem:?30074?15432?14641?0?9?254 -/+ buffers/cache:?15167?14906 Swap:?815?0?815 |
- 每分鐘給所有的120萬個websocket發送一條消息,消息內容為當前的服務器的時間。這里發送顯示是單線程發送,服務器發送完120萬個總用時15秒左右。
| 1 2 | 02:15:43.307?[pool-1-thread-1]?INFO?com.colobu.webtest.netty.WebServer$?-?send?msg?to?channels?for?c4453a26-bca6-42b6-b29b-43653767f9fc 02:15:57.190?[pool-1-thread-1]?INFO?com.colobu.webtest.netty.WebServer$?-?sent 1200000?channels?for?c4453a26-bca6-42b6-b29b-43653767f9fc |
發送時CPU使用率并不高,網絡帶寬占用基本在10M左右。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system-- usr sys idl wai hiq siq| read writ| recv send| in out | int csw 0?0?100?0?0?0| 0 0 | 60B 540B| 0 0 | 224 440 0?0?100?0?0?0| 0 0 | 60B 870B| 0 0 | 192 382 0?0?100?0?0?0| 0 0 | 59k 74k| 0 0 |2306 2166 2?7?87?0?0?4| 0 0 |4998k 6134k| 0 0 | 169k 140k 1?7?87?0?0?5| 0 0 |4996k 6132k| 0 0 | 174k 140k 1?7?87?0?0?5| 0 0 |4972k 6102k| 0 0 | 176k 140k 1?7?87?0?0?5| 0 0 |5095k 6253k| 0 0 | 178k 142k 2?7?87?0?0?5| 0 0 |5238k 6428k| 0 0 | 179k 144k 1?7?87?0?0?5| 0 24k|4611k 5660k| 0 0 | 166k 129k 1?7?87?0?0?5| 0 0 |5083k 6238k| 0 0 | 175k 142k 1?7?87?0?0?5| 0 0 |5277k 6477k| 0 0 | 179k 146k 1?7?87?0?0?5| 0 0 |5297k 6500k| 0 0 | 179k 146k 1?7?87?0?0?5| 0 0 |5383k 6607k| 0 0 | 180k 148k 1?7?87?0?0?5| 0 0 |5504k 6756k| 0 0 | 184k 152k 1?7?87?0?0?5| 0 48k|5584k 6854k| 0 0 | 183k 152k 1?7?87?0?0?5| 0 0 |5585k 6855k| 0 0 | 183k 153k 1?7?87?0?0?5| 0 0 |5589k 6859k| 0 0 | 184k 153k 1?5?91?0?0?3| 0 0 |4073k 4999k| 0 0 | 135k 110k 0?0?100?0?0?0| 0 32k| 60B 390B| 0 0 |4822 424 |
客戶端(一共20個,這里選取其中一個查看它的指標)。每個客戶端保持6萬個連接。每個消息從服務器發送到客戶端接收到總用時平均633毫秒,而且標準差很小,每個連接用時差不多。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Active WebSockets?for eb810c24-8565-43ea-bc27-9a0b2c910ca4 count =?60000 WebSocket Errors?for eb810c24-8565-43ea-bc27-9a0b2c910ca4 count =?0 -- Histograms ------------------------------------------------------------------ Message latency?for eb810c24-8565-43ea-bc27-9a0b2c910ca4 count =?693831 min =?627 max =?735 mean =?633.06 stddev =?9.61 median =?631.00 75% <=?633.00 95% <=?640.00 98% <=?651.00 99% <=?670.00 99.9% <=?735.00 -- Meters ---------------------------------------------------------------------- Message Rate?for eb810c24-8565-43ea-bc27-9a0b2c910ca4 count =?693832 mean rate =?32991.37 events/minute 1-minute rate =?60309.26 events/minute 5-minute rate =?53523.45 events/minute 15-minute rate =?31926.26 events/minute |
平均每個client的RPS = 1000, 總的RPS大約為 20000 requests /seconds.
latency平均值為633 ms,最長735 ms,最短627ms。
Spray服務器
- 建立120萬個連接,不發送消息,輕輕松松達到。它的內存相對較高,內存還剩7G。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [root@colobu ~]# ss -s; free -m Total:?1200234 (kernel?1200251) TCP:?1200006 (estab?1200002, closed?0, orphaned?0, synrecv?0, timewait?0/0), ports?4 Transport Total IP IPv6 *?1200251 - - RAW?0?0?0 UDP?1?1?0 TCP?1200006?1200006?0 INET?1200007?1200007?0 FRAG?0?0?0 total used free shared buffers cached Mem:?30074?22371?7703?0?10?259 -/+ buffers/cache:?22100?7973 Swap:?815?0?815 |
- 每分鐘給所有的120萬個websocket發送一條消息,消息內容為當前的服務器的時間。
CPU使用較高,發送很快,帶寬可以達到46M。群發完一次大約需要8秒左右。
| 1 2 | 05/22?04:42:57.569?INFO [ool-2-worker-15]?c.c.w.s.WebServer - send msg to workers 。for 8454e7d8-b8ca-4881-912b-6cdf3e6787bf 05/22?04:43:05.279?INFO [ool-2-worker-15]?c.c.w.s.WebServer - sent msg to workers?for 8454e7d8-b8ca-4881-912b-6cdf3e6787bf. current workers:?1200000 |
| 1 2 3 4 5 6 7 8 9 10 | ----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system-- usr sys idl wai hiq siq| read writ| recv send| in out | int csw 74?9?14?0?0?3| 0 24k|6330k 20M| 0 0 | 20k 1696 70?23?0?0?0?6| 0 64k| 11M 58M| 0 0 | 18k 2526 75?11?6?0?0?7| 0 0 |9362k 66M| 0 0 | 24k 11k 82?4?8?0?0?6| 0 0 | 11M 35M| 0 0 | 24k 10k 85?0?14?0?0?1| 0 0 |8334k 12M| 0 0 | 44k 415 84?0?15?0?0?1| 0 0 |9109k 16M| 0 0 | 36k 425 81?0?19?0?0?0| 0 24k| 919k 858k| 0 0 | 23k 629 76?0?23?0?0?0| 0 0 | 151k 185k| 0 0 | 18k 1075 |
客戶端(一共20個,這里選取其中一個查看它的指標)。每個客戶端保持6萬個連接。每個消息從服務器發送到客戶端接收到總用時平均1412毫秒,而且標準差較大,每個連接用時差別較大。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Active WebSockets for?6674c9d8-24c6-4e77-9fc0-58afabe7436f count =?60000 WebSocket Errors for?6674c9d8-24c6-4e77-9fc0-58afabe7436f count =?0 -- Histograms ------------------------------------------------------------------ Message latency for?6674c9d8-24c6-4e77-9fc0-58afabe7436f count =?454157 min =?716 max =?9297 mean =?1412.77 stddev =?1102.64 median =?991.00 75% <=?1449.00 95% <=?4136.00 98% <=?4951.00 99% <=?5308.00 99.9% <=?8854.00 -- Meters ---------------------------------------------------------------------- Message Rate for?6674c9d8-24c6-4e77-9fc0-58afabe7436f count =?454244 mean?rate =?18821.51 events/minute 1-minute?rate =?67705.18 events/minute 5-minute?rate =?49917.79 events/minute 15-minute?rate =?24355.57 events/minute |
Undertow
- 建立120萬個連接,不發送消息,輕輕松松達到。內存占用較少,還剩余11G內存。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [root@colobu ~]# ss -s; free -m Total:?1200234 (kernel?1200240) TCP:?1200006 (estab?1200002, closed?0, orphaned?0, synrecv?0, timewait?0/0), ports?4 Transport Total IP IPv6 *?1200240 - - RAW?0?0?0 UDP?1?1?0 TCP?1200006?1200006?0 INET?1200007?1200007?0 FRAG?0?0?0 total used free shared buffers cached Mem:?30074?18497?11576?0?10?286 -/+ buffers/cache:?18200?11873 Swap:?815?0?815 |
- 每分鐘給所有的120萬個websocket發送一條消息,消息內容為當前的服務器的時間。
群發玩一次大約需要15秒。
| 1 2 | 03:19:31.154?[pool-1-thread-1]?INFO?c.colobu.webtest.undertow.WebServer$?-?send?msg?to?channels?for?d9b450da-2631-42bc-a802-44285f63a62d 03:19:46.755?[pool-1-thread-1]?INFO?c.colobu.webtest.undertow.WebServer$?-?sent 1200000?channels?for?d9b450da-2631-42bc-a802-44285f63a62d |
客戶端(一共20個,這里選取其中一個查看它的指標)。每個客戶端保持6萬個連接。每個消息從服務器發送到客戶端接收到總用時平均672毫秒,而且標準差較小,每個連接用時差別不大。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | Active WebSockets?for b2e95e8d-b17a-4cfa-94d5-e70832034d4d count =?60000 WebSocket Errors?for b2e95e8d-b17a-4cfa-94d5-e70832034d4d count =?0 -- Histograms ------------------------------------------------------------------ Message latency?for b2e95e8d-b17a-4cfa-94d5-e70832034d4d count =?460800 min =?667 max =?781 mean =?672.12 stddev =?5.90 median =?671.00 75% <= 672.00 95% <= 678.00 98% <= 684.00 99% <= 690.00 99.9% <= 776.00 -- Meters ---------------------------------------------------------------------- Message Rate?for b2e95e8d-b17a-4cfa-94d5-e70832034d4d count =?460813 mean rate =?27065.85?events/minute 1-minute rate =?69271.67?events/minute 5-minute rate =?48641.78?events/minute 15-minute rate =?24128.67?events/minute Setup Rate?for b2e95e8d-b17a-4cfa-94d5-e70832034d4d |
node.js
node.js不是我要考慮的框架,列在這里只是作為參考。性能也不錯。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Active WebSockets for?537c7f0d-e58b-4996-b29e-098fe2682dcf count =?60000 WebSocket Errors for?537c7f0d-e58b-4996-b29e-098fe2682dcf count =?0 -- Histograms ------------------------------------------------------------------ Message latency for?537c7f0d-e58b-4996-b29e-098fe2682dcf count =?180000 min =?808 max =?847 mean =?812.10 stddev =?1.95 median =?812.00 75% <=?812.00 95% <=?813.00 98% <=?814.00 99% <=?815.00 99.9% <=?847.00 -- Meters ---------------------------------------------------------------------- Message Rate for?537c7f0d-e58b-4996-b29e-098fe2682dcf count =?180000 mean?rate =?7191.98 events/minute 1-minute?rate =?10372.33 events/minute 5-minute?rate =?16425.78 events/minute 15-minute?rate =?9080.53 events/minute |
參考文檔
總結
以上是生活随笔為你收集整理的使用四种框架分别实现百万websocket常连接的服务器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过完整示例来理解如何使用 epoll
- 下一篇: shell 实例收集