使用四種框架分別實現1百萬websocket常連接的服務器

jopen 9年前發布 | 349K 次閱讀 WebSocket 開發 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。

</blockquote>

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

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

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

服務器的參數調優

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

TCP/IP參數配置

修改文件/etc/sysctl.conf,配置網絡參數。

</tr> </tbody> </table>

數值根據需求進行調整。更多的參數可以看以前整理的一篇文章: 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
    3. </ol> </li>

    4. 進程最大打開文件描述符數
      使用ulimit -n查看當前設置。使用ulimit -n 1000000進行臨時性設置。
      要想永久生效,你可以修改/etc/security/limits.conf文件,增加下面的行:
    5. </ul>

               
                
net.ipv4.tcp_wmem = 4096 87380 4161536
net.ipv4.tcp_rmem = 4096 87380 4161536
net.ipv4.tcp_mem = 786432 2097152 3145728


                * hard    nofile      1000000
            </div>
            <div>
                * soft    nofile      1000000
            </div>
            <div>
                root      hard    nofile      1000000
            </div>
            <div>
                root      soft    nofile      1000000
            </div>

</pre></td> </tr> </tbody> </table>

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

查看當前系統使用的打開文件描述符數,可以使用下面的命令:

</tr> </tbody> </table>

其中第一個數表示當前系統已分配使用的打開文件描述符數,第二個數為分配后已釋放的(目前已不再使用),第三個數等于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
  • </ul>

    應用運行時調優

    1. Java 應用內存調優
      服務器使用12G內存,吞吐率優先的垃圾回收器:
    2. </ol>

               
                
1
2
               
                
[root@localhost ~]# cat /proc/sys/fs/file-nr
1632 0 1513506

</tr> </tbody> </table>

  1. V8引擎
  2. </ol>

               
                
JAVA_OPTS="-Xms12G -Xmx12G -Xss1M -XX:+UseParallelGC"

</tr> </tbody> </table>

OutOfMemory Killer

如果服務器本身內存不大,比如8G,在不到100萬連接的情況下,你的服務器進程有可能出現"Killed"的問題。 運行dmesg可以看到

               
                
node --nouse-idle-notification --expose-gc --max-new-space-size=1024 --max-new-space-size=2048 --max-old-space-size=8192 ./webserver.js

</tr> </tbody> </table>

這是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個連接。

               
                
Out of memory: Kill process 10375 (java) score 59 or sacrifice child

</tr> </tbody> </table>

修改/etc/sysctl.conf文件:

               
                
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

</tr> </tbody> </table>

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

服務器測試

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

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

               
                
net.ipv4.ip_local_port_range = 1024 65535

</tr> </tbody> </table>

/etc/security/limits.conf配置

               
                
