从MySQL源码看其网络IO模型

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 从MySQL源码看其网络IO模型前言MySQL是当今最流行的开源数据库,阅读其源码是一件大有裨益的事情(虽然其代码感觉比较凌乱)。而笔者阅读一个Server源码的习惯就是先从其网络IO模型看起。于是,便有了本篇博客。

从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)
AI 代码解读

值得注意的是,在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);
AI 代码解读

如果我们以很快的速度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...)            
AI 代码解读

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;
  }
}
AI 代码解读

即通过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))
AI 代码解读

笔者在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;
  }
}
AI 代码解读

MySQL的连接处理线程

从上面的代码:

mysql_thread_create(...handle_one_connection...)
AI 代码解读

可以发现,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;
    ...
}
AI 代码解读

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
AI 代码解读

线程在新连接尚未到来之前,等待在信号量上(下面代码是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);
   .....
}
AI 代码解读

整个过程如下图所示:


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

总结

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

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
VB6网络通信软件上位机开发,TCP网络通信,读写数据并处理,完整源码下载
本文介绍使用VB6开发网络通信上位机客户端程序,涵盖Winsock控件的引入与使用,包括连接服务端、发送数据(如通过`Winsock1.SendData`方法)及接收数据(利用`Winsock1_DataArrival`事件)。代码实现TCP网络通信,可读写并处理16进制数据,适用于自动化和工业控制领域。提供完整源码下载,适合学习VB6网络程序开发。 下载链接:[完整源码](http://xzios.cn:86/WJGL/DownLoadDetial?Id=20)
67 12
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
105 23
基于ssm的网络直播带货管理系统,附源码+数据库+论文
该项目为网络直播带货网站,包含管理员和用户两个角色。管理员可进行主页、个人中心、用户管理、商品分类与信息管理、系统及订单管理;用户可浏览主页、管理个人中心、收藏和订单。系统基于Java开发,采用B/S架构,前端使用Vue、JSP等技术,后端为SSM框架,数据库为MySQL。项目运行环境为Windows,支持JDK8、Tomcat8.5。提供演示视频和详细文档截图。
69 10
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Python 高级编程与实战:深入理解网络编程与异步IO
在前几篇文章中,我们探讨了 Python 的基础语法、面向对象编程、函数式编程、元编程、性能优化、调试技巧、数据科学、机器学习、Web 开发和 API 设计。本文将深入探讨 Python 在网络编程和异步IO中的应用,并通过实战项目帮助你掌握这些技术。
docker pull mysql:8.0.26提示Error response from daemon: Get “https://registry-1.docker.io/v2/“: EOF错误
docker pull mysql:8.0.26提示Error response from daemon: Get “https://registry-1.docker.io/v2/“: EOF错误
JAVA网络IO之NIO/BIO
本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。
102 0
升级到MySQL 8.4,MySQL启动报错:io_setup() failed with EAGAIN
当MySQL 8.4启动时报错“io_setup() failed with EAGAIN”时,通常是由于系统AIO资源不足所致。通过增加AIO上下文数量、调整MySQL配置、优化系统资源或升级内核版本,可以有效解决这一问题。上述解决方案详细且实用,能够帮助管理员快速定位并处理此类问题,确保数据库系统的正常运行。
219 9
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
2101 2
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等