0x0 问题现象
最近服务在调整QoS,旺哥搜线上日志的时候反馈给我dds-gray有pasc相关的报错,报错如下:
2020-03-12T14:10:06+08:00 ddsserver-gray-8872-smooth-4283622188-srb6v dds: 2020/03/12 14:10:06 [error] 9#9: *4 [lua] pasc_client.lua:196: getProductVersionByAlias(): request pasc error: closed, context: ngx.timer
pasc服务是我负责开发的,对应于dds-gray服务中访问pasc-server使用的lua sdk也是我这边维护,看到报错立马警觉了起来,先根据这个错误去kibana里搜了一下, 应该是从该功能上线后就存在了。
0x1 问题分析
从报错日志看,这些报错是在timer中产生的,dds-gray服务中,其他代码中也有使用sdk的部分都没有相关的报错,首相怀疑的就是timer中的代码写的有问题。于是开始看这块相关的代码,这部分代码是用来刷缓存的,因此失效也不会直接影响业务,所以没有及时发现这个问题。
这个定时器的功能是将部分产品数据做预热,从pasc-server服务读取配置数据然后写到本地的共享内存,由于本地缓存会有ttl, 所以这个定时器里有一个死循环,每隔1分钟请求pasc-server将获取到的数据刷新到缓存中,对于这类包含死循环的timer之前就踩过坑,参见这篇 一次OR timer使用不当导致的内存问题记录
稍作修改后,推了一个版本到测试环境,发现还是有连接close的报错,问题仍然存在,于是加了一些日志,发现虽然连接被close不是每次都产生的,但出现的还是比较频繁的。
服务代码中我们请求pasc-server的请求连接使用了连接池,每次请求完会将连接放回连接池中,下次connect的时候优先从连接池中取出连接,按理取出的连接应该是健康的才对。
于是对照着日志用tcpdump抓取了往来pasc-server之间的数据包,抓到服务日志出现closed报错为止,用wireshark打开看一下究竟是哪里在什么时候关闭了这个连接:
截图中黄色线圈出来的就是请求对应的就是产生closed错误的那次请求,下个报文就是服务端主动发送了一个FIN包关闭了连接,红色线圈出来的两个时间间隔刚超过60s, 从报文上看连接保活和关闭都是pasc-server服务端主动发起的,看了一下pasc-server的代码,确实服务设置的ReadTimeout和WriteTimeout超时时间都是60s, 没有设置IdleTimeout,看了一下go的net/http中关于Server结构中idleTimeout字段的说明:
IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. If IdleTimeout is zero, the value of ReadTimeout is used. If both are zero, there is no timeout.
0x2 问题修复
从文档看,如果不设置IdleTimeout 默认会用ReadTimeOut的值,因此serve这边保持的idle超时时间就是60s,而调用端这边是每隔60s进行一次请求,怀疑大概率是遇到临界的情况了,即connect操作取出来的时候连接还是健康的,等到发送数据到pasc-server服务端的时候,服务检查到连接已经满了IdleTimeout的阈值,就发送了一个FIN报文关闭了连接。
timer的间隔调大或者调小应该都可以规避这个错误,timer间隔调大,服务端每次都会关闭空闲连接使客户端的连接池不再生效,每次请求都创建新的连接;timer间隔调小,请求变频繁一些,但每次连接池中取连接都能复用到连接。
由于这个timer时间调整涉及业务上的逻辑,最后选择了调大pasc-server服务的IdleTimeout数值来规避这个问题,修改这个值潜在的一个影响是在对服务升级的时候,直接rolling update 可能会产生不平滑的现象(连接池的连接异常断开),可以采用其他方式进行升级。
重新编译部署了一个版本,dds-gray上的closed的错误不再产生了,netstat看连接也一直保持在ESTABLISHED的状态。