一次erlang 節點CPU嚴重波動排查


  新服務上線后觀察到,CPU在10 ~ 70%間波動嚴重,但從每秒業務計數器看業務處理速度很平均。

  接下來是排查步驟:

  1. dstat -tam

  

  大概每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 模塊緩存

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM