生产环境中一台2核4G的linux服务器TCP连接数时常保持在5-7w间徘徊,查看日志每秒的请求数也就100-200,怎么会产生这么大的TCP连接数。检查了下客户端上行的HTTP协议,Connection 头字段是Keep-Alive,并且客户端在请求完之后没有立即关闭连接。而服务端的设计也是根据客户端来的,客户端上行如果Connection:Keep-Alive,服务端是不会主动关闭连接的。在客户端与服务端交互比较频繁的时候,这样的设计还是比较合理的,可以减少TCP的重复握手。显然如果只交互一次,就没有这个必要了。我们的生产环境就属于这种情况。客户端在请求响应完之后就得立即释放连接,上代码:
public static void send(request req){ InputStream input=null; ByteArrayOutputStream out=null; HttpClient client=new DefaultHttpClient(); try{ HttpPost post=new HttpPost("http://ip:port"); post.setHeader("User-Agent", "Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; Nexus One Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"); post.setHeader("Expect", "100-continue"); post.setHeader("Client_IP", "ip"); post.addHeader("Connection","close"); HttpEntity entity=new ByteArrayEntity(encoder.encodeXip(req)); post.setEntity(entity); HttpResponse resp=client.execute(post); HttpEntity content = resp.getEntity(); input=content.getContent(); out=new ByteArrayOutputStream(); byte by[]=new byte[1024]; int len=0; while((len=input.read(by))!=-1){ out.write(by, 0, len); } System.out.println(new String(out.toByteArray())); }catch(Exception e){ e.printStackTrace(); return null; }finally{ try { out.close(); input.close(); client.getConnectionManager().shutdown();//主动释放连接 } catch (IOException e) { e.printStackTrace(); } } }
解决方法好像很简单。但现实环境是客户端有很多版本,并且已经运行在客户手中了,改客户端来减少服务器TCP连接数,是一件比较耗时间的活。没有办法只能从服务端自身解决,解决方法只能从服务端主动关闭TCP连接。
一开始不要着急从程序层面去看问题,从操作系统角度看能否解决问题,简单为linux内核增加了一些keep-alive的参数,发现也能降低一些TCP连接数。操作很简单,往 /etc/sysctl.conf 配置文件里增加如下参数:
net.ipv4.tcp_keepalive_intvl = 3 net.ipv4.tcp_keepalive_probes = 2 net.ipv4.tcp_keepalive_time = 6最后 sysctl -p 使之生效。
上面参数的意思是说,客户端与服务端空闲时间达到6秒之后,服务端每隔3秒检测下客户端存活情况,一共检测两次,如果在6+3*2=12之内客户端进程退出了,服务端就会主动关闭该连接。服务端检测客户端存活是通过发送基于TCP的keep alive报文,客户端进程如果没有退出,就会发送确认keep alive的响应报文。如图wireshark中报文:
刚调整参数的那几天没有注意,后来才发现这样的操作会带来带宽的严重浪费,以前只要1M左右的响应带宽,现在要5M左右,都是成本啊,虽然是公司出钱。
因为我们的客户端存活时间比较长,TCP的keep alive保活机制能回收的TCP连接数是比较有限的,但是每隔6秒的报文却能让服务器带宽翻上好几倍,得不偿失啊。
没办法只能从程序设计方面在想想办法,调整服务端的设计,原先服务端不主动去关闭连接,而是根据客户端上行Connection的状态决定是否关闭,新的设计方案就是服务端可以配置连接的状态,如果服务端配置了Connection:close,服务端优先采用配置信息,如果没有配置,则还是由客户端上行来决定连接状态。
服务端是java写的,基于netty的通信框架。netty里keep alive相关的参数只有一个选项配置,相关代码如下:
this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = new DefaultChannelPipeline(); pipeline.addLast("codec", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpChunkAggregator(maxContentLength)); pipeline.addLast("handler", new HttpRequestHandler()); return pipeline; } }); this.bootstrap.setOption("allIdleTime", Integer.valueOf(this.idleTime)); this.bootstrap.setOption("child.keepAlive", Boolean.valueOf(true)); this.bootstrap.setOption("child.tcpNoDelay", Boolean.valueOf(true)); this.bootstrap.setOption("child.soLinger", Integer.valueOf(-1)); this.bootstrap.setOption("child.sendBufferSize", Integer.valueOf(-1));
ChannelFuture future = channel.write(response); if ((!HttpHeaders.isKeepAlive(response)) || (!response.containsHeader("Content-Length"))) { future.addListener(ChannelFutureListener.CLOSE); }当channel write操作完成时,CLOSE监听器主动close掉channel。
TCP保活机制可以参数这篇文章 http://www.blogjava.net/yongboy/archive/2015/04/14/424413.html