標籤:
提出的問題:server100萬人線上,16G記憶體快被吃光。
玩家進程佔用記憶體偏高
解決方案:
第一步:
erlang:system_info(process_count). 查看進程數目是否正常,是否超過了erlang虛擬機器的最大進程數。
第二步:
查看節點的記憶體瓶頸所在地方
> 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 輸入.)
第四步:查看佔用記憶體最高的進程狀態
>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:erlctest.erl && erl -eval "test:t1()"
版本號碼2:erlctest.erl && erl -eval "test:t2()"
你會看到版本號碼1代碼的呼叫堆疊在不斷增長,記憶體也在增長, 而版本號碼2函數調用地址保持不變,記憶體也沒有發生變化!
總結:
1,server編程中,迴圈一定確保為尾遞迴
2,盡量使用OTP,假設使用gen_server更換手寫loop,將避免這個問題
著作權聲明:本文部落格原創文章,部落格,未經同意,不得轉載。
Erlangserver緊記憶體最佳化解決方案