一次db连接泄漏问题排查
- 预备知识
- 问题描述
- 问题定位
- 问题验证
- 问题修复
- 其它
预备知识
- recon库的使用,详见<<erlang in danger>>
- mongodb-erlang库
问题描述
最近从监控平台发现db的可用连接数长时间没有回复到初始值,怀疑是有db连接泄漏。截图如下
登录到终端后,执行
<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而且又没有归还,最好还要找到调用的方法。
- erlang节点终端使用
- 看了看源码,不支持调用的信息的存储。只有自己写一个简单的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方法后,发布,问题修复,截图如下
其它
- mongodb驱动写法可以优化下:调用方不必每次调用前手动获取conn,调用后手动归还conn
- 可视化监控非常重要,就是程序员的眼睛。没有这个,就是瞎子