一次 DNS 解析超时引发的连锁故障排查记
上周三下午的时候,监控平台就收到了很多接口超时告警的信息。核心交易服务的P99响应时间由原来的80毫秒左右上升到了现在的3秒以上,有的请求甚至直接返回了502状态码。另外,CPU和内存的状态都保持稳定,数据库慢查询的日志也没有出现异常的情况。但是“资源没有瓶颈但是大面积超时”的现象却表明问题出在调用链中的一个隐性环节上。
首先提取出故障期间的链路追踪数据,其中耗时最多的是对下层用户的中心服务进行HTTP请求所花费的时间,而超时请求所花费的时间几乎与客户端设置的3秒连接超时时长相同。但是用户中心自己提供的监控数据显示它的处理时间是正常的,并没有把请求送到对方那里去,因此我就让运维在应用节点上抓包,果然发现有大量的指向用户的中心域名的TCP SYN包没有回应——问题就出在这儿。
第二次检验:在故障节点上执行`dig @10.0.0.2 user-center.internal +trace`,开始的时候几次可以正常返回,但是经过并发压力测试之后就出现了超时的情况。从 `/etc/resolv.conf` 文件中可以看到有2个名字服务器,其中第2个地址已经被废弃不用了,而该名字服务器的地址为10.0.0.3,该地址早就不再使用了。glibc解析器默认的超时时间为5秒,并且会尝试2次重试,在主要的名字服务器偶尔出现丢包的情况下就会把请求转交给备用的名字服务器,因此1次完整的解析需要花费10秒以上的时间。
产生的结果如下:DNS解析超时、HTTP连接建立被阻塞、连接池被占用、后续请求排队等候、线程池耗尽、整个服务崩溃。因此CPU是稳定的,因为所有的线程都处在等待socket的状态下,并没有真正地进行操作。
找到问题的原因之后,我分3步来解决它。第一,在/etc/resolv.conf文件中把无效的10.0.0.3这1项删除,并且只保留有效的主DNS以及合适的超时时间:【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。
```
格式要求:1段,输出为1段纯正文内容,不能换行拆分,不能出现任何问候语、开场白、解释说明或者引号包裹。
nameserver 10.0.0.2
```
用timeout=2 attempts=2来把每次解析的最大等待时间由10秒减为4秒以下,并且用rotate的方式,在不同的DNS下分担压力。第二,在应用程序层面上给HTTP客户端增加一个DNS缓存的功能,使得每次请求都不需要经过系统解析就可以直接访问到目标资源。比如,在使用Java时可以使用JVM参数来开启缓存:
【要求格式】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或者引号包裹。
【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。
```
-Dnetworkaddress.cache.ttl=60 -Dnetworkaddress.cache.negative.ttl=10【要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。
```
正向缓存60秒、负向缓存10秒,这样可以减少解析的压力,并且不会在故障恢复之后很久才得到正确的结果。第三步就是把DNS可用性加入到监控当中,运行了一个每分钟都会用到`dig +short +time=2`的方式来检查各个名字服务器的工作状态的脚本,如果连续两次都检查不到的话就会发出警报。
修复之后,在10分钟之内所有的接口超时告警都消失了,P99值降到90毫秒左右。通过对造成此问题的原因进行分析之后得知,该旧DNS服务器是在半年前机房搬迁时留下的配置,并且一直没有被清理掉,在平时没有大的业务量的情况下并没有被发现,直到当天业务量突然增加才被触发。
三条经验可以记录下来:第一种情况是,在`/etc/resolv.conf`文件中所有的nameserver都作为生产依赖进行管理,当服务器停止时要同步地清理掉所有节点的配置,并且备用地址不能只“配了就行”,而必须是失效的备用才更危险。第二种情况是,DNS超时是典型的隐性故障源,在监控的时候不能只看应用指标,还要把解析耗时、解析成功率等基础依赖纳入可观测体系。第三种情况就是客户端侧要保证DNS缓存开启并且设置合理的TTL值,把解析从每次请求的关键路径上摘出去,这样就可以避免事后排查省力得多。
- 点赞
- 收藏
- 关注作者
评论(0)