`
litaocheng
  • 浏览: 337642 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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,就不会出现这个问题!
3
1
分享到:
评论
9 楼 simsunny22 2015-11-06  
4年之后我才看到 慢慢的干货
8 楼 yjl49 2012-03-14  
顺便问个问题编译后的字节码咋查看?谢谢
7 楼 yjl49 2012-03-14  
litaocheng 写道
chaoslawful 写道
最好说明一下,try...catch结构中只有try block ....

顶!谢谢指点哦~~~~

文章看了几遍了,觉得原始的问题还没有解决:
以下这段代码的问题出在哪里?
send_msg(Socket, Pid) ->
   try
        receive
            {send, Bin} ->
                ...
            {inet_reply, _Sock, Result} ->
               ...
   catch
       _:_ ->
           send_msg(Sock, Pid)
   end.

按chaoslawful的说法
send_msg(Socket, Pid) ->
   try
        receive
          ...
          ...
        end
   catch
       _:_ ->
           send_msg(Sock, Pid)
   end.

这种结构的代码是没问题的(只要recive ...end 中间没有再调用send_msg/2)
6 楼 zdx3578 2012-02-29  
good 学习力
5 楼 expp 2011-11-04  
记得armstrong爷爷某书中提高erlang做法是任其崩溃,ms不提倡try catch防御式的做法
4 楼 francis333 2011-10-31  
good!  帅哥,我接触erlang没多久,发现确实有其他语言不能比拟的优势,能不能写一篇关于进程字典的文章,包括其用法等等,我目前在开发阶段遇到一个问题,我需要从数据库读取信息,然后逐个执行模块,但是当数据库信息更新后,需要在不停止已运行模块的前提下更新该模块执行的个别参数,从网上看好像可以通过进程字典实现参数的替换,不知道行不行
3 楼 litaocheng 2011-10-26  
chaoslawful 写道
最好说明一下,try...catch结构中只有try block ....

顶!谢谢指点哦~~~~
2 楼 chaoslawful 2011-10-26  
最好说明一下,try...catch结构中只有try block(即try/catch之间的代码)中的函数调用无法构造成尾递归,而catch block(即catch/end之间的代码)中的函数调用则可构造为尾递归形式。故文中代码里
send_msg(Socket, Pid) ->
    try
        receive
            {send, Bin} ->
                ...
            {inet_reply, _Sock, Result} ->
                ...
    catch
        _:_ ->
            [color=red]send_msg(Sock, Pid)[/color]
    end.

有问题的非尾递归调用存在于try/catch间receive中被隐去的部分里,而catch/end之间的那个 send_msg/2 调用则确实是尾递归,不会造成问题。同理,后面测试用例中造成内存占用不断上涨的问题在于 do_t1/0 中 try/catch 间 receive 中的 do_t1/0 调用,而非 catch/end 间的 do_t1/0 调用。

对于有疑问的 Erlang 代码,从 erlc 产生的字节码文件其实可以看得更清楚,尾递归调用会使用 call_last 字节码,而普通调用则使用 call 字节码,一目了然。比如如下代码
-module(x).
-compile(export_all).

x() ->
	try
		x()
	catch
		_:_ -> x()
	end.

生成字节码文件:
$ erlc +"'S'" x.erl
$ cat x.S
...
{function, x, 0, 2}.
  {label,1}.
    {func_info,{atom,x},{atom,x},0}.
  {label,2}.
    {allocate_zero,1,0}.
    {'try',{y,0},{f,3}}.
    [color=red]{call,0,{f,2}}.[/color]
    {try_end,{y,0}}.
    {deallocate,1}.
    return.
  {label,3}.
    {try_case,{y,0}}.
    [color=green]{call_last,0,{f,2},1}.[/color]
...

1 楼 orez88 2011-10-26  
好文章,学习了

相关推荐

    搭建自己的erlang服务器(基于RabbitMQ改进)(一)

    这些工具可以帮助你跟踪进程、查看内存使用情况和监控系统状态。 最后,部署你的定制RabbitMQ服务器。编译后的二进制文件通常位于`ebin`目录下,你可以直接运行它来启动服务器。在生产环境中,你可能需要配置...

    远古封神+英雄远征的ERLANG游戏服务器代码

    《远古封神》与《英雄远征》是两款受欢迎的网络游戏,它们的后端服务器采用了ERLANG这一编程语言来构建。ERLANG是一种为并发、分布式和容错系统设计的函数式编程语言,因其在实时系统和大规模并发处理中的优秀性能而...

    Erlang虚拟机内存管理

    Erlang核心开发者Lukas Larsson在2014年3月份Erlang Factory上的一个演讲详细介绍了Erlang内存体系的原理以及调优案例 根据siyao zheng博客上听写的资源进行的翻译,大致只翻译了80%但核心部分已经完整,希望对大家...

    英雄远征erlang服务器源码含数据库

    【标题】:“英雄远征erlang服务器源码含数据库” 【描述】:“Erlang是一种强大的编程语言,以其优秀的高并发性能和热更新能力而闻名。这个资源包含了一个Erlang服务器的源码,特别适合那些希望快速入门Erlang编程...

    erlang的小型游戏服务器

    在游戏服务器领域,Erlang因其强大的处理大量并发连接的能力而被广泛应用。本项目是一个基于Erlang的小型游戏服务器,使用了Mnesia数据库来存储游戏数据。 Mnesia是Erlang内置的一个分布式数据库管理系统,支持事务...

    Erlang项目内存泄漏分析方法

    比如使用`erlang:memory()`命令可以看到Erlang emulator分配的内存情况,包括总的内存使用量、atom使用的内存和process消耗的内存等。 4. 检查进程创建数量:通过`erlang:system_info(process_count)`可以获取当前...

    erlang服务器集合

    **Erlang服务器集合** Erlang是一种面向并发的、函数式编程语言,特别适合构建高可用性、分布式系统。这个“erlang服务器集合”包含了多个项目,主要围绕`openpoker`和`英雄远征`游戏服务器的源代码,为深入理解...

    Erlang内存管理.pdf

    Erlang的内存管理系统是它的一个核心特性,被设计用来支持高可靠性与容错性,而无需显式的垃圾回收。这种机制在处理并发进程时尤其有用,因为Erlang中的每个进程都有自己的私有堆,互相独立,且易于管理。 在Erlang...

    java php python erlang 千万级内存数据性能比较

    Erlang的字典数据结构通常比其他语言的哈希表更快,因为它优化了并发访问和内存管理。 接下来是Java,它以其跨平台能力和丰富的库而闻名。"java_class_arr_data_test.jar"和"java_string_arr_data_test.jar"可能是...

    erlang整理的一些心得和lunix查看cpu和内存信息的方法

    在IT领域,Erlang是一种强大的并发编程语言,主要用于构建高可用性、容错性和分布式系统。它以其轻量级进程、消息传递和热代码升级等特性而闻名。Unix(包括其衍生版本如Linux)是广泛使用的操作系统,具有丰富的...

    erlang ranch实现的游戏tcp服务器

    8. **日志和监控**:为了调试和优化,我们需要集成日志系统和性能监控工具,如Erlang的observer工具,以便跟踪服务器的运行状态。 9. **代码组织**:游戏服务器的代码通常会分为多个模块,如负责监听的模块、处理...

    一个我自己学习Erlang的聊天室服务器及客户端代码

    【Erlang聊天室服务器及客户端代码解析】 在IT领域,Erlang是一种面向并发、分布式、容错的编程语言,特别适合构建高可用性的实时系统。本项目提供了一个使用Erlang编写的聊天室服务器端代码以及Java编写的客户端...

    高性能集群服务器Erlang解决方案

    - **内置并发**:Erlang通过轻量级进程(每个进程占用的内存很少且创建速度快)实现并发,每个进程间通过消息传递进行通信,避免了共享内存所带来的复杂性和竞争条件问题。 - **集群能力**:Erlang支持集群全连通或...

    EMQTT(Erlang MQTT消息服务器) v2.3.11 for windows7 x64

    Erlang MQTT消息服务器简称EMQTT。EMQTT是采用Erlang语言开发,全面支持MQTT V3.1.1协议,支持集群和大规模连接的开源MQTT消息服务器。EMQTT致力于发布一个基于Erlang/OTP语言平台,企业级稳定可靠,完全开源免费,...

    erlang-java聊天

    Erlang客户端可以直接与Erlang服务器通信,而Java客户端则通过Jinterface与Erlang服务器交互。客户端负责显示聊天界面,接收用户输入,并将消息发送到服务器。 - **消息协议**:为了实现Erlang和Java之间的通信,...

    erlang_ls:Erlang 语言服务器

    erlang_ls 一个实现微软语言服务器协议 3.15 的 Erlang 服务器。最低要求 快速开始编译项目: make要在/usr/local/bin安装生成的erlang_ls escript: make install命令行参数这些是可以提供给erlang_ls脚本的命令行...

    erlang写的一个特别的web服务器

    9. **性能优化**:Erlang的BEAM虚拟机和其内存管理策略有利于高性能服务器的构建。emongrel2可能会针对Erlang环境进行优化,以确保最佳的性能表现。 10. **社区支持和文档**:开源项目通常依赖于社区的支持和文档。...

    erlang编程 Introducing Erlang

    它负责解释Erlang字节码,提供内存管理、垃圾回收和并发调度等功能。 ### 10. 语言特性 Erlang的语法简洁,支持模式匹配、函数式编程、列表处理和递归等特性。它的动态类型系统和强大的类型推断让代码更加灵活。 ...

    erlang25.0 windows版本

    - **行为模式**:如GenServer、GenEvent和Gen_fsm,它们定义了Erlang中服务器、事件处理和状态机的通用行为。 - **Elixir**:基于Erlang VM的现代编程语言,提供了更接近Ruby的语法,同时保留了Erlang的并发特性和...

    erlang游戏服务器网关源码.zip

    Erlang是一种面向并发的、基于进程的编程语言,它在构建高可用性、高性能的游戏服务器网关方面表现优秀。本资源"erlang游戏服务器网关源码.zip"包含四份核心源代码文件:`server_app.erl`、`net_work.erl`、`main....

Global site tag (gtag.js) - Google Analytics