网络编程中,关于Keep-Alive与Idle你了解多少?

原创 吴就业 120 0 2019-10-25

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/102a43314d8544908ee6d5a62aca5832

作者:吴就业
链接:https://wujiuye.com/article/102a43314d8544908ee6d5a62aca5832
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

有朋友问我,关于使用Netty开发长连接应用,为什么要加一个Keep Alive为true的配置。你是否也有这样的疑惑呢?

要解答这个问题,首先要了解Keep Alive。所以,今天笔者给大家分享笔者对Keep alive的一点浅薄的了解,再来说下应用层为什么要自己实现Keep Alive,就是常说的“心跳保活”,以及什么是Idle机制,Netty是怎么实现的。

对网络编程不了解的朋友可能都没听过Keep Alive,但Http请求头携带的Connection:keep-alive大家可能熟悉。不过HTTP1.1版本是默认开启的,也就导致很少能看到请求头中有Connection这个参数。

KeepAlive译为“保活”,就是“心跳保活”。KeepAlive最早应该是TCP协议定义的规范,即支持TCP协议的设备都应该要实现这个规范。我们知道,两个Socket建立连接后就可以发送和接收数据,但是连接是否健康双方是无法感知的,只有下次想给对方发送数据,结果发送失败,才判断对方是否断开连接了。

怎么理解Socket通信是无法感知对方状态的呢。来个简单的比喻。小明和小红修了一条路,目的是可以去对方家里玩,路修好了,小明和小红随时都可以走到对方家里玩。假设没有任何的通信设备的情况下,比如写信、打电话、发短信等,小明去不去小红家,小红没办法知道,也不知道小明是不是今天有事来不了,或者因为上次来玩时生气了,以后都不来了,同样的,小明也不知道小红的情况。某天,小红突然很想找小明玩,但是走到半路,发现路已经被堵住了。

数据包在网络传输中会出现丢包的情况,所以不能仅仅通过发送一次报文失败,就判定是对方断开连接,这样未免果断了些,网络波动还是很正常的现象。所以,就需要经过多次试探来确认对方是否真的不可达。这是TCP协议规定的,也可以通过修改配置,默认是重试9次,每次间隔75ms,每7200ms内没有接收到任何数据报文,则发送KeppAlive探测报文。详细的可以查阅下资料,这里不展开说了。

从上面的默认参数可以看出,TCP的KeepAlive需要两分多钟才能完成。既然可以修改默认配置,那为什不改配置就好了。修改配置是对整个操作系统上的应用起作用的,而一个操作系统上不只部署一个应用。

那为什么TCP提供了KeepAlive,应用还需要自己去实现呢。一个是修改配置会影响到整个系统的应用,二是不修改的话,默认2分多钟才完成一个连接是否健康的检测。2分钟是什么概念,对于高并发的应用服务器而言,2分钟黄花菜都凉了。三是不同厂商的路由器可能对keepalive的配置默认是关闭的,这个我不是很了解。

那HTTP协议中的Connection:keep-alive又跟TCP协议的KeepAlive有什么联系呢?没有联系。Http是应用层协议。说到协议,我记得有一次我说了句“协议,协议,协议只是协议”,同事笑了,然后我解释到“本来就是嘛,协议就是一种约定,是需要你去实现它才能用它定义的特性”。

HTTP1.1版本默认开启keep-alive,只是说协议这样规定,而如果某公司提供的HTTP服务器声明实现了HTTP1.1版本,比如Nginx,则说明Nginx是支持Http协议规定的,默认保持长连接的特性,再比如tomcat、jetty等Servlet容器。

以上只是笔者对KeepAlive的一点了解。那么,我们自己基于Netty开发为什么配置KeepAlive,相必大家也都清楚了。看Netty源码你会发现,这其实是为Jdk的SocketChannel配置的。

前面提到,我们在开发网络通信应用时,需要自己去实现KeepAlive的原因。在高并发场景下,2分钟堆积的SocketChannel有多少,除占用文件句柄数,导致文件句柄数达到最大限制,无法接收新的连接外,大量无效连接也会影响epoll轮询事件的效率。

