性能文章>Keepalive长链接的问题排查>

Keepalive长链接的问题排查原创

398146

事件:

统一接口的管理,推行网关。选型产品为云原生开源Apisix(一套基于Nginx、OpenResty、Luajit实现的一套支持热插拔组件的云原生网关)。现在运行的应用是通过Nginx作为代理转发,通过Apisix能够替换调Nginx,达到更灵活的管理应用API。

起因:

现有的一套应用A -> Nginx -> B,Nginx反向代理了B应用的服务接口。A应用的服务逻辑通Apache HttpClient组件作为Http客户端通过Nginx向B应用请求服务,这套服务已经运行了2、3年的时间相对比较稳定。现在需要通过Apisix取代Nginx做请求转发,问题就这样出现了,在切换后的第二天应用的维护人员反馈在A应用的日志里发现了少量的NoHttpResponseException的异常。这引起了应用侧的关注,并反馈需要对此问题做出分析和解决。

经过:

  1. 首先排查了Nginx配置的差异,从客户端到服务端的连接方面,返现keepalive_timeout这个参数存在差异。Nginx的配置是65(默认单位是秒),Apisix的配置是60s。 版本的差异是,Nginx的版本是1.16.1, Apisix的版本是1.19.1。单纯从版本上的差异看Nginx的核心模块是没有变化的。于是问题的焦点就放在了keepalive_timeout这个参数上。Nginx官网上的参数解释如下:
    image.png
    默认值是75s 并说明了有个可选参数header_timeout,是用做响应头回复客户端长连接会话保持时长的。结合客户端的代码逻辑能够影响到HttpClient的是默认的Keepalive策略,如果响应头中包含这个值就会被应用到连接的保持时长中,否则会返回-1标识长期有效并被复用(reused)。
    HttpClient的版本上4.x和5.x默认Keepalive策略的实现上存在差异,在这次的案例中是4.x版本也就说当响应头中没有这值时,按照-1长期有效处理。5.x的代码对没有Keepalive头信息的时候,会通过请求上下文中获取,如果走默认值的话会比较长,默认值是3分钟。
    image.png

1.1. 第一次配置文件修改
将Apisix的keepalive_timeout配置调整到65秒,通过应用调用的观察并没有得到改善,但出现的频率有所减少。
1.2. 第二次配置修改
将Apisix配置修改成了keepalive_timeout 65s 65s,问题依旧。
1.3. 第三次配置修改
将Apisix配置修改成了keepalive_timeout 75s 75s,问题解决。

  1. 以上修改存在这样几个问题,
    1)header_timeout 可选参数是否生效,对客户端的影响是否如我们上面的猜测。
    2)65s 到 75s 应用发生了行为上的改变,这个时间的配置和应用侧的哪个配置呼应。
    3)NoHttpResponseException 异常的发生时,Apisix侧是否收到了这次的请求。
    4)Nginx上没有发生的问题,切换到Apisix上为什么发生了。
    以上一连串的问题确实头疼了一阵,毕竟最底层都是通过Nginx做的代理转发,为什么会出现应用行为上的差异。冷静后梳理需要做的事情,既然我们通过官网了解了参数的行为就要从两方面来验证,一方面是代码层面,自己写的代码和httpclient的源码。另一方面是通过测试用例复现这个问题。

问题复现:同样的逻辑搭建好测试用例,通过10线程并发、轮询10次,每次轮询间隔时长大于Nginx的keepalive_timeout配置时长(没有配置header_timeout)的情况下进行测试。在原Nginx上出现了NoHttpResponseException异常,从这一点上我们暂时确定了问题是存在是,只是在运行过程中没有出现而已。并通过tcpdump抓包工具,抓取并分析了网络层的数据,确定了发生NoHttpResponseException的这次请求在Nginx侧是收到了,并且Nginx回复的是RST(关注63218端口)。
image.png
如下图可以看到应用在最后一次请求前,Nginx侧主动发起了FIN的短链请求,应用侧恢复了ACK,但应用侧并没有发起FIN做最终的断链处理。导致使用了半连接导致的Nginx在收到请求后直接回复RST,导致的客户端异常。
image.png

结果:

以上问题没有得到充分的验证和解答,目前需要说明的结论是

  1. 在HttpClient在全部默认值下创建实例存在部分机制的确实,例如长连接池化后的失效驱逐策略,长连接的空闲时长。应对此种情况的作为服务端通过配置header_timeout可选项进行干预。
  2. 需要完善客户端机制
    2.1. 连接池的驱逐策略
    2.2. 连接池内连接实例的空闲时长

说明:

作为一次问题排查的记录,整个过程有些繁琐并不复杂。个别的问题没能得到有效的答案,例如上述问题中的2。后续有机会再继续跟踪追查,按照有些解决问题和打消使用者疑虑的原则,就先做出一些理论上的合理解释,毕竟风险是存在的(同样的环境下复现了问题),在切换环境后快速的暴露了出来,站在不同的角度看待这个问题,使用者会疑虑Apisix的稳定和隐藏风险,推广者自然是通过理论+实践让使用者了解这服务的原理,毕竟新的东西需要有个接受的过程。

点赞收藏
heroyeah_293541
请先登录,查看4条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

JDBC PreparedStatement 字段值为null导致TBase带宽飙升的案例分析

JDBC PreparedStatement 字段值为null导致TBase带宽飙升的案例分析

随机一门技术分享之Netty

随机一门技术分享之Netty

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

6
4