著名的 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,配置网络参数。
net.ipv4.tcp_wmem = 4096873804161536
net.ipv4.tcp_rmem = 4096873804161536
net.ipv4.tcp_mem = 78643220971523145728
数值根据需求进行调整。更多的参数可以看以前整理的一篇文章: 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文件,增加下面的行:
* hard nofile1000000
* soft nofile1000000
root hard nofile1000000
root soft nofile1000000
还有一点要注意的就是hard limit不能大于/proc/sys/fs/nr_open,因此有时你也需要修改nr_open的值。
执行echo 2000000 > /proc/sys/fs/nr_open
查看当前系统使用的打开文件描述符数,可以使用下面的命令:
[root@localhost ~]# cat /proc/sys/fs/file-nr
163201513506
其中第一个数表示当前系统已分配使用的打开文件描述符数,第二个数为分配后已释放的(目前已不再使用),第三个数等于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
应用运行时调优
-
Java 应用内存调优
服务器使用12G内存,吞吐率优先的垃圾回收器:
2. V8引擎
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可以看到
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个连接。
ifconfig eth0:0192.168.77.10netmask255.255.255.0up
ifconfig eth0:1192.168.77.11netmask255.255.255.0up
ifconfig eth0:2192.168.77.12netmask255.255.255.0up
ifconfig eth0:3192.168.77.13netmask255.255.255.0up
ifconfig eth0:4192.168.77.14netmask255.255.255.0up
ifconfig eth0:5192.168.77.15netmask255.255.255.0up
ifconfig eth0:6192.168.77.16netmask255.255.255.0up
ifconfig eth0:7192.168.77.17netmask255.255.255.0up
ifconfig eth0:8192.168.77.18netmask255.255.255.0up
ifconfig eth0:9192.168.77.19netmask255.255.255.0up
ifconfig eth0:10192.168.77.20netmask255.255.255.0up
ifconfig eth0:11192.168.77.21netmask255.255.255.0up
ifconfig eth0:12192.168.77.22netmask255.255.255.0up
ifconfig eth0:13192.168.77.23netmask255.255.255.0up
ifconfig eth0:14192.168.77.24netmask255.255.255.0up
ifconfig eth0:15192.168.77.25netmask255.255.255.0up
ifconfig eth0:16192.168.77.26netmask255.255.255.0up
ifconfig eth0:17192.168.77.27netmask255.255.255.0up
ifconfig eth0:18192.168.77.28netmask255.255.255.0up
修改/etc/sysctl.conf文件:
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配置
fs.file-max = 2000000
fs.nr_open = 2000000
net.ipv4.ip_local_port_range = 102465535
/etc/security/limits.conf配置
* soft nofile2000000
* hard nofile2000000
* soft nproc2000000
* hard nproc2000000
服务端配置如下:
/etc/sysctl.conf配置
fs.file-max = 2000000
fs.nr_open = 2000000
net.ipv4.ip_local_port_range = 102465535
/etc/security/limits.conf配置
* soft nofile2000000
* hard nofile2000000
* soft nproc2000000
* hard nproc2000000
Netty服务器
-
建立120万个连接,不发送消息,轻轻松松达到。内存还剩14G未用。
[roocolobu ~]# ss -s; free -m
Total: 1200231(kernel1200245)
TCP: 1200006(estab1200002,closed0,orphaned0,synrecv0,timewait0/0),ports4
Transport Total IP IPv6
* 1200245 - -
RAW000
UDP110
TCP120000612000060
INET120000712000070
FRAG000
total used free shared buffers cached
Mem: 30074154321464109254
-/+ buffers/cache: 1516714906
Swap: 8150815
每分钟给所有的120万个websocket发送一条消息,消息内容为当前的服务器的时间。这里发送显示是单线程发送,服务器发送完120万个总用时15秒左右。
02:15:43.307[pool-1-thread-1]INFO com.colobu.webtest.netty.WebServer$ - send msg tochannels forc4453a26-bca6-42b6-b29b-43653767f9fc
02:15:57.190[pool-1-thread-1]INFO com.colobu.webtest.netty.WebServer$ - sent1200000channels forc4453a26-bca6-42b6-b29b-43653767f9fc
发送时CPU使用率并不高,网络带宽占用基本在10M左右。
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| readwrit| recv send| inout | intcsw
00100000| 00 | 60B540B| 00 | 224440
00100000| 00 | 60B870B| 00 | 192382
00100000| 00 | 59k74k| 00 |23062166
2787004| 00 |4998k6134k| 00 | 169k140k
1787005| 00 |4996k6132k| 00 | 174k140k
1787005| 00 |4972k6102k| 00 | 176k140k
1787005| 00 |5095k6253k| 00 | 178k142k
2787005| 00 |5238k6428k| 00 | 179k144k
1787005| 024k|4611k5660k| 00 | 166k129k
1787005| 00 |5083k6238k| 00 | 175k142k
1787005| 00 |5277k6477k| 00 | 179k146k
1787005| 00 |5297k6500k| 00 | 179k146k
1787005| 00 |5383k6607k| 00 | 180k148k
1787005| 00 |5504k6756k| 00 | 184k152k
1787005| 048k|5584k6854k| 00 | 183k152k
1787005| 00 |5585k6855k| 00 | 183k153k
1787005| 00 |5589k6859k| 00 | 184k153k
1591003| 00 |4073k4999k| 00 | 135k110k
00100000| 032k| 60B390B| 00 |4822424
客户端(一共20个,这里选取其中一个查看它的指标)。每个客户端保持6万个连接。每个消息从服务器发送到客户端接收到总用时平均633毫秒,而且标准差很小,每个连接用时差不多。
Active WebSockets foreb810c24-8565-43ea-bc27-9a0b2c910ca4
count = 60000
WebSocket Errors foreb810c24-8565-43ea-bc27-9a0b2c910ca4
count = 0
-- Histograms ------------------------------------------------------------------
Message latency foreb810c24-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 foreb810c24-8565-43ea-bc27-9a0b2c910ca4
count = 693832
mean rate = 32991.37events/minute
1-minute rate = 60309.26events/minute
5-minute rate = 53523.45events/minute
15-minute rate = 31926.26events/minute
平均每个client的RPS = 1000, 总的RPS大约为 20000 requests /seconds.
latency平均值为633 ms,最长735 ms,最短627ms。
Spray服务器
-
建立120万个连接,不发送消息,轻轻松松达到。它的内存相对较高,内存还剩7G。
# ss -s; free -m
Total: 1200234(kernel1200251)
TCP: 1200006(estab1200002,closed0,orphaned0,synrecv0,timewait0/0),ports4
Transport Total IP IPv6
* 1200251 - -
RAW000
UDP110
TCP120000612000060
INET120000712000070
FRAG000
total used free shared buffers cached
Mem: 30074223717703010259
-/+ buffers/cache: 221007973
Swap: 8150815
每分钟给所有的120万个websocket发送一条消息,消息内容为当前的服务器的时间。
CPU使用较高,发送很快,带宽可以达到46M。群发完一次大约需要8秒左右。
05/2204:42:57.569INFO[ool-2-worker-15]c.c.w.s.WebServer - send msg toworkers。for8454e7d8-b8ca-4881-912b-6cdf3e6787bf
05/2204:43:05.279INFO[ool-2-worker-15]c.c.w.s.WebServer - sent msg toworkers for8454e7d8-b8ca-4881-912b-6cdf3e6787bf.current workers: 1200000
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| readwrit| recv send| inout | intcsw
74914003| 024k|6330k20M| 00 | 20k1696
70230006| 064k| 11M58M| 00 | 18k2526
75116007| 00 |9362k66M| 00 | 24k11k
8248006| 00 | 11M35M| 00 | 24k10k
85014001| 00 |8334k12M| 00 | 44k415
84015001| 00 |9109k16M| 00 | 36k425
81019000| 024k| 919k858k| 00 | 23k629
76023000| 00 | 151k185k| 00 | 18k1075
客户端(一共20个,这里选取其中一个查看它的指标)。每个客户端保持6万个连接。每个消息从服务器发送到客户端接收到总用时平均1412毫秒,而且标准差较大,每个连接用时差别较大。
Active WebSockets for6674c9d8-24c6-4e77-9fc0-58afabe7436f
count = 60000
WebSocket Errors for6674c9d8-24c6-4e77-9fc0-58afabe7436f
count = 0
-- Histograms ------------------------------------------------------------------
Message latency for6674c9d8-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 for6674c9d8-24c6-4e77-9fc0-58afabe7436f
count = 454244
mean rate = 18821.51events/minute
1-minute rate = 67705.18events/minute
5-minute rate = 49917.79events/minute
15-minute rate = 24355.57events/minute
Undertow
-
建立120万个连接,不发送消息,轻轻松松达到。内存占用较少,还剩余11G内存。
# ss -s; free -m
Total: 1200234(kernel1200240)
TCP: 1200006(estab1200002,closed0,orphaned0,synrecv0,timewait0/0),ports4
Transport Total IP IPv6
* 1200240 - -
RAW000
UDP110
TCP120000612000060
INET120000712000070
FRAG000
total used free shared buffers cached
Mem: 300741849711576010286
-/+ buffers/cache: 1820011873
Swap: 8150815
每分钟给所有的120万个websocket发送一条消息,消息内容为当前的服务器的时间。
群发玩一次大约需要15秒。
03:19:31.154[pool-1-thread-1]INFOc.colobu.webtest.undertow.WebServer$ - send msg tochannels ford9b450da-2631-42bc-a802-44285f63a62d
03:19:46.755[pool-1-thread-1]INFOc.colobu.webtest.undertow.WebServer$ - sent1200000channels ford9b450da-2631-42bc-a802-44285f63a62d
客户端(一共20个,这里选取其中一个查看它的指标)。每个客户端保持6万个连接。每个消息从服务器发送到客户端接收到总用时平均672毫秒,而且标准差较小,每个连接用时差别不大。
Active WebSockets forb2e95e8d-b17a-4cfa-94d5-e70832034d4d
count = 60000
WebSocket Errors forb2e95e8d-b17a-4cfa-94d5-e70832034d4d
count = 0
-- Histograms ------------------------------------------------------------------
Message latency forb2e95e8d-b17a-4cfa-94d5-e70832034d4d
count = 460800
min = 667
max = 781
mean = 672.12
stddev = 5.90
median = 671.00
75%
95%
98%
99%
99.9%
-- Meters ----------------------------------------------------------------------
Message Rate forb2e95e8d-b17a-4cfa-94d5-e70832034d4d
count = 460813
mean rate = 27065.85events/minute
1-minute rate = 69271.67events/minute
5-minute rate = 48641.78events/minute
15-minute rate = 24128.67events/minute
Setup Rate forb2e95e8d-b17a-4cfa-94d5-e70832034d4d
node.js
node.js不是我要考虑的框架,列在这里只是作为参考。性能也不错。
Active WebSockets for537c7f0d-e58b-4996-b29e-098fe2682dcf
count = 60000
WebSocket Errors for537c7f0d-e58b-4996-b29e-098fe2682dcf
count = 0
-- Histograms ------------------------------------------------------------------
Message latency for537c7f0d-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 for537c7f0d-e58b-4996-b29e-098fe2682dcf
count = 180000
mean rate = 7191.98events/minute
1-minute rate = 10372.33events/minute
5-minute rate = 16425.78events/minute
15-minute rate = 9080.53events/minute
相关推荐
3. **WebSocket协议**:WebSocket协议定义了一种在客户端和服务器之间建立长连接的方法,它在HTTP握手后升级为WebSocket连接,从而实现了双向通信。协议规定了帧格式、错误处理等细节。 4. **帧处理**:在MFC ...
WebSocketSharp库中的客户端部分允许开发者创建WebSocket连接到任何支持WebSocket的服务器。通过简单的API调用,你可以初始化一个WebSocket对象,设置必要的配置(如握手头部信息),然后连接到指定的URL。一旦连接...
2. **STOMP集成**:Spring的WebSocket支持STOMP协议,我们可以定义订阅和发布消息的路由,使得客户端可以通过WebSocket连接订阅特定主题,然后服务器可以在这些主题上发布消息。 3. **数据库设计**:为了实现私聊和...
WebSocketSharp 是一个 C# 实现的 WebSocket 客户端和服务器框架,它为开发者提供了在 .NET 应用程序中实现 WebSocket 协议的强大工具。WebSocketSharp 允许你轻松地创建可以双向通信的实时应用,例如在线游戏、实时...
WebSocket是一种在客户端和服务器之间建立持久连接的协议,它允许双方进行双向通信,即服务器可以主动向客户端推送数据。在Web开发中,WebSocket为实时应用提供了便利,比如在线聊天、实时股票报价、游戏等场景。...
html5协议websocket与java服务器的一个简单聊天应用,服务器使用了mina框架,代码中对websocket数据交互协议进行了注释说明,MinaEncoder类与MinaDecoder类对应数据的编码与解码。
WebSocket是一种在客户端和服务器之间建立持久连接的协议,它允许双方进行全双工通信,即数据可以在两个方向上同时传输,极大地提高了实时性。在Web应用中,WebSocket为开发者提供了实时交互的能力,常用于在线聊天...
WebSocket是一种在Web应用中实现全双工通信的协议,它允许服务器和客户端之间进行实时、低延迟的数据交换。WebSocket协议是HTML5的一个重要特性,它弥补了HTTP协议在长连接和双向通信上的不足,为实时Web应用提供了...
WebSocket是一种在客户端与服务器之间建立持久连接的协议,它允许双方进行全双工通信,即数据可以在两个方向上同时传输,极大地提高了实时性。在本项目中,"简单实现了websocket功能:websocket客户端、winform...
Netty 是一个高性能、异步事件驱动的网络应用程序框架,常用于开发高效的服务器和客户端。在本项目中,“netty5实现的websocket服务器”利用了Netty 5版本的特性来构建WebSocket服务端,旨在为Android和iOS的APP提供...
3. **实现WebSocket连接**:在MFC的某个类中,例如对话框类,创建WebSocket连接的方法。初始化连接,发送HTTP Upgrade请求,接收并解析服务器的响应,完成握手过程。 4. **处理WebSocket消息**:实现消息发送和接收...
WebSocket是一种在客户端和服务器之间建立持久连接的协议,它允许双方进行全双工通信,即数据可以在两个方向上同时传输,极大地提高了实时性。在Web应用中,WebSocket为需要实时交互的服务提供了强大的支持,比如...
在WebSocket服务器中,我们需要实现`WebSocketServerProtocolHandler`来处理WebSocket升级请求和管理WebSocket连接。 2. **WebSocketFrameDecoder/Encoder**:这些处理器用于解码和编码WebSocket帧。WebSocket帧...
WebSocket是一种在客户端和服务器之间建立长时间连接的协议,允许双向通信,即服务器可以主动向客户端推送数据,而不仅仅是响应客户端的请求。在Spring Boot中,可以通过`spring-websocket`模块实现WebSocket功能。...
9. **安全性与认证**: WebSocket连接可以通过SSL/TLS加密,同时也可以集成到已有的身份验证和授权框架中,以确保安全性。 10. **性能优化**: 考虑到WebSocket连接长时间保持,需要关注资源管理,如心跳机制保持连接...
- 实现用户输入框和发送按钮,当用户输入消息并点击发送时,通过WebSocket连接向服务器发送消息 - 监听WebSocket的`message`事件,接收服务器发送的聊天消息并显示在页面上 6. **安全性和会话管理**: - 可以...
ChatClient类可以演示如何在C#中建立WebSocket连接,发送文本或二进制数据,并处理服务器的响应。 在C#中开发WebSocket服务器时,需要注意以下几点: - **WebSocket握手**:WebSocket连接的建立需要HTTP Upgrade头...
为了实现多聊天室的功能,可以为每个聊天室创建一个独立的Websocket连接池。用户加入或离开聊天室时,只需要将他们的连接添加或移除到相应的连接池即可。同时,每个聊天室的消息应该独立管理,确保不同聊天室之间的...
这个库提供了处理WebSocket连接、接收和发送数据的基础框架。 2. **大客户端**:大客户端通常是一个网页应用,它需要创建WebSocket连接到服务器,监听服务器的响应,并在需要时发送"jump"命令。这可以通过...