TCP 连接与TCP keep alive 保活检测机制

来源:互联网 发布:nginx 配置虚拟目录 编辑:程序博客网 时间:2024/04/25 10:06

生产环境中一台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 = 3net.ipv4.tcp_keepalive_probes = 2net.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



0 0
原创粉丝点击