聊聊TCP Keepalive、Netty和Docker
聊聊TCP Keepalive、Netty和Docker
本文主要阐述TCP Keepalive和对应的内核参数,及其在Netty,Docker中的实现。简单总结了工作中遇到的问题,与大家共勉。
起因
之所以研究TCP Keepalive机制,主要是由于在项目中涉及TCP长连接。服务端接收客户端请求后需要执行时间较长的任务,再将结果返回给客户端。期间,客户端和服务端没有任何通讯,客服端持续等待服务端返回结果。
+-----------+ +-----------+
| | | |
| Client | | Server |
| | | |
| | Long Connection | |
| <---+--------------------+--> |
| | | |
+-----------+ +-----------+
那么,问题来了,实际情况往往不会这么简单。在服务器和客户端之间往往还有众多的网络设备,其中一些网络设备,由于特殊的原因,会导致上述的长连接无法维持较长时间,客户端因此也无法获得正确的结果。
典型的例子就是NAT或者防火墙,这类网络中介设备都应用了一种叫连接跟踪(connection tracking,conntrack)的技术,用来维护输入和输出的TCP连接信息,使两端设备发送的数据可达。但由于硬件上的瓶颈及基于性能的考虑,这类设备不会维持所有的连接信息,而是会将过期的不活跃的连接信息踢出去。如果这时其中一方还在执行任务,没有返回数据,造成这条连接彻底断开,另一方永远无法获得数据。为了解决这一问题,引入了TCP Keepalive的技术。
+-----------+ +-----------+ +-----------+
| | | NAT OR | | |
| Client | | Firewall | | Server |
| | | | | |
| | Long Connection | drop | Long Connection | |
| <---+--------------------+--x x--+-------------------+--> |
| | | | | |
+-----------+ +-----------+ +-----------+
TCP Keepalive是什么
其实理解起来非常简单,就是在TCP层的心跳包。当客户端与服务端之间的连接空闲了很长时间,期间没有任何交互时,服务端或客户端会发送一个空数据的ACK探测包给对方,如果连接没有问题,对方再以同样的方式响应一个ACK包,如果网络有中断ACK包会重复发多次直到上限。这样TCP Keepalive就能解决两个问题,其中之一是上述中使网络中介设备保持该连接的活性,维持连接的状态;另外,通过发包也可以探测双方的程序存活状态。Linux在内核中内建了对TCP Keepalive的支持,不过默认是关闭的,需要通过Socket选项SO_KEEPALIVE
打开这个功能,这里还涉及三个内核参数:
- tcp_keepalive_time:连接空闲的时长,默认7200秒。
- tcp_keepalive_probes:发送ACK探测包的次数上限,默认9次。
- tcp_keepalive_intvl:发送ACK探测包之间的间隔,默认75秒。
Client Server
| |
+----------------->|
| Last |
| Communicate |
|<-----------------+
| |
| |
| Long |
| |
| Idle Time |
| |
| |
|<-----------------+
| Keepalive ACK |
+----------------->|
| |
| |
Docker和内核参数
在应用层,当我们打开了Socket SO_KEEPALIVE
选项,那么Linux内核就会通过内置的定时器帮我们做好TCP Keepalive的相关工作。由于第一节描述的原因,现实中网络中介设备NAT或防火墙往往都会把失活的判断标准调低,也就是说判断长连接活性的空闲时间会远远小于Linux内核锁设置的7200秒,一般也就几十分钟甚至几分钟,这就需要我们调整将内核参数tcp_keepalive_time调低。最简单的方式就是通过sysctl
接口,调整对应的参数:
sysctl -w net.ipv4.tcp_keepalive_time=300
但是这里要留意的是,如果你的服务运行在Docker容器中,调整内核参数的方式会有所不同。
这是由于Docker会通过命名空间(namespace)隔离不同的容器网络,而对应的内核参数也是被隔离的。当Docker在启动容器的时候,创建的network命名空间并不会从宿主机继承大部分的内核网络参数,而是将这些参数设置为Linux内核编译时指定的默认值。
因此我们必须通过--sysctl
参数,在Docker启动容器时,将对应的内核参数初始化。
并不是所有的内核参数都支持命名空间,我们从Docker的官方文档中,可以了解已支持的内核参数以及使用的限制:
IPC Namespace:
kernel.msgmax, kernel.msgmnb, kernel.msgmni, kernel.sem, kernel.shmall, kernel.shmmax, kernel.shmmni, kernel.shm_rmid_forced.
Sysctls beginning with fs.mqueue.*
If you use the --ipc=host option these sysctls are not allowed.Network Namespace:
Sysctls beginning with net.*
If you use the --network=host option using these sysctls are not allowed.
Netty中的Keepalive
在了解完TCP Keepalive的机制及Linux内核对其相关支持后,我们回到应用层,看看具体如何实现,以及另外推荐的解决方案。下面我拿Java的Netty举例。Netty中直接提供了ChannelOption.SO_KEEPALIVE
选项,将其传给ServerBootstrap.childOption
方法,即可开启TCP Keepalive功能,配置好相关内核参数后,剩下的交给内核搞定。那么,既然内核将TCP Keepalive参数暴露给用户态,有没有一种方法能在应用级别调整这些参数,而不用修改系统全局的参数呢?通过man pages了解到,可以通过setsockopt方法为当前TCP Socket配置不同的TCP Keepalive参数,这些参数将会覆盖系统全局的。
通过调整每个Socket的Keepalive参数会更加灵活,不会因修改系统全局参数而影响到其他应用。接下来看看如何通过Java 的Netty库来设置对应的参数,Netty中默认的NIO transport没有直接提供对应的Socket Option,除非使用了netty-transport-native-epoll (https://github.com/netty/netty/pull/2406)。而在JDK 11中新增了对这些参数的支持:
若想在Netty中使用,还需要做一层封装。下面是对应的示例代码,仅供参考:
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
// 配置TCP Keepalive参数,将Keepalive空闲时间设为150秒
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPIDLE), 150)
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPINTERVAL), 75)
.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), 9)
// 打开SO_KEEPALIVE
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
接下来,我们如何知道设置的参数已经起作用了呢?由于涉及TCP Keepalive机制内建在Linux内核,因此无法在应用级别debug,但可以通过一些其他手段对连接进行监测。其一是通过iproute2
提供的ss
命令的-o
选项查看对应的Socket Options;其二,是通过tcpdump
抓包分析。
首先来看,默认不做任何改动时的情况:
接下来仅开启SO_KEEPALIVE
:
可以看到Socket Options的keepalive定时器为119min,也就是反映出系统默认配置的空闲时间为7200秒。
最后,我们开启SO_KEEPALIVE
,并且设置TCP_KEEPIDLE
参数为150秒:
可以看到上面tcpdump
抓包显示出,两次ACK包间隔为2分半,即150秒,包的length为0,这就是TCP Keepalive的ACK探测包。同时也可以看到下面ss
命令显示Socket Options中keepalive timer定时器的倒计时状态。
总结
通过这篇文章,我们了解到:
- TCP Keepalive的概念、原理及其两个重要作用。
- TCP Keepalive的三个系统内核参数,及其在Docker容器环境中的特殊配置方式。
- 通过Java的Netty库演示如何开启TCP Keepalive,探索在应用层灵活配置三个内核参数。
ref:
TCP Keepalive HOWTO
SO: tcp_keepalive_time in docker container
docker run Docs
聊聊TCP Keepalive、Netty和Docker的更多相关文章
- 聊聊 TCP 中的 KeepAlive 机制
KeepAlive并不是TCP协议规范的一部分,但在几乎所有的TCP/IP协议栈(不管是Linux还是Windows)中,都实现了KeepAlive功能 RFC1122#TCP Keep-Alives ...
- TCP Keepalive笔记
TCP是无感知的虚拟连接,中间断开两端不会立刻得到通知.一般在使用长连接的环境下,需要心跳保活机制可以勉强感知其存活.业务层面有心跳机制,TCP协议也提供了心跳保活机制. 长连接的环境下,人们一般使用 ...
- TCP keepalive overview
2. TCP keepalive overview In order to understand what TCP keepalive (which we will just call keepali ...
- 【转载】TCP保活(TCP keepalive)
下图是我遇到tcp keepalive的例子: 以下为转载: TCP保活的缘起 双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据 ...
- TCP keepalive under Linux
TCP Keepalive HOWTO Prev Next 3. Using TCP keepalive under Linux Linux has built-in support for ke ...
- TCP keepalive
2. TCP keepalive overview In order to understand what TCP keepalive (which we will just call keepa ...
- TCP连接探测中的Keepalive和心跳包. 关键字: tcp keepalive, 心跳, 保活
1. TCP保活的必要性 1) 很多防火墙等对于空闲socket自动关闭 2) 对于非正常断开, 服务器并不能检测到. 为了回收资源, 必须提供一种检测机制. 2. 导致TCP断连的因素 如果网络正常 ...
- TCP Keepalive HOWTO
TCP Keepalive HOWTO Fabio Busatto <fabio.busatto@sikurezza.org> 2007-05-04 Revision History Re ...
- 【 总结 】Tcp Keepalive 和 HTTP Keepalive 详解
TCP Keepalive Tcp keepalive的起源 双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据 ...
随机推荐
- 第11章 PADS功能使用技巧(1)-最全面
一.如何走蛇形线? 蛇形线是布线过程中常用的一种走线方式,其主要目的是为了调节延时满足系统时序设计要求,但是设计者应该有这样的认识:蛇形线会破坏信号质量,改变传输延时,布线时要尽量避免使用,因此一块P ...
- windows 上 OpenSSH 服务 启用秘钥登录(微软真心逆天)
windows 上 OpenSSH 服务 启用秘钥登录(微软真心逆天) windows 安装 OpenSSH 服务 最近需要在windows 服务器上部署自动发布程序,那么就需要用到 scp 和 ss ...
- 基于 electron 实现简单易用的抓包、mock 工具
背景 经常我们要去看一些页面所发出的请求时,经常会用到 Charles 做为抓包工具来进行接口抓取,但一方面市面是很多抓包工具都是收费或者无法二次开发的.当前我们团队大多数用的也都是 Charles, ...
- Linux mlocate源码分析:updatedb
在Linux的文件查找命令中,mlocate提供的locate命令在单纯进行路径名名查找时有着显著的效率优势,因为mlocate预先对磁盘文件进行扫描并存储到一个数据库文件中,查找时只需要检索数据库而 ...
- 2、SpringBoot整合之SpringBoot整合servlet
SpringBoot整合servlet 一.创建SpringBoot项目,仅选择Web模块即可 二.在POM文件中添加依赖 <!-- 添加servlet依赖模块 --> <depen ...
- Opencv 播放mp4文件和读取摄像头图以及可能会发生的一些异常问题解决方法
学习内容 学习Opencv 读取并播放本地视频和打开摄像头图像以及可能会发生的一些异常问题解决方法 代码演示 电脑环境信息: OpenCV版本:4.5.2 ,vs2017 1.视频文件读取与播放 加载 ...
- 96、linux之rpm包定制
96.1.rpm包定制介绍: 编译安装软件,优点是可以定制化安装目录.按需开启功能等,缺点是需要查找并实验出适合的编译参数,诸如MySQL之类的软件编译耗时过长. yum安装软件,优点是全自动化安装, ...
- 15、nginx编译安装
15.1.nginx介绍: 1.Nginx是一个开源,支持高性能,高并发的www服务和代理服务软件,由俄罗斯人开发,是开源的,nginx因具有高并发(特别是静态的 资源).占用系统资源少等特性,且功能 ...
- Flask(11)- 操作 Cookie
前言 Cookie 详解:https://www.cnblogs.com/poloyy/p/12513247.html 这一节来瞧一瞧如何用 Flask 操作 Cookie 接下来就是 实战栗子!!! ...
- 盘点linux操作系统中的10条性能调优命令,一文搞懂Linux系统调优
原文链接:猛戳这里 性能调优一直是运维工程师最重要的工作之一,如果您所在的生产环境中遇到了系统响应速度慢,硬盘IO吞吐量异常,数据处理速度低于预期值的情况,又或者如CPU.内存.硬盘.网络等系统资源长 ...