erlang mysql driver_erlang_mysql_driver 源码分析1-程序员宅基地

技术标签: erlang mysql driver  

erlang_mysql_driver 是个mysql的数据库驱动

源码主要包含 mysql mysql_conn mysql_recv mysql_auth 这几个模块

mysql模块提供给外部调用的接口,包括启动、添加连接、执行sql语句。

mysql模块的另一主要功能是维护mysql_conn连接池,在执行sql语句时,选择合适的mysql_conn进程进行sql处理。

mysql_conn 和 mysql_recv 负责具体的sql执行逻辑和获取返回结果。

一、mysql模块的启动

1、mysql提供了两种启动方式 start 和start_link

start1(PoolId, Host, Port, User, Password, Database, LogFun, Encoding,

StartFunc) ->

crypto:start(),

gen_server:StartFunc(

{local, ?SERVER}, ?MODULE,

[PoolId, Host, Port, User, Password, Database, LogFun, Encoding], []).

我们可以看上面代码,通过传入StartFunc值的不同(start_link或start),最终分别调用gen_server:start和gen_server:start_link

关于gen_server:start和gen_server:start_link的异同,我们可以查看官方文档:

gen_server:start启动一个独立的gen_server进程:

Creates a stand-alone gen_server process, i.e. a gen_server which is not part of a supervision tree and thus has no supervisor.

gen_server:start_link在监督树下启动一个gen_server进程:

Creates a gen_server process as part of a supervision tree. The function should be called, directly or indirectly, by the supervisor. It will, among other things, ensure that the gen_server is linked to the supervisor.

回到正题,当我们调用mysql:start/start_link后

这里启动的进程名是 mysql_dispatcher,并不是mysql,回调模块才是mysql。

启动成功后,通过sys:get_status(mysql_dispatcher) 可以查看mysql_dispatcher的state状态。

2、启动时的初始化准备

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.

调用gen_server:start或gen_server:start_link时,init方法启动一个mysql_conn进程,并且在mysql_conn成功启动后将mysql_conn的进程信息记录在gen_server state的结构中,方便下次使用时,直接在state中找到需要的mysql_conn进程。

二、添加mysql_conn进程

1、mysql_conn进程 与 mysql:connect

mysql:connect 启动一个mysql_conn进程,(mysql_conn进程会建立一个和指定mysql数据库的连接),启动成功后记录下mysql_conn的进程ID。

这里在调用mysql:connect方法的时候,需要提供pool_id(atom),用来表示将这个进程添加到具体 哪个连接池中。

另外需要注意的是,mysql_conn进程与mysql_dispacher之间并不是父子进程关系,mysql_conn与调用这个方法(mysql:connect)的进程才具有监督关系(如果start_link)的话,mysql_dispacher只是在state上记录mysql_conn的pid。

connect(PoolId, Host, Port, User, Password, Database, Encoding, Reconnect,

LinkConnection) ->

Port1 = if Port == undefined -> ?PORT; true -> Port end,

Fun = if LinkConnection ->

fun mysql_conn:start_link/8;

true ->

fun mysql_conn:start/8

end,

{ok, LogFun} = gen_server:call(?SERVER, get_logfun),

case Fun(Host, Port1, User, Password, Database, LogFun,

Encoding, PoolId) of

{ok, ConnPid} ->

Conn = new_conn(PoolId, ConnPid, Reconnect, Host, Port1, User,

Password, Database, Encoding),

case gen_server:call(

?SERVER, {add_conn, Conn}) of

ok ->

{ok, ConnPid};

Res ->

Res

end;

Err->

Err

end.

2、 mysql:connect

1、启动mysql_conn

2、如果顺利启动了mysql_conn,则call add_conn消息给mysql_dispacher

发送add_conn消息给mysql_dispacher的时候,我们可以注意下,record #conn,在mysql里使用一个#conn来表示一个mysql连接。

#conn里也包含了一个mysql连接所需的数据(host ip port database encoding等信息),还有就是 pool_id和本身的pid。

三、mysql_dispacher是怎么处理的 add_conn 的消息的?

mysql_dispacher 对于这个消息的处理,都封装在 mysql:add_conn里

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

1、monitor 传过来的pid,这个monitor的目的是可以在mysql_conn挂掉的时候,收到一条通知消息{‘DOWN’…},具体可以查看erlang:monitor文档

2、修改mysql_dispacher中的两个重要的数据 conn_pools和pid_pools

