基于Netty的Comet测试及调优

 

测试环境:

 

服务器 1

  OS: Red Hat Enterprise Linux Server release 5.4

  CPU: 4xIntel(R) Xeon(R) CPU E5450 @ 3.00GHz

  MEMORY:4G

客户端5台,配置同服务器

上述的测试环境都是采用的虚拟机,而且虚拟机性能不是很好,所以我主要是测连接数,其中有涉及到性能的地方不具可参考性。另外Comet需要关注的是它能支撑的连接数个数,而并非qps,当然qps也是我们需要考虑的性能点之一。

 

 

服务端环境配置

 

JDK版本1.6.0_18

Netty版本3.2.2

 

sudo vi /etc/sysctl.conf

加入如下配置:

 

net.core.somaxconn = 2048
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.somaxconn = 10000
net.core.netdev_max_backlog = 20000

net.ipv4.tcp_rmem = 7168 11264 16777216
net.ipv4.tcp_wmem = 7168 11264 16777216
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_max_orphans = 131072
net.ipv4.tcp_max_tw_buckets=180000
fs.file-max = 1000000
 

编辑完成后执行如下命令让配置生效

sudo /sbin/sysctl -p

 

sudo vi /etc/security/limits.conf 

找到hard nofile和soft nofile配置信息,修改为如下:

*  hard nofile 1000000
*  soft nofile 1000000

 

客户端环境配置

 

由于系统默认参数,自动分配的端口数有限,是从32768到61000,所以我们需要更改客户端/etc/sysctl.conf的参数:

 

net.ipv4.ip_local_port_range = 1024 65535

 

编辑完成后执行如下命令让配置生效

sudo /sbin/sysctl -p

客户端程序是基于libevent写的一个测试程序,不断的建立新的连接请求。客户端与服务端需要建立大量的socket,所以我们需要调速一下最大文件描述符。客户端,需要创建六万多个socket,我设置最大为十万,在 /etc/security/limits.conf 中添加

 

*  hard nofile 1000000
*  soft nofile 1000000

 

 

java的内存调优

 

对于java的内存管理,肯定离不开GC的调优。长连接是一个长期占用内存的一种应用,直到连接中断才会被释放。如果按照传统的方式来分配Eden、S0、S1是存在问题的,如果按照默认的配置Eden空间大于Survivor,那么在进行GC的时候由于长连接所占用的内存并不释放,导致Survivor空间无法容纳,收集器会将无法容纳的数据丢入到Old区。

 

为了验证这个情况作了一个测试,java的配置参数如下:

-server -Xms1G -Xmx1G -XX:PermSize=64m -XX:+UseParallelGC -XX:+UseParallelGC -verbose:gc -XX:+PrintGCDetails

运行之后jstat日志信息如下,关注其中的红色部分,进行minor GC的时候由于S1的空间不够容纳,收集器将无法容纳的数据丢入到Old区,如果持续下去,会频繁进行Full GC,这是我们不想看到的。

 

 

  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT  

  0.00   0.00  98.00   0.00   8.78      0    0.000     0    0.000    0.000

  0.00  99.93   9.94   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93   9.94   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93   9.94   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93   9.94   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93   9.94   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93   9.94   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  13.84   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  25.08   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  37.62   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  48.86   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  51.46   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  62.70   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  66.60   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  77.84   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  79.14   9.20   8.78      1    0.195     0    0.000    0.195

  0.00  99.93  90.39   9.20   8.78      1    0.195     0    0.000    0.195

 99.93   0.00   7.17  26.40   8.78      2    0.511     0    0.000    0.511

 

实际在线上的应用场景,连接每隔一段时间就会关闭。那么仍到Old区的内存几乎每次都能够完全回收,但是交给Old区来回收实在是太重了,如何避免频繁的Full GC是我们需要关注的。

 

接下来另外的一种配置方式,淘宝韩彰分享过这种做法。加大Survivor区,让Survivor区的空间和Eden空间一样大,这样可以保证在minor GC的时候数据不会放入到Old区。下一次执行minor GC的时候,先前的Survivor区的数据可以完全回收掉。

