文档章节

从MySQL源码看其网络IO模型

无毁的湖光-Al
 无毁的湖光-Al
发布于 06/26 13:22
字数 1588
阅读 4364
收藏 78

从MySQL源码看其网络IO模型

前言

MySQL是当今最流行的开源数据库,阅读其源码是一件大有裨益的事情(虽然其代码感觉比较凌乱)。而笔者阅读一个Server源码的习惯就是先从其网络IO模型看起。于是,便有了本篇博客。

MySQL启动Socket监听

看源码,首先就需要找到其入口点,mysqld的入口点为mysqld_main,跳过了各种配置文件的加载 之后,我们来到了network_init初始化网络环节,如下图所示:

下面是其调用栈:

mysqld_main (MySQL Server Entry Point)
	|-network_init (初始化网络)
		/* 建立tcp套接字 */
		|-create_socket (AF_INET)
		|-mysql_socket_bind (AF_INET)
		|-mysql_socket_listen (AF_INET)
		/* 建立UNIX套接字*/
		|-mysql_socket_socket (AF_UNIX)
		|-mysql_socket_bind (AF_UNIX)
		|-mysql_socket_listen (AF_UNIX)

值得注意的是,在tcp socket的初始化过程中,考虑到了ipv4/v6的两种情况:

// 首先创建ipv4连接
ip_sock= create_socket(ai, AF_INET, &a);
// 如果无法创建ipv4连接,则尝试创建ipv6连接
if(mysql_socket_getfd(ip_sock) == INVALID_SOCKET)
 	ip_sock= create_socket(ai, AF_INET6, &a);

如果我们以很快的速度stop/start mysql,会出现上一个mysql的listen port没有被release导致无法当前mysql的socket无法bind的情况,在此种情况下mysql会循环等待,其每次等待时间为当前重试次数retry * retry/3 +1秒,一直到设置的--port-open-timeout(默认为0)为止,如下图所示:

MySQL新建连接处理循环

通过handle_connections_sockets处理MySQL的新建连接循环,根据操作系统的配置通过poll/select处理循环(非epoll,这样可移植性较高,且mysql瓶颈不在网络上)。
MySQL通过线程池的模式处理连接(一个连接对应一个线程,连接关闭后将线程归还到池中),如下图所示:
对应的调用栈如下所示:

handle_connections_sockets
	|->poll/select
	|->new_sock=mysql_socket_accept(...sock...) /*从listen socket中获取新连接*/
	|->new THD 连接线程上下文 /* 如果获取不到足够内存,则shutdown new_sock*/
	|->mysql_socket_getfd(sock) 从socket中获取
		/** 设置为NONBLOCK和环境有关 **/
	|->fcntl(mysql_socket_getfd(sock), F_SETFL, flags | O_NONBLOCK);
	|->mysql_socket_vio_new
		|->vio_init (VIO_TYPE_TCPIP)
			|->(vio->write = vio_write)
			/* 默认用的是vio_read */
			|->(vio->read=(flags & VIO_BUFFERED_READ) ?vio_read_buff :vio_read;)
			|->(vio->viokeepalive = vio_keepalive) /*tcp层面的keepalive*/
			|->.....
	|->mysql_net_init
		|->设置超时时间,最大packet等参数
	|->create_new_thread(thd) /* 实际是从线程池拿,不够再新建pthread线程 */
		|->最大连接数限制
		|->create_thread_to_handle_connection
			|->首先看下线程池是否有空闲线程
				|->mysql_cond_signal(&COND_thread_cache) /* 有则发送信号 */
			/** 这边的hanlde_one_connection是mysql连接的主要处理函数 */
			|->mysql_thread_create(...handle_one_connection...)			

MySQL的VIO

如上图代码中,每新建一个连接,都随之新建一个vio(mysql_socket_vio_new->vio_init),在vio_init的过程中,初始化了一堆回掉函数,如下图所示:
我们关注点在vio_read和vio_write上,如上面代码所示,在笔者所处机器的环境下将MySQL连接的socket设置成了非阻塞模式(O_NONBLOCK)模式。所以在vio的代码里面采用了nonblock代码的编写模式,如下面源码所示:

vio_read

