前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Erlang服务器占用内存偏高的解决方法

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

作者头像
星哥玩云
发布2022-07-04 14:09:42
5.8K0
发布2022-07-04 14:09:42
举报
文章被收录于专栏:开源部署

问题提出: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,就会避免出现该问题。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档