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

一次db连接泄漏问题排查

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

问题描述

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

一次线上db连接泄漏问题排查_第1张图片
屏幕快照 2016-10-12 下午6.02.04.png

登录到终端后,执行


mongo_comm:get_info().

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

问题定位

  • 首先排查最近发布的代码,使用git diff命令,查看最近三次发布的变化。发现均没有修改mongodb的模块
  • 其次怀疑是频繁使用mongo,导致恰好执行命令查看可用的连接的时候,都有进程在使用连接,那么需要验证使用的数据库连接进程号是不是每次都是一样的,如果每次都是一样的,那么肯定就是泄漏了。
    • erlang节点终端使用

      lists:map(Fun,gen_server:call(mongo_pool_be_logic, get_all_workers)) -- gen_server:call(mongo_pool_be_logic, get_avail_workers)

      erlang节点中,使用命令找出使用的进程pid,发现结果总是如下

      [<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>]

      看来确实有连接泄漏(此时连接归还的策略是fifo,还不是lifo.如果是lifo,还不能证明问题所在),现在的问题是要如何找到哪个进程使用这个conn而且又没有归还,最好还要找到调用的方法。
  • 看了看源码,不支持调用的信息的存储。只有自己写一个简单的ets表来存储调用的上下文。思路是:在获取conn之后,记录调用方的pid和进程上下文;归还连接的时候,从ets表中删除。如果发现了没有归还的连接,立刻调用ets:tab2list()来查看数据,即可定位。

    -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).
  • 修改代码

    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).

问题验证

  • 重新部署后,过了半个小时,发现开始有未归坏的连接了,立刻进入终端执行

    ets:tab2list(ets_mongo_pool_be_logic)

    结果如下

    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}]}]}}.

已经找到了调用方了mongo_exchange:get_order/2。
查看源代码,发现果然有问题


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;

代码中缺少了关闭连接的方法调用!

问题修复

  • mongo_exchange:get_order加上close_conn方法后,发布,问题修复,截图如下
一次线上db连接泄漏问题排查_第2张图片
屏幕快照 2016-10-13 上午9.35.09.png

其它

  • mongodb驱动写法可以优化下:调用方不必每次调用前手动获取conn,调用后手动归还conn
  • 可视化监控非常重要,就是程序员的眼睛。没有这个,就是瞎子

你可能感兴趣的:(一次线上db连接泄漏问题排查)