fs.file-max = 2000000
fs.nr_open = 2000000
net.ipv4.ip_local_port_range = 1024 65535


  •                 * soft    nofile      2000000
                </div>
                <div>
                    * hard    nofile      2000000
                </div>
                <div>
    
                </div>
                <div>
                    * soft nproc 2000000
                </div>
                <div>
                    * hard nproc 2000000
                </div>
    

    </pre></td> </tr> </tbody> </table>

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

    </tr> </tbody> </table>

    /etc/security/limits.conf配置

                   
                    
    fs.file-max = 2000000
    fs.nr_open = 2000000
    net.ipv4.ip_local_port_range = 1024 65535

  •                
                    
    1
    2
    3
    4
    5

                    * soft    nofile      2000000
                </div>
                <div>
                    * hard    nofile      2000000
                </div>
                <div>
    
                </div>
                <div>
                    * soft nproc 2000000
                </div>
                <div>
                    * hard nproc 2000000
                </div>
    

    </pre></td> </tr> </tbody> </table>

    Netty服務器

    • 建立120萬個連接,不發送消息,輕輕松松達到。內存還剩14G未用。
    • </ul>


      [roocolobu ~]# ss -s; free -m
      Total: 1200231 (kernel 1200245)
      TCP: 1200006 (estab 1200002, closed 0, orphaned 0, synrecv 0, timewait 0/0), ports 4

                  </div>
                  <div>
                      Transport Total     IP        IPv6
                  </div>
                  <div>
                      * 1200245 -         -
                  </div>
                  <div>
                      RAW 0 0 0
                  </div>
                  <div>
                      UDP 1 1 0
                  </div>
                  <div>
                      TCP 1200006 1200006 0
                  </div>
                  <div>
                      INET 1200007 1200007 0
                  </div>
                  <div>
                      FRAG 0 0 0
                  </div>
                  <div>
      
                  </div>
                  <div>
                      total       used       free     shared    buffers     cached
                  </div>
                  <div>
                      Mem: 30074 15432 14641 0 9 254
                  </div>
                  <div>
                      -/+ buffers/cache: 15167 14906
                  </div>
                  <div>
                      Swap: 815 0 815
                  </div>
      

      </pre></td> </tr> </tbody> </table>

      • 每分鐘給所有的120萬個websocket發送一條消息,消息內容為當前的服務器的時間。這里發送顯示是單線程發送,服務器發送完120萬個總用時15秒左右。
      • </ul>

        </tr> </tbody> </table>

        發送時CPU使用率并不高,網絡帶寬占用基本在10M左右。

                       
                        
        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

        </tr> </tbody> </table>

        客戶端(一共20個,這里選取其中一個查看它的指標)。每個客戶端保持6萬個連接。每個消息從服務器發送到客戶端接收到總用時平均633毫秒,而且標準差很小,每個連接用時差不多。

                       
                        
        ----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


        Active WebSockets for eb810c24-8565-43ea-bc27-9a0b2c910ca4
        count = 60000
        WebSocket Errors for eb810c24-8565-43ea-bc27-9a0b2c910ca4
        count = 0

                    </div>
                    <div>
                        -- Histograms ------------------------------------------------------------------
                    </div>
                    <div>
                        Message latency for eb810c24-8565-43ea-bc27-9a0b2c910ca4
                    </div>
                    <div>
                        count = 693831
                    </div>
                    <div>
                        min = 627
                    </div>
                    <div>
                        max = 735
                    </div>
                    <div>
                        mean = 633.06
                    </div>
                    <div>
                        stddev = 9.61
                    </div>
                    <div>
                        median = 631.00
                    </div>
                    <div>
                        75% <= 633.00
                    </div>
                    <div>
                        95% <= 640.00
                    </div>
                    <div>
                        98% <= 651.00
                    </div>
                    <div>
                        99% <= 670.00
                    </div>
                    <div>
                        99.9% <= 735.00
                    </div>
                    <div>
        
                    </div>
                    <div>
                        -- Meters ----------------------------------------------------------------------
                    </div>
                    <div>
                        Message Rate for eb810c24-8565-43ea-bc27-9a0b2c910ca4
                    </div>
                    <div>
                        count = 693832
                    </div>
                    <div>
                        mean rate = 32991.37 events/minute
                    </div>
                    <div>
                        1-minute rate = 60309.26 events/minute
                    </div>
                    <div>
                        5-minute rate = 53523.45 events/minute
                    </div>
                    <div>
                        15-minute rate = 31926.26 events/minute
                    </div>
        

        </pre></td> </tr> </tbody> </table>

        Spray服務器

        • 建立120萬個連接,不發送消息,輕輕松松達到。它的內存相對較高,內存還剩7G。
        • </ul>


          [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

                      </div>
                      <div>
                          Transport Total     IP        IPv6
                      </div>
                      <div>
                          * 1200251 -         -
                      </div>
                      <div>
                          RAW 0 0 0
                      </div>
                      <div>
                          UDP 1 1 0
                      </div>
                      <div>
                          TCP 1200006 1200006 0
                      </div>
                      <div>
                          INET 1200007 1200007 0
                      </div>
                      <div>
                          FRAG 0 0 0
                      </div>
                      <div>
          
                      </div>
                      <div>
                          total       used       free     shared    buffers     cached
                      </div>
                      <div>
                          Mem: 30074 22371 7703 0 10 259
                      </div>
                      <div>
                          -/+ buffers/cache: 22100 7973
                      </div>
                      <div>
                          Swap: 815 0 815
                      </div>
          

          </pre></td> </tr> </tbody> </table>

  • sesese色