一次erlang 节点CPU严重波动排查

摘要:
新服务推出后,据观察,CPU在10%至70%之间波动严重,但根据每秒业务计数器,业务处理速度非常平均。网络流量的变化与性能计数器的结果不一致。与服务相关的业务更加复杂。首先,找出哪些业务占用了网络流量。也就是说,所有异步接口调用都将被阻止一段时间。7.erlangdns dig域名,dnssever是一个intranet服务器,响应为1

  新服务上线后观察到,CPU在10 ~ 70%间波动严重,但从每秒业务计数器看业务处理速度很平均。

  接下来是排查步骤:

  1. dstat -tam

  一次erlang 节点CPU严重波动排查第1张

  大概每10s一个周期,网络流量开始变得很小,随后突然增大,CPU也激增。

  网络流量变化和从性能计数器结果上并不符合,服务相关业务较为复杂,先找出那个业务占用网络流量。

  2. iftop

  找出流量最大的几个目标IP,并且周期的流量变为0随后激增。

  通过IP 知道是外部http接口地址,因为接口调用是异步进行的,性能计算是执行开始记录的,而不是结束记录,因此可以理解。

  也就是每过一段时间所有异步执行的接口调用都会被block一段时间。

  3. tcpdump

  既然被block,首先想到的就是依赖服务不稳定,不定期超时造成。

  抓取tcp 包确认

  但是从网络包中,没有找到任何tcp连接失败,重传等,IO统计流量为0 时,客户端就没有发起任何http 请求。

  也就说明问题出在调用方,httpclient。

  

  4.lhttpc

  仔细看了一遍lhttpc源码,能找到唯一可能影响的地方就是 lhttpc 限制并发连接数为50.

  线上查lhttpc配置时,却发现部署版本不是最新的,汗。。。找到对应版本发现老版本没有并发连接数限制,比较简单也找不到其他问题。

  5. 找出block 代码路径

  找出调用入口,手动执行,发现确实,存在block情况

  通过抓取进程栈:

    io:format("~s~n", [element(2, process_info(Pid, backtrace))]).

  定位到是 lhttpc 老实在调用其他一个接口时block 数秒

  6. fprof 工具

  fprof:start().

  fprof:apply(M, F, A).

  fprof:profile().

  fprof:analyze().

  得到如下结果(摘要)

{[{{lhttpc_client,request,9},                     1, 4993.730,    0.018}],
 { {lhttpc_client,execute,9},                     1, 4993.730,    0.018},     %
 [{{lhttpc_client,send_request,1},                1, 4993.486,    0.004},
  {{lhttpc_lib,format_request,7},                 1,    0.130,    0.008},
  {{gen_server,call,3},                           1,    0.046,    0.003},
  {{lhttpc_lib,normalize_method,1},               1,    0.038,    0.017},
  {{proplists,get_value,3},                       6,    0.006,    0.006},
  {{proplists,is_defined,2},                      2,    0.003,    0.003},
  {{proplists,get_value,2},                       1,    0.003,    0.002}]}.

{[{{lhttpc_client,execute,9},                     1, 4993.486,    0.004},
  {{lhttpc_client,send_request,1},                1,    0.000,    0.003}],
 { {lhttpc_client,send_request,1},                2, 4993.486,    0.007},     %
 [{{lhttpc_sock,connect,5},                       1, 4932.214,    0.006},
  {{lhttpc_client,read_response,1},               1,   61.202,    0.004},
  {{lhttpc_sock,send,3},                          1,    0.061,    0.001},
  {{erlang,setelement,3},                         1,    0.002,    0.002},
  {{lhttpc_client,send_request,1},                1,    0.000,    0.003}]}.

{[{{inet_gethost_native,getit,2},                 1, 4929.280,    0.000},
  {{prim_inet,recv0,3},                           1,   59.932,    0.000},
  {{prim_inet,connect0,4},                        1,    2.082,    0.000},
  {{lhttpc_client,request,9},                     1,    0.017,    0.000},
  {{gen,do_call,4},                               1,    0.017,    0.000}],
 { suspend,                                       5, 4991.328,    0.000},     %
 [ ]}.