PS:前提是minor GC周期必须大于一个连接的生命周期,比如一个连接每隔30s会关闭,那么两次minor GC的时间间隔必须大于30s。

 

测试

 

最后,为了测试Netty支持最大的连接数,我们配置如下:

 

-server -Xms4G -Xmx4G -XX:NewSize=3584m -XX:PermSize=64m -XX:SurvivorRatio=1 -XX:+UseParallelGC -XX:-UseAdaptiveSizePolicy

Eden、S0、S1各1G,Old512M,UseParallelGC回收机制,加入UseAdaptiveSizePolicy不允许回收器自动调整Eden和Survivor区大小。

 

启动服务端的comet进程后,初始的对象占用 167.26MB。然后启动客户端连接到服务端,每个客户端建立6w个连接。当客户端的连接数达到 6w左右的时候就不能再连接了。

 

执行server端的dmesg命令,发现存在如下信息:

 

ip_conntrack: table full, dropping packet.

再执行如下命令

sudo cat /proc/sys/net/ipv4/ip_conntrack_max

是65322,看来是最大连接跟踪值开得较小了。

 

接下来执行sudo vi /etc/sysctl.conf 进行编辑,加入如下配置:

 

net.ipv4.ip_conntrack_max = 1000000   //设置最大连接跟踪值
net.ipv4.netfilter.ip_conntrack_max=1000000 
net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait=120 
net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait=60 
net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait=120

再从新进行测试,现在连接已经可以突破6w了。

 

 

当生成12万个连接后,内存占用1194.75MB。

也就是说每个连接占用8.8k  = (1194.75-167.26)*1024/120000

这只是纯粹的连接,还没有涉及到数据的传输,一旦涉及到数据的传输,每个连接占用的内存肯定会超过8.8k

 

当超过12万之后java进程会进行一次minor GC,耗时1.11秒(用的是虚拟机,线上环境应该会好上几倍)

具体信息如下:

398.120: [GC [PSYoungGen: 1223424K->504705K(2446720K)] 1223424K->504705K(2971008K), 1.1087630 secs] [Times: user=2.45 sys=0.81, real=1.11 secs] 

 

持续增加连接,当增加到26万个连接系统仍然表现稳定。

 

当连接数达到27万左右的时候java进程进行了第二次minor GC,由于是一直保持着的长连接,数据是不会被释放的,此时杯具的事情发生了:

 

下面是第二次minor GC的信息

 

  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT

  0.00  41.26 100.00   0.00   8.81      1    1.109     0    0.000    1.109

  5.51  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 21.86  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 40.32  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 56.11  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 67.12  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 78.00  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 80.87  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 80.92  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 80.92  41.26 100.00   0.00   8.81      2    1.109     0    0.000    1.109

 .....

 86.05   0.00  29.31   0.00   8.90      2  588.199     0    0.000  588.199

 

最终耗时587s,不可想象啊!

 

1777.982: [GC [PSYoungGen: 1728129K->1052673K(2446720K)] 1728129K->1052673K(2971008K), 587.0906510 secs]
 

结论:

上述测试场景下最多能够支撑26万左右的长连接。超过26万就会出现上述第二次minor GC的情况,可用性无法得到保障。

 

在真实场景下,并不是真正的保持长连接状态,当连接了一定的时间,连接会关闭掉。假如设定每个连接每隔30s会关闭,那么你只需要保证minior GC的周期大于30s就可以避免上述第二次minor GC的情况。因为放入到S1中的数据一定能在下一个GC周期内全部释放掉。

 

目前的测试场景还不能完全反映和还原线上真实场景,真实场景下有着各种复杂的情况,数据传输对内存的占用也会不一样。线上的连接数肯定要打折扣。

 

如果 minor GC 的周期大于30s,保守估计一个netty进程大概能支持10万左右长连接(1G的Eden空间的能容纳12万个连接)。

 

另外,感谢淘宝 李子 在整个测试过程中予以的帮助。


你可能感兴趣的:(Web,Technology)