size_t vio_read(Vio *vio, uchar *buf, size_t size)
{
  while ((ret= mysql_socket_recv(vio->mysql_socket, (SOCKBUF_T *)buf, size, flags)) == -1)
  {
    ......
    // 如果上面获取的数据为空,则通过select的方式去获取读取事件,并设置超时timeout时间
    if ((ret= vio_socket_io_wait(vio, VIO_IO_EVENT_READ)))
        break;
  }
}

即通过while循环去读取socket中的数据,如果读取为空,则通过vio_socket_io_wait去等待(借助于select的超时机制),其源码如下所示:

vio_socket_io_wait
	|->vio_io_wait
		|-> (ret= select(fd + 1, &readfds, &writefds, &exceptfds, 
              (timeout >= 0) ? &tm : NULL))

笔者在jdk源码中看到java的connection time out也是通过这,select(...wait_time)的方式去实现连接超时的。
由上述源码可以看出,这个mysql的read_timeout是针对每次socket recv(而不是整个packet的),所以可能出现超过read_timeout MySQL仍旧不会报错的情况,如下图所示:

vio_write

vio_write实现模式和vio_read一致,也是通过select来实现超时时间的判定,如下面源码所示:

size_t vio_write(Vio *vio, const uchar* buf, size_t size)
{
  while ((ret= mysql_socket_send(vio->mysql_socket, (SOCKBUF_T *)buf, size, flags)) == -1)
  {
    int error= socket_errno;

    /* The operation would block? */
    // 处理EAGAIN和EWOULDBLOCK返回,NON_BLOCK模式都必须处理
    if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK)
      break;

    /* Wait for the output buffer to become writable.*/
    if ((ret= vio_socket_io_wait(vio, VIO_IO_EVENT_WRITE)))
      break;
  }
}

MySQL的连接处理线程

从上面的代码:

mysql_thread_create(...handle_one_connection...)

可以发现,MySQL每个线程的处理函数为handle_one_connection,其过程如下图所示:


代码如下所示:

for(;;){
	// 这边做了连接的handshake和auth的工作
	rc= thd_prepare_connection(thd);
	// 和通常的线程处理一样,一个无限循环获取连接请求
	while(thd_is_connection_alive(thd))
	{
		if(do_command(thd))
			break;
	}
	// 出循环之后,连接已经被clientdu端关闭或者出现异常
	// 这边做了连接的销毁动作
	end_connection(thd);
end_thread:
	...
	// 这边调用end_thread做清理动作,并将当前线程返还给线程池重用
	// end_thread对应为one_thread_per_connection_end
	if (MYSQL_CALLBACK_ELSE(thread_scheduler, end_thread, (thd, 1), 0))
		return;	
	...
	// 这边current_thd是个宏定义,其实是current_thd();
	// 主要是从线程上下文中获取新塞进去的thd
	// my_pthread_getspecific_ptr(THD*,THR_THD);
	thd= current_thd;
	...
}

mysql的每个woker线程通过无限循环去处理请求。

线程的归还过程

MySQL通过调用one_thread_per_connection_end(即上面的end_thread)去归还连接。

MYSQL_CALLBACK_ELSE(...end_thread)
	one_thread_per_connection_end
		|->thd->release_resources()
		|->......
		|->block_until_new_connection

线程在新连接尚未到来之前,等待在信号量上(下面代码是C/C++ mutex condition的标准使用模式):

static bool block_until_new_connection()
{	
	mysql_mutex_lock(&LOCK_thread_count);
	......
    while (!abort_loop && !wake_pthread && !kill_blocked_pthreads_flag)
      mysql_cond_wait(&x1, &LOCK_thread_count);
   ......
   // 从等待列表中获取需要处理的THD
   thd= waiting_thd_list->front();
   waiting_thd_list->pop_front();
   ......
   // 将thd放入到当前线程上下文中
   // my_pthread_setspecific_ptr(THR_THD,  this)    
   thd->store_globals();
   ......
   mysql_mutex_unlock(&LOCK_thread_count);
   .....
}

整个过程如下图所示:


由于MySQL的调用栈比较深,所以将thd放入线程上下文中能够有效的在调用栈中减少传递参数的数量。

总结

