最近一直在进行手机推送服务器的研发,代码采用Java实现。netty作为一个优秀基于NIO的客户、服务器端编程框架,当然是首选的。server的研发必然是充满血与泪的,除了要熟悉Java的NIO,还得对tcp的封包、拆包,协议的设计、对象的序列化;压缩方式的选择等等之外,对Linux内核的调优是一件绝对比前面任何一个都要复杂。由于采用Java开发,由于Java的内存墙等诸多因素的影响,服务器支持的长连接我规划在一个server大概50W左右。可能对于一些C、C++服务器编程的码农来说这个链接数还真算不了什么,但是对于Java来说还是相当不错了。关于Java的内存墙方面的东西此处有一篇好文章推荐给大家,链接如下:http://ifeve.com/jvm-performance-optimization-java-scalability-5。既然一台服务器要支持50W的长连接 ,如果不对Linux内核进行调优,server的支撑的链接数和半链接的回收都会存在较大的问题。
前面YY了很多了,OK那我们下面进入正题。调优,顾名思义那就是碰见问题才调优,下面就是我碰见的问题和解决方案:
1.要支持50W的长连接,TCP栈对内存的使用需要调大。tcp_rmem和tcp_wmem我们都采用缺省值8KB,那么一个tcp链接,需要占用的内存大概为16KB。
一个简单的计算如下:接近8.0GB TCP内存能容纳的连接数,约为 8000000KB/16KB = 50万。所以下面我分别配置成3G,8G,12G,一定注意单位是page,大小一般为4KB。
1 | >net.ipv4.tcp_mem = 786432 2097152 3145728 |
2.由于手机网络不是很稳定,会经常出现网络闪断的情况,server端如何快速回收链接。tcp是可靠的传输协议,链接的关闭也需要4次握手,可能很多人对链接建立的3次握手非常熟悉 ,但是对正常关闭的4次握手却不熟悉。下面我附带一张图:
此处我们思考的时候,把server和client的对调。当server端发现client端很久没有心跳,那我就得将该链接回收。由于Client端已经不可达,那server端链接会处在FIN-WAIT-1。这个时候该tcp链接已经是一个孤儿链接,也就是说它已经不属于任何一个进程。在不可达的情况下,它会默认发送9次,重试8次。由于该状态是非常占用资源的最大可占用64KB。所以我们得尽快让这个链接从FIN-WAIT-1中解放出来,所以设置如下:
1 | >net.ipv4.tcp_orphan_retries=1 |
3.由于手机网络不是很稳定,可能会出现数据包的乱跳;最主要的是NAT环境如LVS,很多公司都用LVS做负载均衡,通常是前面一台LVS,后面多台后端服务器,以NAT方式构建,当请求到达LVS后,它修改地址数据后便转发给后端服务器,但不会修改时间戳数据,对于后端服务器来说,请求的源地址就是LVS的地址,加上端口会复用,所以从后端服务器的角度看,原本不同客户端的请求经过LVS的转发,就可能会被认为是同一个连接,加之不同客户端的时间可能不一致,所以就会出现时间戳错乱的现象,于是后面的数据包就被丢弃了,具体的表现通常是是客户端明明发送的SYN,但服务端就是不响应ACK。就会出现一部分手机就是链接不上。
1 | #是否启用时间戳选项,该选项会影响net.ipv4.tcp_tw_reuse,默认开启。一般我们为了安全我们不关闭该选项。 |
PS:由于个人理解有限,如果不正确的请大家指正,以免误导他人,如果不明白原理,内核参数最好别动。
参考文章