[置顶] TCP 连接与TCP keep alive 保活检测机制

生产环境中一台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));

this.bootstrap.setOption("child.keepAlive", Boolean.valueOf(true));  这行代码只配置是否启用TCP保活检测,如果启用了,多久检测一次还是取决于操作系统本身。说白了,还是跟编辑  sysctl.conf文件的效果一样,因为它们都是从TCP层的检测。netty从应用层主动关闭连接的话,可以简单增加一个监听器,代码如下:

    ChannelFuture future = channel.write(response);
    if ((!HttpHeaders.isKeepAlive(response)) || (!response.containsHeader("Content-Length"))) {
      future.addListener(ChannelFutureListener.CLOSE);
    }
当channel write操作完成时,CLOSE监听器主动close掉channel。
问题基本解决,后来发现其实也可以通过增加一层nginx代理解决问题,nginx通过短连接与后端进行交互,与前端保持长连接,不过不是很清楚nginx长连接的检测机制,但根据生产环境表现出的现象,肯定不是用操作系统参数。


TCP保活机制可以参数这篇文章 http://www.blogjava.net/yongboy/archive/2015/04/14/424413.html



你可能感兴趣的:([置顶] TCP 连接与TCP keep alive 保活检测机制)