阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Erlang服务器占用内存偏高的解决方法

205次阅读
没有评论

共计 3096 个字符,预计需要花费 8 分钟才能阅读完成。

问题提出:Erlang 服务器 100 万人在线,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, 服务器编程中, 循环一定确保为尾递归;
2, 尽量使用 OTP, 如果使用 gen_server 替换手写 loop, 就会避免出现该问题。

在 CentOS 上编译安装 Erlang R15B http://www.linuxidc.com/Linux/2012-03/57663.htm

Ubuntu 11.10 搭建 Erlang 环境 http://www.linuxidc.com/Linux/2012-04/58208.htm

在 CentOS 5.7 上通过 YUM 安装 Erlang 过程 http://www.linuxidc.com/Linux/2012-12/75622.htm

Erlang— 启动参数学习 / 研究 http://www.linuxidc.com/Linux/2011-07/39157.htm

CentOS 5.5 下源代码编译安装 Erlang http://www.linuxidc.com/Linux/2011-07/39156.htm

Ubuntu 10.10 下源码安装 Erlang 5.8.3 版本 http://www.linuxidc.com/Linux/2011-04/34863.htm

在 CentOS 6.4 上安装 Erlang http://www.linuxidc.com/Linux/2013-06/85964.htm

Ubuntu 下 Erlang R16B 的安装 http://www.linuxidc.com/Linux/2013-05/84235.htm

Erlang 的详细介绍:请点这里
Erlang 的下载地址:请点这里

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-20发表,共计3096字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中