一、问题背景
近日在分析类生产环境在进行在线升级时,IO归零时间长问题时,经过分析定位把问题锁定在brpc Start的时候的问题,发现进程启动时调用brpc Start函数时耗时超过10秒导致进程启动慢了。
问题原因
brpc的Start代码中这个日志中的butil::my_hostname() 可能触发 DNS反向解析,而某些环境下的DNS服务器响应缓慢会导致阻塞。默认先查 /etc/hosts ,如果/etc/hosts中没有,会再查 DNS。
二、问题解决过程
锁定是brpc Start的问题后,给brpc代码中添加日志,以确定到底是Start中哪个步骤慢:
1 | int Server::StartInternal(const butil::EndPoint& endpoint, |
重新编译二进制后,运行并分析运行日志(其实没有加上述日志也能看出来耗时在哪,加上上述日志后更明显和明确问题在哪):
1 | Apr 02 13:51:27 gz-kwe8-az1-eds-ssd-55e125e160e36 nohup[3426698]: I0402 13:51:27.787414 3426752 /root/code/gitcode/lava-engine/src/brpc/src/brpc/socket.cpp:2341] Checking Socket{id=0 addr=55.125.33.193:9210} (0x1c33e000) |
可以最后两条日志之间是隔了10秒钟,看代码其实之间就两三行代码,然后关键就是有一行是打印日志
1 |
|
butil::my_hostname(),这个函数可能触发 DNS反向解析,而某些环境下的DNS服务器响应缓慢会导致阻塞。默认先查 /etc/hosts ,如果/etc/hosts中没有,会再查 DNS。
DNS 配置检查
- 查看 /etc/nsswitch.conf:
1 |
|
若配置为 hosts: files dns,表示会先查 /etc/hosts 再查 DNS。
- 检查 /etc/hosts:
1 |
|
若无对应条目,则可能触发 DNS 查询。
网络策略限制
DNS 端口阻断:
1 |
|
MyAddressInfo 结构体构造函数逻辑
1 |
|
关键函数行为
- gethostname()
直接读取内核维护的主机名(如 gz-kwe8-az1-eds-ssd-55e125e160e35),耗时 0ms。
- hostname2ip()
内部调用 getaddrinfo(),遵循 /etc/nsswitch.conf 的 hosts 配置,典型顺序为:
1 |
|
现象解释
- 在 /etc/hosts 添加条目后,应用程序启动变快
原因:hostname2ip() 优先查询 /etc/hosts,成功解析到 IP,无需触发 DNS 查询。
执行路径:
my_hostname -> 查/etc/hosts -> 直接返回IP(耗时 0ms)
三、问题总结
排查的过程发现有两台不通的服务器比较典型,两台服务器的DNS服务器都不通:
一台ping是这个结果,有一定卡顿,这台启动就会耗时10秒
1 |
|
另外一台结果是
1 |
|
该服务器立即返回该结果,且启动时很快,不存在耗时10秒问题。
使用strace命令进行分析:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52strace -e trace=open,read,connect getent hosts 10.24.171.3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\\\2\0\0\0\0\0"..., 832) = 832
read(3, "multi on\n", 4096) = 9
read(3, "", 4096) = 0
read(3, "# Generated by NetworkManager\nse"..., 4096) = 101
read(3, "", 4096) = 0
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 4096) = 1516
read(3, "", 4096) = 0
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0003\0\0\0\0\0\0"..., 832) = 832
read(3, "127.0.0.1 localhost localhost."..., 4096) = 312
read(3, "", 4096) = 0
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320!\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220C\0\0\0\0\0\0"..., 832) = 832
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("114.114.114.114")}, 16) = -1 ENETUNREACH (Network is unreachable)
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.100.153")}, 16) = -1 ENETUNREACH (Network is unreachable)
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("114.114.114.114")}, 16) = -1 ENETUNREACH (Network is unreachable)
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.100.153")}, 16) = -1 ENETUNREACH (Network is unreachable)
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\00004\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P|\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\3402\0\0\0\0\0\0"..., 832) = 832
10.24.171.3 NMA04-303-H-21-A2P2-SEV-SC5212M7-02U05
10.24.171.129 NMA04-303-H-21-A2P2-SEV-SC5212M7-02U05
10.8.96.127 NMA04-303-H-21-A2P2-SEV-SC5212M7-02U05
+++ exited with 0 +++
使用该命令可以发现也很快返回结果,通过上述分析可知,其实两台服务器都是没有配置/etc/hosts,所以第一步在hosts配置文件中没有找到,然后触发DNS,但是第一台直接返回失败,就触发第3步,直接使用系统维护的hostname。
网络不通但 my_hostname() 快速返回的机制
gethostname() 不依赖网络
MyAddressInfo 构造函数中的 gethostname() 仅读取内核维护的主机名(如 NMA04-303-H-21-A2P2-SEV-SC5212M7-02U05),是纯本地操作,耗时 0ms。
hostname2ip() 走 /etc/hosts
若 /etc/hosts 中存在主机名到 IP 的正向映射(如 NMA04-… 10.24.171.3),解析直接命中文件,无需触发 DNS 查询。
/etc/nsswitch.conf 配置中 hosts 项包含 myhostname:
1 |
|
myhostname 模块的行为:
当 files(即 /etc/hosts)和 dns 均未找到匹配项时,myhostname 会尝试将系统主机名与本地 IP 关联。
自动生成解析规则:若系统主机名为 NMA04-303-H-21-A2P2-SEV-SC5212M7-02U05,且该机器有 IP 10.24.171.3,myhostname 会自动创建如下映射:
1 |
|
可以调整顺序解决该问题
1 |
|
1 | time getent hosts 55.125.33.196 |
改为
1 | hosts: files myhostname dns |
再执行发现
1 | time getent hosts 55.125.33.196 |
耗时果然降到毫秒级别了。
最后,再简单总结一下:
- 先看nsswitch.conf文件
1 | grep hosts /etc/nsswitch.conf |
如果结果是:
1 | hosts: files dns myhostname |
表示获取ip-hostname路径是:
(1)/etc/hosts
(2)DNS服务
(3)使用myhostname
使用myhostname会尝试将系统主机名与本地 IP 关联。
自动生成解析规则:若系统主机名为 NMA04-303-H-21-A2P2-SEV-SC5212M7-02U05,且该机器有 IP 10.24.171.3,myhostname 会自动创建如下映射
1 | 10.24.171.3 NMA04-303-H-21-A2P2-SEV-SC5212M7-02U05 |
顺序是依次的,如果第一个方式就成功了就不会走到后面的方式。
其实出现说的10秒的原因是因为:
(1) /etc/hosts中没有配相应的ip-hostname
(2) DNS服务器无效(如果直接是Network is unreachable,直接就返回了,不会导致10秒问题,如果是Destination Net Unreachable,则需要等超时)
出现问题的环境ping DNS服务器出现的是:Destination Net Unreachable会等超时,造成10秒耗时。
| 服务器类型 | 网络状态 | DNS 行为 | 解决方案 |
|---|---|---|---|
| 无默认路由 | Network is unreachable | 立即失败,快速回退 | 保持现状 |
| 有路由但不可达 | Destination Net Unreachable | 等待超时(10s) | 删除路由、减少 DNS 超时或调整顺序或禁用DNS |
最终结论:网络层配置差异是导致两台服务器 DNS 解析速度不同的根本原因。通过调整路由或 DNS 策略,可统一行为并消除延迟。