从MySQL源码看其网络IO模型
从MySQL源码看其网络IO模型前言
MySQL是当今最流行的开源数据库,阅读其源码是一件大有裨益的事情(虽然其代码感觉比较凌乱)。而笔者阅读一个Server源码的习惯就是先从其网络IO模型看起。于是,便有了本篇博客。
MySQL启动Socket监听
看源码,首先就需要找到其入口点,mysqld的入口点为mysqldmain,跳过了各种配置文件的加载
之后,我们来到了networkinit初始化网络环节,如下图所示:
下面是其调用栈:mysqldmain(MySQLServerEntryPoint)
networkinit(初始化网络)
建立tcp套接字
createsocket(AFINET)
mysqlsocketbind(AFINET)
mysqlsocketlisten(AFINET)
建立UNIX套接字
mysqlsocketsocket(AFUNIX)
mysqlsocketbind(AFUNIX)
mysqlsocketlisten(AFUNIX)
值得注意的是,在tcpsocket的初始化过程中,考虑到了ipv4v6的两种情况:首先创建ipv4连接
ipsockcreatesocket(ai,AFINET,a);
如果无法创建ipv4连接,则尝试创建ipv6连接
if(mysqlsocketgetfd(ipsock)INVALIDSOCKET)
ipsockcreatesocket(ai,AFINET6,a);
如果我们以很快的速度stopstartmysql,会出现上一个mysql的listenport没有被release导致无法当前mysql的socket无法bind的情况,在此种情况下mysql会循环等待,其每次等待时间为当前重试次数retryretry31秒,一直到设置的portopentimeout(默认为0)为止,如下图所示:
MySQL新建连接处理循环
通过handleconnectionssockets处理MySQL的新建连接循环,根据操作系统的配置通过pollselect处理循环(非epoll,这样可移植性较高,且mysql瓶颈不在网络上)。
MySQL通过线程池的模式处理连接(一个连接对应一个线程,连接关闭后将线程归还到池中),如下图所示:
对应的调用栈如下所示:handleconnectionssockets
pollselect
newsockmysqlsocketaccept(。。。sock。。。)从listensocket中获取新连接
newTHD连接线程上下文如果获取不到足够内存,则shutdownnewsock
mysqlsocketgetfd(sock)从socket中获取
设置为NONBLOCK和环境有关
fcntl(mysqlsocketgetfd(sock),FSETFL,flagsONONBLOCK);
mysqlsocketvionew
vioinit(VIOTYPETCPIP)
(viowriteviowrite)
默认用的是vioread
(vioread(flagsVIOBUFFEREDREAD)?vioreadbuff:vioread;)
(vioviokeepaliveviokeepalive)tcp层面的keepalive
。。。。。
mysqlnetinit
设置超时时间,最大packet等参数
createnewthread(thd)实际是从线程池拿,不够再新建pthread线程
最大连接数限制
createthreadtohandleconnection
首先看下线程池是否有空闲线程
mysqlcondsignal(CONDthreadcache)有则发送信号
这边的hanldeoneconnection是mysql连接的主要处理函数
mysqlthreadcreate(。。。handleoneconnection。。。)
MySQL的VIO
如上图代码中,每新建一个连接,都随之新建一个vio(mysqlsocketvionewvioinit),在vioinit的过程中,初始化了一堆回掉函数,如下图所示:
我们关注点在vioread和viowrite上,如上面代码所示,在笔者所处机器的环境下将MySQL连接的socket设置成了非阻塞模式(ONONBLOCK)模式。所以在vio的代码里面采用了nonblock代码的编写模式,如下面源码所示:
vioreadsizetvioread(Viovio,ucharbuf,sizetsize)
{
while((retmysqlsocketrecv(viomysqlsocket,(SOCKBUFT)buf,size,flags))1)
{
。。。。。。
如果上面获取的数据为空,则通过select的方式去获取读取事件,并设置超时timeout时间
if((retviosocketiowait(vio,VIOIOEVENTREAD)))
break;
}
}
即通过while循环去读取socket中的数据,如果读取为空,则通过viosocketiowait去等待(借助于select的超时机制),其源码如下所示:viosocketiowait
vioiowait
(retselect(fd1,readfds,writefds,exceptfds,
(timeout0)?tm:))
笔者在jdk源码中看到java的connectiontimeout也是通过这,select(waittime)的方式去实现连接超时的。
由上述源码可以看出,这个mysql的readtimeout是针对每次socketrecv(而不是整个packet的),所以可能出现超过readtimeoutMySQL仍旧不会报错的情况,如下图所示:
viowrite
viowrite实现模式和vioread一致,也是通过select来实现超时时间的判定,如下面源码所示:sizetviowrite(Viovio,constucharbuf,sizetsize)
{
while((retmysqlsocketsend(viomysqlsocket,(SOCKBUFT)buf,size,flags))1)
{
interrorsocketerrno;
Theoperationwouldblock?
处理EAGAIN和EWOULDBLOCK返回,NONBLOCK模式都必须处理
if(error!SOCKETEAGAINerror!SOCKETEWOULDBLOCK)
break;
Waitfortheoutputbuffertobecomewritable。
if((retviosocketiowait(vio,VIOIOEVENTWRITE)))
break;
}
}
MySQL的连接处理线程
从上面的代码:mysqlthreadcreate(。。。handleoneconnection。。。)
可以发现,MySQL每个线程的处理函数为handleoneconnection,其过程如下图所示:
代码如下所示:for(;;){
这边做了连接的handshake和auth的工作
rcthdprepareconnection(thd);
和通常的线程处理一样,一个无限循环获取连接请求
while(thdisconnectionalive(thd))
{
if(docommand(thd))
break;
}
出循环之后,连接已经被clientdu端关闭或者出现异常
这边做了连接的销毁动作
endconnection(thd);
endthread:
。。。
这边调用endthread做清理动作,并将当前线程返还给线程池重用
endthread对应为onethreadperconnectionend
if(MYSQLCALLBACKELSE(threadscheduler,endthread,(thd,1),0))
return;
。。。
这边currentthd是个宏定义,其实是currentthd;
主要是从线程上下文中获取新塞进去的thd
mypthreadgetspecificptr(THD,THRTHD);
thdcurrentthd;
。。。
}
mysql的每个woker线程通过无限循环去处理请求。
线程的归还过程
MySQL通过调用onethreadperconnectionend(即上面的endthread)去归还连接。MYSQLCALLBACKELSE(。。。endthread)
onethreadperconnectionend
thdreleaseresources
。。。。。。
blockuntilnewconnection
线程在新连接尚未到来之前,等待在信号量上(下面代码是CCmutexcondition的标准使用模式):staticboolblockuntilnewconnection
{
mysqlmutexlock(LOCKthreadcount);
。。。。。。
while(!abortloop!wakepthread!killblockedpthreadsflag)
mysqlcondwait(x1,LOCKthreadcount);
。。。。。。
从等待列表中获取需要处理的THD
thdwaitingthdlistfront;
waitingthdlistpopfront;
。。。。。。
将thd放入到当前线程上下文中
mypthreadsetspecificptr(THRTHD,this)
thdstoreglobals;
。。。。。。
mysqlmutexunlock(LOCKthreadcount);
。。。。。
}
整个过程如下图所示:
由于MySQL的调用栈比较深,所以将thd放入线程上下文中能够有效的在调用栈中减少传递参数的数量。
总结
MySQL的网络IO模型采用了经典的线程池技术,虽然性能上不及reactor模型,但好在其瓶颈并不在网络IO上,采用这种方法无疑可以节省大量的精力去专注于处理sql等其它方面的优化。