erlang_mysql_driver源码阅读笔记

本文是关于erlang_mysql_driver源码的阅读笔记,详细介绍了mysql_dispatcher进程的启动、初始化、重连机制以及mysql_conn进程的启动和初始化过程。内容包括连接池的管理、fetch操作的调度算法,以及mysql_recv进程的启动和初始化。通过阅读源码,理解了驱动如何处理数据库连接和异常恢复。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

erlang_mysql_driver源码阅读笔记

最近项目里发现了一个比较严重的bug,线上服务器游戏服创建了大量的mysql连接没有释放,轻松达到了设定的连接数上限,因为使用了第三方的驱动erlang_mysql_driver,排查的过程中就研究了下驱动的源码。

结构

代码比较简单,四个文件组成:
mysql.erl:唯一一个behavior为gen_server的模块,主要封装了一些外部调用的api(比如执行sql语句)以及管理连接池。
mysql_conn.erl:负责处理一条与mysql数据库的连接,可单独使用,也可通过mysql模块管理多条连接,甚至可以连接到不同的服务器。sql语句的执行和返回结果也是由这个模块处理的。
mysql_recv.erl:负责实际与mysql的tcp连接,将结果返回给父进程(mysql_conn).
mysql_auth.erl:提供给mysql_conn模块调用的接口,认证相关,可以忽略。

mysql_dispatcher进程启动与初始化

启动

mysql模块提供两种启动方式:start_link和start,实际上都是由start1函数处理,区别就是start1最终调用的是gen_server:start_link还是gen_server:start(前者可以启动在supervisor监督树下,后者则是启动独立的进程)。
要注意的是,mysql启动的进程别名不是mysql而是mysql_dispatcher.

初始化

init([PoolId, Host, Port, User, Password, Database, LogFun, Encoding]) ->
    LogFun1 = if LogFun == undefined -> fun log/4; true -> LogFun end,
    case mysql_conn:start(Host, Port, User, Password, Database, LogFun1,
              Encoding, PoolId) of
    {
   ok, ConnPid} ->
        Conn = new_conn(PoolId, ConnPid, true, Host, Port, User, Password,
                Database, Encoding),
        State = #state{
   log_fun = LogFun1},
        {
   ok, add_conn(Conn, State)};
    {
   error, _Reason} ->
        ?Log(LogFun1, error,
         "failed starting first MySQL connection handler, "
         "exiting"),
         C = #conn{
   pool_id = PoolId,
          reconnect = true,
          host = Host,
          port = Port,
          user = User,
          password = Password,
          database = Database,
          encoding = Encoding},
          start_reconnect(C, LogFun),
        {
   ok, #state{
   log_fun = LogFun1}}
    end.

init函数主要做了两件事:
一是启动一个mysql_conn进程,如果正常启动就继续,如果启动失败则调用msql:start_reconnect/2进行重连。
二是如果启动成功,调用mysql:add_conn/2将启动的连接进程信息(比如pid,poolId,端口等)保存到#state{}中。

add_conn(Conn, State) ->
    Pid = Conn#conn.pid,
    erlang:monitor(process, Conn#conn.pid),
    PoolId = Conn#conn.pool_id,
    ConnPools = State#state.conn_pools,
    NewPool = 
	case gb_trees:lookup(PoolId, ConnPools) of
	    none ->
		{
   [Conn],[]};
	    {
   value, {
   Unused, Used}} ->
		{
   [Conn | Unused], Used}
	end,
    State#state{
   conn_pools =
		gb_trees:enter(PoolId, NewPool,
			       ConnPools),
		pids_pools = gb_trees:enter(Pid, PoolId,
					    State#state.pids_pools)}.

这个add_conn函数值得仔细关注下:

  1. 当调用add_conn时,mysql_dispatcher首先会调用erlang:monitor建立一个进程的单向监控,目的是当mysql_conn异常挂掉时监控进程mysql_dispatcher收到来自于子进程的‘DOWN’消息.
  2. mysql_dispatcher进程维护两个数据结构——ConnPools连接池和PidPools进程池。使用gb_trees二叉平衡树存储key-value类型的数据结构:
    a. ConnPools:key为PoolId::term(连接池id),value为{Unused::Lists, Used::Lists}元组,其中Unused为未使用的连接列表,Used为使用中的连接列表。
    b. PidPools:key为Pid::pid(进程id),value为PoolId::term(连接池id)

mysql_dispather的重连机制

当mysql:init/1启动mysql:start失败或者当收到‘DOWN’消息且#conn.reconnect字段为true时,首先将旧的连接通过mysql:remove_conn/2移除,然后触发调用mysql:start_reconnect/2函数进行重连。

start_reconnect(Conn, LogFun) ->
    Pid = spawn(fun () ->
      process_flag(trap_exit,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值