{[{{gen_tcp,connect,4},                           1, 4932.188,    0.007}],
 { {gen_tcp,connect1,4},                          1, 4932.188,    0.007},     %
 [{{inet_tcp,getaddrs,2},                         1, 4929.488,    0.013},
  {{gen_tcp,try_connect,6},                       1,    2.672,    0.020},
  {{gen_tcp,mod,2},                               1,    0.019,    0.002},
  {{inet_tcp,getserv,1},                          1,    0.002,    0.002}]}.

{[{{gen_tcp,connect1,4},                          1, 4929.488,    0.013}],
 { {inet_tcp,getaddrs,2},                         1, 4929.488,    0.013},     %
 [{{inet,getaddrs_tm,3},                          1, 4929.475,    0.008}]}.

{[{{inet_tcp,getaddrs,2},                         1, 4929.475,    0.008}],
 { {inet,getaddrs_tm,3},                          1, 4929.475,    0.008},     %
 [{{inet,gethostbyname_tm,3},                     1, 4929.445,    0.005},
  {{inet_parse,visible_string,1},                 1,    0.022,    0.001}]}.

{[{{inet,getaddrs_tm,3},                          1, 4929.445,    0.005}],
 { {inet,gethostbyname_tm,3},                     1, 4929.445,    0.005},     %
 [{{inet,gethostbyname_tm,4},                     1, 4929.430,    0.002},
  {{inet_db,res_option,1},                        1,    0.009,    0.002},
  {{lists,member,2},                              1,    0.001,    0.001}]}.

{[{{inet,gethostbyname_tm,3},                     1, 4929.430,    0.002}],
 { {inet,gethostbyname_tm,4},                     1, 4929.430,    0.002},     %
 [{{inet,gethostbyname_tm_native,4},              1, 4929.428,    0.004}]}.

{[{{inet,gethostbyname_tm,4},                     1, 4929.428,    0.004}],
 { {inet,gethostbyname_tm_native,4},              1, 4929.428,    0.004},     %
 [{{inet_gethost_native,gethostbyname,2},         1, 4929.423,    0.002},
  {{inet,gethostbyname_tm,5},                     1,    0.001,    0.001}]}.

  从上面看,block在于 inet_tcp:getaddrs, 阅读源码发现是调用native 解析dns 的问题。

  

  7. erlang dns

  dig 域名,dnsserver 是内网服务器,响应都在1ms内,长时间测试,不存在任何延时。

  因为怀疑是erlang dns 实现的问题

  http://erlang.org/doc/apps/erts/inet_cfg.html

> inet_db:get_rc().
[{nameservers,{10,13,8,25}},
 {nameservers,{172,16,105,248}},
 {resolv_conf,"/etc/resolv.conf"},
 {hosts_file,"/etc/hosts"},
 {lookup,[native]}]
> ets:lookup(inet_db, cache_size).
[{cache_size,100}]

{lookup, Methods}.

Methods = [atom()]

Specify lookup methods and in which order to try them. The valid methods are: native (use system calls), file (use host data retrieved from system configuration files and/or the user configuration file) or dns (use the Erlang DNS client inet_res for nameserver queries). 

上面说明,默认情况erlang 每次dns查询都是查询直接走系统调用,只有但 lookup [dns] 时才会启用erlang 内部的 dns cache。

  

8. 系统dns

既然erlang 没有dnscache 抓包分析一下,系统

tcpdump -i any udp port 53

21:34:58.739732 IP 10.77.128.49.49003 > 10.13.8.25.domain: 26954+ A? i.api.xxx.cn. (32)
21:34:58.739941 IP 10.13.8.25.domain > 10.77.128.49.49003: 26954 1/4/4 A 172.16.105.232 (193)
21:34:58.740546 IP 10.77.128.49.40072 > 10.13.8.25.domain: 39440+ A? i.api.xxx.cn. (32)
21:35:02.205299 IP 10.77.128.49.6060 > 10.13.8.25.domain: 48139+ A? bx49. (22)
21:35:02.207506 IP 10.13.8.25.domain > 10.77.128.49.6060: 48139 NXDomain 0/1/0 (97)
21:35:03.479277 IP 10.77.128.49.51277 > 10.13.8.25.domain: 11779+ A? bx49. (22)
21:35:03.479501 IP 10.13.8.25.domain > 10.77.128.49.51277: 11779 NXDomain 0/1/0 (97)
21:35:03.745591 IP 10.77.128.49.24134 > 172.16.105.248.domain: 39440+ A? i.api.xxx.cn. (32)
21:35:03.747254 IP 172.16.105.248.domain > 10.77.128.49.24134: 39440 1/4/4 A 172.16.105.232 (193)

