一次线上db连接泄漏问题排查

一次db连接泄漏问题排查

  • 预备知识
  • 问题描述
  • 问题定位
  • 问题验证
  • 问题修复
  • 其它
预备知识
  • recon库的使用,详见<<erlang in danger>>
  • mongodb-erlang库

问题描述

最近从监控平台发现db的可用连接数长时间没有回复到初始值,怀疑是有db连接泄漏。截图如下

屏幕快照 2016-10-12 下午6.02.04.png

登录到终端后,执行
<pre>
mongo_comm:get_info().
</pre>

确实验证了这个问题,长时间可用的连接维持在26,甚至更低,而连接池初始大小为30,正常情况下,应该回归到30

问题定位

  • 首先排查最近发布的代码,使用git diff命令,查看最近三次发布的变化。发现均没有修改mongodb的模块
  • 其次怀疑是频繁使用mongo,导致恰好执行命令查看可用的连接的时候,都有进程在使用连接,那么需要验证使用的数据库连接进程号是不是每次都是一样的,如果每次都是一样的,那么肯定就是泄漏了。
    • erlang节点终端使用
      <pre>
      lists:map(Fun,gen_server:call(mongo_pool_be_logic, get_all_workers)) -- gen_server:call(mongo_pool_be_logic, get_avail_workers)
      </pre>
      erlang节点中,使用命令找出使用的进程pid,发现结果总是如下
      <pre>
      [<0.1610.0>,<0.1596.0>,<0.1613.0>,<0.1614.0>,<0.1599.0>,
      <0.1601.0>,<0.1586.0>,<0.1602.0>,<0.1604.0>,<0.1589.0>,
      <0.1605.0>]
      </pre>
      看来确实有连接泄漏(此时连接归还的策略是fifo,还不是lifo.如果是lifo,还不能证明问题所在),现在的问题是要如何找到哪个进程使用这个conn而且又没有归还,最好还要找到调用的方法。
  • 看了看源码,不支持调用的信息的存储。只有自己写一个简单的ets表来存储调用的上下文。思路是:在获取conn之后,记录调用方的pid和进程上下文;归还连接的时候,从ets表中删除。如果发现了没有归还的连接,立刻调用ets:tab2list()来查看数据,即可定位。
    <pre>
    -spec insert({Conn :: pid(), Pid :: pid(), Current :: term()}) -> ok.
    insert({Conn, Pid, Current}) ->
    ets:insert(?POOLNAME_ETS, {Conn, Pid, Current}).
    -spec delete(Conn :: pid()) -> ok.
    delete(Conn) ->
    ets:delete(?POOLNAME_ETS, Conn).
    </pre>
  • 修改代码
    <pre>
    get_conn() ->
    Conn = mongo_pool:checkout(?POOLNAME),
    insert({Conn, self(), recon:info(self(), 'current_stacktrace')}),
    {ok, Conn}.
    get_info() ->
    mongo_pool:status(?POOLNAME).
    close_conn(Conn) ->
    delete(Conn),
    mongo_pool:checkin(?POOLNAME, Conn).
    </pre>

问题验证

  • 重新部署后,过了半个小时,发现开始有未归坏的连接了,立刻进入终端执行
    <pre>
    ets:tab2list(ets_mongo_pool_be_logic)
    </pre>
    结果如下
    <pre>
    ets:tab2list(ets_mongo_pool_be_logic).
    [{<0.1606.0>,<0.2923.0>,
    {current_stacktrace,[{recon,proc_info,2,
    [{file,"src/recon.erl"},{line,231}]},
    {mongo_comm,get_conn,0,
    [{file,"src/mongo/mongo_comm.erl"},{line,51}]},
    {mongo_exchange,get_order,2,
    [{file,"src/mongo/mongo_exchange.erl"},{line,53}]},
    {exchange_manager,buy,2,
    [{file,"src/manager/exchange_manager.erl"},{line,51}]},
    {gen_server,try_handle_call,4,
    [{file,"gen_server.erl"},{line,629}]},
    {gen_server,handle_msg,5,
    [{file,"gen_server.erl"},{line,661}]},
    {proc_lib,init_p_do_apply,3,
    [{file,"proc_lib.erl"},{line,240}]}]}}.
    </pre>

已经找到了调用方了mongo_exchange:get_order/2。
查看源代码,发现果然有问题
<pre>
get_order(id, Id) ->
{ok, Conn} = mongo_comm:get_conn(),
case mongo:find_one(Conn, ?COLLECTION_ORDER, {'_id', Id}) of
{} -> no_order;
{Ans} ->
bson_to_order(Ans)
end;
</pre>
代码中缺少了关闭连接的方法调用!

问题修复

  • mongo_exchange:get_order加上close_conn方法后,发布,问题修复,截图如下
屏幕快照 2016-10-13 上午9.35.09.png

其它

  • mongodb驱动写法可以优化下:调用方不必每次调用前手动获取conn,调用后手动归还conn
  • 可视化监控非常重要,就是程序员的眼睛。没有这个,就是瞎子
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,991评论 19 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,778评论 18 399
  • **冬,与众老师赴济南,偶有闲暇,不堪众人聒噪,幸离千佛山不远,遂游之,有是记。 我只身一人,攀险石,走狭路,探险...
    东营王建军阅读 193评论 0 2
  • 想想,所以成才,
    你最爱我的那颗牙阅读 211评论 0 0
  • Document Types中可以设置应用跟文件的关联。比如你开发了一个图片应用,可以设置双击图片时自动运行你的应...
    开发者老岳阅读 2,445评论 0 1