所以,我们在应用层一般设置6秒到15秒的时间间隔,规定客服端给服务端定时发送心跳包,当服务端超过这个时间没有接收到任何心跳包时,就认为客户端掉线,不会设置重试,即便网络波动也断开连接。

但是,只客户端给服务端发送心跳包还不行啊,我客户端也需要感知连接是否可用,也需要你服务器回应我的心跳包,或者也定时给我发送心跳包啊。

即便是设置为1分钟,假设有1w个长连接,一分钟内给服务端发送一个心跳数据包,这对客户端来说并没有什么影响,但是服务端一分钟内就接收1w个数据包,心跳包也是需要编码和解码,也占用buff接收,在一次解码时也会伴随一次内存拷贝,因为要解决数据粘包和半包问题。

怎样才能优化不必要的心跳包发送和接收。就是idle机制。比如netty提供的IdleChannelHandle(是这这个名吧,记不清),在读空闲超时或写空闲超时时会发送一个IdleStatusEvent事件,我们可以监听这些事件,比如服务端监听读空闲超时事件,当多久没有接收过客户端发来数据包时,触发keepalive,这时再主动去给客户端发送一条数据包,如果发送成功呢,说明客户端还活着,发送失败则关闭连接。也可以空闲超时直接关闭连接。

我们可以只关心读空闲超时事件,也可以只关心写空闲超时事件,也可以即关心读也关心写空闲超时事件。读空闲就是很久没有接收到客户端发来消息,写空闲超时就是很久没有给客户端发送消息。

其实Netty的Idle实现并不难理解,就是在每次接收到客户端发来的数据包时,更新一下最近一次读的时间,在每次给客户端发送数据包时,更新一下最近一次写的时间。然后根据配置的读或写空闲超时时间分别注册两个定时任务,定时检测当前时间与最近一次更新的时间比较,超过配置的时间间隔,则发送一个Idle事件。定时任务是一次性的,每次执行完后都重新注册定时任务。

照这么说,我们也可以自己实现一个。其实Netty提供的Idle实现,并没有那么简单,如果去看它的源码,你会发现,写空闲超时检测的实现, Netty会判断当前是否有数据在写,但未写完成。比如大文件传输,文件未传输完,最近一次写时间是还没得到更新的,此时如果就触发Idle超时事件,显然是不能的。

关于keep alive与idle,就说这么多,看完后是否对学习Netty过程中遇到的keep alive与如何运用idle+心跳包实现应用层的keep alive更了解了呢?

#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

Fastjson与Jackson性能问题

可以给出的结论是,jackson在解析大json场景下性能是秒杀fastjson的,不急,我们有图有真相。

ES与Redis实现千万级数据的范围查询性能比较,远程 http调用耗时也能降低到0ms

本篇介绍使用ES与使用Redis实现的IP库范围查询谁性能更强,以及远程http调用ip服务耗时如何降低到0ms。

基于Redis实现范围查询的IP库缓存设计方案

IP信息库是按区间存储的,拿到一个ip得要知道它所属的范围才能知道它对应哪条记录。本篇介绍如何使用Redis的Sorted Set数据结构实现支持范围查找的IP库缓存方案。

我所经历的一次Dubbo服务雪崩,压力山大

服务雪崩,听到这个词就能想到问题的严重性。是的,整个项目,整条业务线都挂了,从该业务线延伸出来的下游业务线也跟着凉了。

深入理解Dubbo源码,分析Java SPI与Dubbo SPI的实现源码

SPI全称是Service Provider Interface,直译就是服务提供者接口,是一种服务发现机制,是Java的一个内置标准,允许不同的开发者去实现某个特定的服务。Dubbo的SPI并非使用Java提供的SPI,完全是自己实现的一套SPI机制,并对其进行了增强,如通过字节码实现动态代理类。

深入理解Dubbo源码,如何高效的阅读Dubbo框架源码

笔者最近的一次重构项目选择用dubbo去实现服务间的调用,选择dubbo作为分布式的RPC远程服务调用框架,但笔者在使用的过程中遇到了很多疑难问题,网上搜不到一篇能解决我疑问的文章,无奈,只能选择自己从源码中寻找答案。