MySQL的网络IO模型采用了经典的线程池技术,虽然性能上不及reactor模型,但好在其瓶颈并不在网络IO上,采用这种方法无疑可以节省大量的精力去专注于处理sql等其它方面的优化。

原文链接

https://my.oschina.net/alchemystar/blog/3048760

© 著作权归作者所有

无毁的湖光-Al

无毁的湖光-Al

粉丝 385
博文 29
码字总数 48025
作品 0
浦东
后端工程师
私信 提问
加载中

评论(7)

h
hsmOsChina
看了博主的文章,感觉茅塞顿开,感觉像博主那样能举重若轻把MySQL底层代码讲清楚的,这样的人并不多。
无毁的湖光-Al
无毁的湖光-Al 博主
😎
JPer
JPer
官方团队有没有考虑采用NIO呢;
无毁的湖光-Al
无毁的湖光-Al 博主
reactor模式感觉是没有的,(NONBLOCK IO)的话现在就是了
JPer
JPer
对,我就说reactor模型
h
heike07
看不懂 看不懂 ~哈哈 也不敢说 也不敢问 没评论的是不是都这个心态 那么总要有一个第一人 我来吧。
无毁的湖光-Al
无毁的湖光-Al 博主
@heike07 ^_^ C语言的玩法比较繁琐
Redis 专栏(使用介绍、源码分析、常见问题...)

来源http://blog.csdn.net/yangbodong22011/article/details/78529448 https://github.com/hurley25 https://github.com/hurley25/ANet ANet 基于Redis网络模型的简易网络库,网络模块代码取......

libaineu2004
2017/12/16
0
0
MySQL · 特性分析 · 5.7 代价模型浅析

代价模型 mysql 5.7代价计算相对之前的版本有较大的改进。例如 代价模型参数可以动态配置,可以适应不同的硬件 区分考虑数据在内存和在磁盘中的代价 代价精度提升为浮点型 jion计算时不仅要考...

阿里云RDS-数据库内核组
2016/07/03
0
0
Redis网络库源码浅解

Redis网络模型是一个使用IO多路复用、单线程、非阻塞的模型。这个模型的优点在于单线程不用考虑加锁,如果在单核环境上可以将效率发挥到最大。它没有那么庞大,代码一共2000多行,因此比较容...

Tanswer_
2017/08/12
0
0
java bio,nio,aio及源码

版权声明:本文为博主原创文章,未经博主允许不得转载。 目录(?)[+] NIO 简介 随着JavaIO类库的不断发展和改进,基于Java的网络编程会变得越来越简单。随着异步IO功能的增强,基于JavaNIO开发...

onedotdot
2017/10/20
0
0
我的开源项目汇总(机器&深度学习、NLP、网络IO、AIML、mysql协议、chatbot)

TextAnalyzer https://github.com/sea-boat/TextAnalyzer A text analyzer which is based on machine learning,statistics and dictionaries that can analyze text. So far, it supports h......

超人汪小建
2018/07/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux输入法fcitx的安装问题

Fcitx 总共要安装的包如下 fcitxfcitx-binfcitx-config-commonfcitx-config-gtk | fcitx-config-gtk2fcitx-datafcitx-frontend-allfcitx-frontend-gtk2fcitx-frontend-gtk3......

CHONGCHEN
今天
4
0
网络基础

前言: 最近整理一些以前的学习笔记(有部分缺失,会有些乱,日后再补)。 过去都是存储在本地,此次传到网络留待备用。 计算机网络的功能: 1.数据通信; 2.资源共享; 3.增加数据可靠性; 4....

迷失De挣扎
今天
7
0
spring boot升级到spring cloud

1、先升级spring boot 版本到2.1.3 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.3.RELEAS......

moon888
今天
12
0
从蓝鲸视角谈DevOps

DevOps源于Development和Operations的组合 常见的定义 DevOps是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变...

嘉为科技
今天
1
0
微服务设计 笔记

微服务设计 一、微服务架构理论 1.六边形架构 1)六边形架构(Hexagonal Architecture),又称为端口和适配器架构风格;使用适配器与外界进行交互,外界通过应用层API与内部进行交互。 2)经典...

啃不动地大坚果
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部