这两个数据结构都是使用的gb_trees,事实上state中有3个元素是使用gb_trees的。gb_trees就是二叉平衡树,使用二叉平衡树的目的是为了快速查找。

conn_pools :连接池,如果添加连接的时候,发现这个连接对应的连接池PoolId并不存在,则新建一个PoolId

pid_pools: 进程池,添加mysql_conn对应的进程到进程池中

3、返回最终的State

四、conn_pools 的维护

mysql_dispatcher进程的主要作用就是维护多个连接池,管理连接池的连接。具体的业务就是,当用户请求使用连接时选择合适的连接,并记录该连接的使用情况。再到用户使用完连接收到消息,重新记录下连接的使用情况。

这个业务过程就是很常见的池的处理了,如emysql在处理这样情况的时候就是对连接进行分组,分为可用队列和已在使用队列,然后玩家请求则从可用组取,放入使用队列,用后再放回可用队列。

然而erlang_mysql_driver并不是这样处理的,但是他的处理也是一种不错的手段,对以后(进程池、连接池等)池的处理可以作为参考,所以我们重点关心mysql_dispatcher对于conn_pools的维护。

(注:conn_pools的存储位置在mysql_dispatcher中state中)

1、conn_pools与pool_id的关系

gen_server进程mysql_dispatcher的state里有一个元素是conn_pools,conn_pools以gb_trees的形式维护所有的pool_id。

一个pool_id对应gb_trees上的一个节点,每次新添加一个关于pool_id_1的conn的时候。会现在conn_pools中寻找是否存在pool_id_1,

{value, ConnList} = gb_trees:lookup(pool_id_1, ConnPools),如果已经存在则已添加列表的形式,将Conn添加到ConnList的列表头上。

none = gb_trees:lookup(pool_id_1, ConnPools),如果不存在pool_id_1,则新建一个,最终gb_trees:enter到原先的conn_pools中。

也就是说,conn_pools是一个gb_trees的数据结构,而pool_id为这个结构上的key。

而这个key对应的value是ConnList。ConnList是一个列表,该列表的形式:

[{#conn{}, Num}, {#conn{}, Num}, {#conn{}, Num}…],一个#conn表示一个mysql_conn进程,Num表示该进程当前执行的sql数。

2、为什么要使用gb_trees ?

这里我们可以看到,在添加连接到连接池的操作中,pool_id的查询操作是需要经常用到的。

频繁地查询使用二叉查找树可以提高查询的效率。

不过在实际使用中,并不会有太多的连接池,一般来说有一个连接池专门负责select操作,一个连接池负责insert、delete和update操作,就可以了。

3、当我们调用mysql:fetch(PoolId, Query)的时候,mysql_dispatcher是怎么选择mysql_conn进程来执行的?

查看代码 mysql:get_next_conn(PoolId, State)

1、 在mysql_dispatcher的state里的conn_pools中找到PoolId

如果这个时候连PoolId都找不到的话,就失败了,证明不存在这个连接池。

或者说找到后,发现这个连接池里的conn数为空,那也失败了。这种情况正常逻辑下不会出现,因为在一开始的时候都是通过启动mysql_conn才添加pool的。

2、lookup PoolId后,会获得一个列表,列表里面就是所有可用的conn,[{conn1, num1}, {conn2, num2}, {conn3, num3}…]。

每个conn后面都会有一个num,num表示当前这个conn被调用的次数。

这里我们会直接选择列表里的第一个Conn,选择后再把这个Conn对应的Num值加1。

最后根据Num值从小到大排序一遍,那么列表开头的conn又是被调用次数最少的一个。

下次调用的时候,有继续上面的过程。

所以回顾我们之前说的add_connect的过程,新添加的conn,其num为0,直接添加到列表头。

这里面还是有一个效率问题,就是每一次执行sql语句,其实都是需要对连接池里的连接进行重新排序的。

4、sql执行完成后,Num数据减一

在上面,我们可以看到,当mysql_dispatcher发消息给mysql_conn的时候,该Conn对应的Num是会加一的。那么当mysql_conn执行完sql后,他又会发消息回来给mysql_dispatcher,将Num减一。

具体的这个处理过程,可以查看mysql_dispatcher:query_done。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39548740/article/details/113564462

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include<stdio.h>#include<string.h>#include<stdlib.h>#include<malloc.h>#include<iostream>#include<stack>#include<queue>using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签