zoukankan      html  css  js  c++  java
  • Erlang服务器内存耗尽bug跟踪过程

    本文描述朋友Erlang服务器内存耗尽bug的解决过程。
    首先说明一下问题,服务器1千多人在线,16G内存快被吃光。玩家进程占用内存偏高:


    接下来是解决过程。

    第一步:
    查看进程数目是否正常? erlang:system_info(process_count). 进程数目合理

    第二步:
    查看节点的内存消耗在什么地方?
    > erlang:memory().
    [{total,2099813400},
     {processes,1985444264},
     {processes_used,1985276128},
     {system,114369136},
     {atom,4479545},
     {atom_used,4477777},
     {binary,22756952},
     {code,10486554},
     {ets,47948808}]

    显示内存大部分消耗在进程上,由此确定是进程占用了大量内存

    第三步:
    查看哪些进程占用内存最高?
    > spawn(fun() -> etop:start([{output, text}, {interval, 1}, {lines, 20}, {sort, memory}]) end).
    (以输出text方式启动etop,其间隔为1秒,输出行数为20行,按照内存排序. 这里spawn一个新进程,目的是输出etop数据时不影响erlang shell 输入.)
    结果如下:
    etop输出有点乱,超过一定范围变成了**,不过我们已经找到了内存占用最高的进程.

    第四步:
    查看占用内存最高的进程状态
    > erlang:process_info(pid(0,12571,0)).           
    [{current_function,{mod_player,send_msg,2}},
     {initial_call,{erlang,apply,2}},
     {status,waiting},
     {message_queue_len,0},
     {messages,[]},
     {links,[<0.12570.0>]},
     {dictionary,[]},
     {trap_exit,false},
     {error_handler,error_handler},
     {priority,normal},
     {group_leader,<0.46.0>},
     {total_heap_size,12538050},
     {heap_size,12538050},
     {stack_size,10122096},
     {reductions,3795950},
     {garbage_collection,[{min_bin_vheap_size,46368},
                          {min_heap_size,233},
                          {fullsweep_after,65535},
                          {minor_gcs,0}]},
     {suspending,[]}]

    其中” {total_heap_size,12538050},” 表示占用内存为 12358050 words(32位系统word size为4,64位系统word size为8, 可以通过erlang:system_info(wordsize) 查看),在64位系统下将近100M, 太夸张了!

    第五步:
    手动gc回收,希望问题可以解决
    >  erlang:garbage_collect(pid(0,12571,0)).
    true

    再次查看进程内存,发现没有任何变化!gc没有回收到任何资源,因此消耗的内存还在发挥作用,没有回收!

    第六步:
    不要怀疑系统,首先要怀疑自己的代码
    认真观察代码,其大致结构如下:
    send_msg(Socket, Pid) ->
       try
            receive
                {send, Bin} ->
                    ...
                {inet_reply, _Sock, Result} ->
                   ...
       catch
           _:_ ->
               send_msg(Sock, Pid)
       end.
    其目的是循环等待数据,然后进行发送,其使用了try...catch捕获异常.
    这段代码有问题么?
    对,这段代码的确有问题, 其不是尾递归! try...catch会在stack中保存相应的信息,异常捕获需要放置在函数内部,所以send_msg最后调用的是try...catch,而不是自身,所以不是尾递归!
    可以通过代码得到验证:
     cat test.erl
    -module(test).
    -compile([export_all]).


    t1() ->
       Pid = spawn(fun() -> do_t1() end),
       send_msg(Pid, 100000).

    t2() ->
       Pid = spawn(fun() -> do_t2() end),
       send_msg(Pid, 100000).

    send_msg(_Pid, 0) ->
       ok;
    send_msg(Pid, N) ->
       Pid ! <<2:(N)>>,
       timer:sleep(200),
       send_msg(Pid, N-1).

    do_t1() ->
       erlang:garbage_collect(self()),
       Result = erlang:process_info(self(), [memory, garbage_collection]),
       io:format("~w ~n", [Result]),
       io:format("backtrace:~w~n~n", [erlang:process_display(self(), backtrace)]),
       try
         receive
             _ ->
                 do_t1()
         end
       catch
         _:_ ->
             do_t1()
       end.

    do_t2() ->
       erlang:garbage_collect(self()),
       Result = erlang:process_info(self(), [memory, garbage_collection]),
       io:format("~w ~n", [Result]),
       io:format("backtrace:~w~n~n", [erlang:process_display(self(), backtrace)]),
       receive
         _ ->
             do_t2()
       end.

    版本1:erlc test.erl && erl -eval "test:t1()"
    版本2:erlc test.erl && erl -eval "test:t2()"
    你会看到版本1代码的调用堆栈在不断增长,内存也在增长, 而版本2函数调用地址保持不变,内存也没有发生变化!

    总结:
    1,服务器编程中,循环一定确保为尾递归
    2,善于使用OTP,如果使用gen_server替换手写loop,就不会出现这个问题!

  • 相关阅读:
    Cordova-conifg.xml配置
    Cordova插件开发
    android shape的使用
    Cordova
    性能优化
    ionic默认样式android和ios差异
    在IIS中部署ASP.NET 5应用程序遭遇的问题
    Ionic命令大全
    IOS开发
    Cordova 8 架构使用sqlite
  • 原文地址:https://www.cnblogs.com/wuxi/p/3114044.html
Copyright © 2011-2022 走看看