mysql指引(三):mysql线程处理模型
提到了 mysql 的线程模型,本节我们就来看看。
One-Thread-Per-Connection模型与 Pool-Threads模型
MySQL每个连接使⽤⼀个线程,另外还有内部处理线程、特殊⽤途的线程、以及所有存储引擎创建的线程。-- 《⾼性能MySQL》
站在客户端视⾓来看,也就是下⾯的 conn 对象就可以对应到 server 端的线程A:
// 从DriverManager处获取数据库连接
Connection conn = Connection(
"jdbc:mysql://数据库ip/数据库名称",
"账号",
"密码");
应用程序中的服务器错误怎么办客户端对这个 conn 对象执⾏SQL语句时,server端的这个线程A就会处理SQL语句,也就是⾛ 提到的整体流程。下图中的 work 线程就是包含解析器、优化器等的那部分模块。
当我们在代码中执⾏SQL时,如前⽂的:
Statement st = ateStatement();
// 只查⼀⾏数据
ResultSet rs = st.executeQuery("SELECT id, username, password FROM sys_user LIMIT 1");
这部分代码对应的 server 端的执⾏过程就是在 work 线程中执⾏的。由 work 线程利⽤ 解析器、优化器等模块⽅法来执⾏。这种模型叫做:One-Thread-Per-Connection。
那么,当连接数过多的时候,⽐如1万个连接,就要创建1万个 work 线程。⼜由于我们在客户端缓存了 conn 对象,也就是保持了这个长连接。那么在这种模型下(每个连接使⽤⼀个线程),server 端
也需要保持1万个线程。
虽然每个 work 线程可以复⽤来处理同⼀个 conn 对象的多个 SQL 语句,但是维护这么多的线程意味着内存占⽤开销⼤,意味着CPU调度开销⼤,性能肯定上不来。
所以呢?所以 mysql server 的线程处理模型是不是应该换⼀换呢?
我们先说结论,不是的。原因如下:
1. 数据库的长连接⼀般不会很多,属于常量连接;但是数据库的请求是⾮常多的,也就是⼀个长连接中,可能来回收发请求数达到成百
上千。由于客户端的连接池将连接缓存并做了最⼤限制,实际应⽤中数据库长连接不是很多。
自定义格式化json文件
2. 我们可以看下 mysql5.7 官⽅⽂档是怎么说的:
The default thread-handling model in MySQL Server executes statements using one thread per client connection
⾸先,官⽅说是默认的线程处理模型是对每个客户端连接都创建⼀个线程,这个和我们认知⼀致。但是呢,还有其他线程处理模型,这就是Pool-Threads 处理模型,官⽅说明如下:
to limit the number of concurrently executing statements/queries and transactions to ensure that each has sufficient CPU and memory resources to fulfill its task
主要的⽬的是限流以确保有⾜够的资源来完成已经接受的任务。那么原先的 One-Thread-Per-Connection 缺陷为:
As more clients connect to the server and execute statements, overall performance degrades.
就是⼤量客户端连接到服务器并执⾏各种语句,会导致服务端的整体性能下降。那么使⽤了 Pool-Threads 模型后,会怎么样呢?
The Thread Pool plugin increases server performance by efficiently managing statement execution threads for large numbers of client connections, especially on modern multi-CPU/Core systems
通过有效的线程管理会提升服务器性能。但是我们想⽤也难,因为:
For MySQL 5.7, the Thread Pool plugin is included in MySQL Enterprise Edition, a commercial product.
企业版才配置这个功能,也就是才⽀持这个线程处理模型。
所以,结论就是 mysql 的线程模型是 One-Thread-Per-Connection ,即每个连接都创建⼀个新的处理线程。
实际上,完整的线程模型如下图:
因为我们是基于 TCP 长连接的,所以前级有⼀个 Dispatcher线程 (实际上叫什么都⾏),它就是来监听TCP的连接请求,然后创建⼀个Work线程 并将这个连接绑定到上⾯。
接着,work线程执⾏读取请求信息操作,也就是 read 操作,这样⼦就收到了客户端发来的信息,⽐如查询请求。然后调⽤业务处理,这⾥的业务处理就是上⼀讲中的解析器等⼀堆业务操作。
最后,将查询到的数据返回给客户端。
并发处理
既然讲到了线程模型,就需要提及⼀下并发处理模型。此处不区分线程和进程,进⾏简单的解析,后续会深⼊。
⼀个线程对应⼀个连接
这就是 One-Thread-Per-Connection 模型,不再赘述。这种思路下,资源消耗巨⼤,能否换种思路,让资源消耗降低,从⽽多余出去的资源给到真正需要⽤的地⽅。也就是 ⼀个线程同时对应多条连接。
⼀个线程对应多条连接
这就是⼤名⿍⿍的 I/O多路复⽤技术 。可以⼤⼤降低线程的数量,从⽽解决⼀个线程对应⼀个连接的调度问题。
当连接建⽴后,我们需要对连接做什么:
ioctl头文件1. 监听连接上的请求信息
2. 监听到了,就去读取信息内容,并执⾏后续处理。即 read --> 业务处理 --> send
那么当我们的线程持有⽐如1万个连接时,如何处理第⼀步呢?即如何监听哪个或者哪些长连接⽬前发来了请求信息呢?
⽅法1:轮询
divorce形容词线程A⼀次轮询每个长连接,看是否有请求发出,有请求则执⾏第⼆步。
很容易想到,当第⼆步阻塞时,即该连接的数据还没有发送读取完成,没法⽴即返回结果,则排在后⾯的长连接就算有信息到来也⽆法⽴刻被线程A读取。
⽅法2:select
线程A利⽤ select 系统调⽤向操作系统内核注册长连接的⽂件句柄,当某个长连接有数据发来并且数据可以读取时,线程A就可以再次检查是哪个长连接的⽂件句柄发⽣了变化,就可以去依次读取发⽣变化的数据信息。由于此时数据已经准备好了,所以 read 操作⼏乎瞬间就可以完成。scratch3官方下载
但是⽂件句柄数量有限⽽且还需要再次检查是哪个句柄发⽣了变化,浪费性能。
⽅法3:epoll
线程A利⽤ epoll 系统调⽤向系统内核同样注册⽂件句柄,但是操作系统只返回可读的⽂件句柄。这样,就避免了重复轮询⼤量没有准备好数据的⽂件句柄了。
不过,虽然是 epoll⾮阻塞,但是 read、send这些仍然是同步读取数据的,这⾥还是会慢。mysql是什么系统
⽅法4:异步IO
异步IO是将 read 和 send 操作异步化,实现数据从⽹卡拷贝到内存中也是⾮阻塞的。但是⽬前 linux 层⾯对于异步IO的⽀持不完善,⽽且真正的异步化不好实现,所以⼀般也不采⽤。
这些实际上是IO模型,也就是下图:
图中右侧的两个阶段,就对应上⽂的两个步骤。等待数据对应的是监听连接上的请求信息,将数据从内核拷贝到⽤户空间,就对应的是第⼆步 read 过程。
⽅法1 对应 ⾮阻塞I/O,⽅法2 对应I/O复⽤,⽅法3 对应 信号驱动I/O,⽅法4 对应 异步I/O。
在第⼀个阶段等待数据处,图中的后四种都是⾮阻塞的,因为他们不会阻塞在同⼀个长连接上;然⽽只有最后⼀种是异步的,因为在第⼆个阶段拷贝数据时,异步I O将数据拷贝到⽤户空间后再通知程序数据可读,不像其他四个,都需要主动去读取数据,所以他们⼜叫做同步IO。
到这⾥,等待数据、拷贝数据并读取都理清了。那么业务处理怎么办?对于处理速度较快的业务,可以直接在同⼀个线程中进⾏操作,完了再处理其他准备好的连接。对于业务处理慢的,⽐如有I/O操作的业务,则可以另起线程池进⾏操作。
其实,I/O复⽤模型 + 线程池 的组合就是⾮常重要的 Reactor 并发模式,后续⽂章会继续涉及。
再看One-Thread-Per-Connection模型
对于每个连接创建⼀个“线程“,这个线程是否就是操作系统内核管理的线程呢?答案是否定的。这个”线程“实际上是 mysql thread,其只是⼀个对象,需要和操作系统的真实线程 os thread 关联起来。
每个连接对应⼀个 mysql thread ,每个 mysql thread 对应⼀个 os thread 。当连接关闭时,mysql thread 也随之消失,但是 os thread 可以继续被其他的 mysql thread 继续使⽤。
下⼀个mysql⽂章中,我们会对关联查询的底层原理进⾏分析。在此之前,会先补充⼀个短篇,关于线程和操作系统的关系介绍。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论