从包中,存在大量频繁的请求对i.api.xxx.cn 域名的查询,而且被我标黄的那条记录没有应答,5s后重发请求才收到结果。

 

总结:

i.api.xxx.cn 接口不支持keepalive,每次都需要新建连接,erlang 每次都需要进行系统调用查询DNS

服务器没有启动 nscd 服务,没有缓存

DNS 使用UDP 协议,即使时内网,偶尔也会丢失

erlang 对于并发查询同一个DNS 会做合并,同时只会有一个DNS 请求

erlang 的系统调用超时时间太长(5s),没有及时的重发查询,造成期间请求堆积,等DNS返回,堆积的业务同时开始处理,造成CPU波动。

解决方案:

1. 启动nscd 2. 配置inet 使用内存dns 模块缓存

  

免责声明:文章转载自《一次erlang 节点CPU严重波动排查》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇iOS:iOS开发中用户密码保存位置Linux中使用mysqldump对MySQL数据库进行定时备份下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

(100%成功超详细图文教程)虚拟机VM ware中centos7无法上网及Xshell配置正确但是连接不上本地虚拟机问题汇总

前言: 作为linux新手,想必一定会遇到各种各样的网络连接问题,菜鸟阶段总感觉自己的错误网上找不到,一度怀疑自己犯的错别人都没犯过,我晕,折腾一天后终于解决了,前来帮助小伙伴解决心中的疑惑。 如果你的目的是从Xshell连接本地的Linux虚拟机,那么请往下看,当然我会顺带着将虚拟机没网的问题说清楚: 前几天做了几个项目,然后不知为何,Xshell就莫名...

LVS分析

概述 LVS是章文嵩博士十几年前的开源项目,已经被何如linux kernel 目录十几年了,可以说是国内最成功的kernle 开源项目, 在10多年后的今天,因为互联网的高速发展LVS得到了极大的应用, 是当今国内互联网公司,尤其是大型互联网公司的必备武器之一, 从这一点上来说,LVS名副其实。搞了这么多年linux 网络开发维护, 由于一直偏通信方向,...

STM32-ETH-Lwip以太网通信

互联网模型 通信至少是两个设备的事,需要相互兼容的硬件和软件支持,我们称之为通信协议。以太网通信在结构比较复杂,国际标准组织将整个以太网通信结构制定了 OSI 模型,总共分层七个层,分别为应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层,每个层功能不同,通信中各司其职,整个模型包括硬件和软件定义。 OSI 模型是理想分层,一般的网络系统只是涉及...

nginx 4层tcp代理获取真实ip

举个例子,Nginx 中的代理配置假如是这样配置的: location / { proxy_http_version 1.1; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add...

TCP输入 之 tcp_rcv_established

概述 tcp_rcv_established用于处理已连接状态下的输入,处理过程根据首部预测字段分为快速路径和慢速路径; 1. 在快路中,对是有有数据负荷进行不同处理: (1) 若无数据,则处理输入ack,释放该skb,检查是否有数据发送,有则发送; (2) 若有数据,检查是否当前处理进程上下文,并且是期望读取的数据,若是则将数据复制到用户空间,若不满足直...

Linux网络配置命令介绍【转】

方法/步骤 1 在接下来的讲解中,讲解的Linux网络配置和网络诊断的命令有: ifconfig、ping、netstat、traceroute、dig和nslookup、host、hostname、route、arp、ethtool、GUI管理命令system-config-network。   2 第一个命令ifconfig,这个命令可以...