池化思想-Mysql异步连接池
前言
继前文池化思想-线程池的后续,继续学习池化思想,本例为Mysql连接池。根据前文针对池化思想的概述,池化的目的在于“复用”。“复用”达到效率的高低要看对于“工具”的创建代价。如果新创建一个“工具”所需付出的代价越高那么在控制好复用代价的前提下能极大提高整体效率。对于Mysql连接池作为一个符合上述内容的例子,后文介绍如何一个简单的Mysql连接池。
一、Mysql 连接过程
本文介绍Mysql连接池之前先介绍一下Mysql连接建立的过程。因为连接池需要维护的是Mysql连接,因此理解Mysql连接建立过程后才能理解为什么Mysql连接池复用Mysql连接能够提效。
对于Mysql连接主要的建立方式很多,包括:Unix套接字、内存共享、命名管道、TCP/IP套接字等。
其中除了TCP/IP套接字外,其他几种都是基于进程间通信涵盖的内容,因此需要满足一个前置条件就是:Mysql客户端和服务端需要在一台物理机上。虽然TCP/IP通信也可用在同一机器上的进程间通信,但是此方式提供跨机器进行服务访问就是其他方式不具备的了。这也是最常用的使用方式。
既然是基于TCP连接实现的Mysql服务端客户端连接,因此在Mysql连接正式建立之前需要建立起TCP连接。对于TCP连接建立的开销,可以查看之前的文章,三次握手的开销。这已经是一部分开销了,后续还需要握手认证,之后才正式建立了一条Mysql连接,客户端可以向服务端发送需要执行的操作命令。其中大概的流程如下图:
针对上述内容的分析,对于Mysql连接池的建立,可以复用Mysql连接,一次创建,节省后续多次建立Mysql连接的开销,从而达到提升整体效率的目的。
二、同步连接池 VS 异步连接池
既然想要创建一个Mysql连接池,对于Mysql连接池有同步与异步之分。对于同步与异步的区别,关键在于调用方线程在“提交任务”与“获取结果”这两个阶段的行为方式。下文分别介绍同步连接池与异步连接池:
1、同步连接池使用过程
-
调用方行为: 调用方线程执行的是“同步阻塞式”的操作流程。
-
获取连接:
getConnection()
调用会阻塞线程,直到拿到一个可用连接(或超时/失败)。 -
执行SQL: 调用方线程使用获得的连接执行
executeQuery
,executeUpdate
等操作。 -
等待结果: 这些执行SQL的方法会阻塞调用方线程,直到数据库服务器返回最终结果(数据行、影响行数、错误等)。
-
全过程: 调用方线程在整个数据库操作的生命周期(获取连接 -> 发送SQL -> 等待/处理结果 -> 释放连接)中都是被独占且阻塞的。它不能在这段时间内做任何其他工作
2、异步连接池使用过程
-
调用方行为: 调用方线程执行的是“异步非阻塞式”的操作流程。
-
提交任务: 调用方线程发起一个数据库操作请求(可能包含SQL语句和参数)。这个请求通常是:
-
向连接池“预约”一个连接(非阻塞获取)。
-
直接将SQL任务提交给一个更高层次的异步接口(该接口内部管理连接池)。
-
-
非阻塞: 提交请求的方法(如返回
Future
,CompletableFuture
或接受回调的方法)会立即返回,不会阻塞调用方线程。调用方线程可以立即去做其他事情(处理其他请求、计算等)。 -
连接池/驱动工作: 连接池在后台处理连接获取(如果需要等待,也是在后台线程或事件循环中等待,不阻塞调用方线程)。然后驱动使用这个连接异步地、非阻塞地将SQL发送到数据库服务器,并注册一个监听器等待响应。
-
结果返回: 当数据库服务器返回结果时:
-
回调 (Callback): 预先注册的回调函数会被调用(通常在某个特定的事件循环线程或线程池中),参数里包含结果或错误。
-
Future/Promise: 之前返回的
Future
/CompletableFuture
会被完成(complete(result)
或completeExceptionally(error)
)。调用方可以在之后的某个时间点(需要结果时)调用future.get()
(这会阻塞,但通常避免)或者更优雅地使用thenApply
,thenAccept
,thenCompose
等链式方法注册后续处理逻辑(非阻塞)。
-
-
全过程: 调用方线程只负责提交任务和(稍后)处理结果通知。任务的实际执行(连接获取、网络IO、数据库处理、结果接收)都是在后台线程、事件循环或数据库驱动内部异步完成的,不会阻塞调用方线程。
3、同步 VS 异步
对于上述内容的理解可知:对于同步于异步而言,异步方式效率更高但是实现方式更复杂,流程上更加繁杂对于C++而言需要基于回调函数/Future。同步方式编程模型更简单直观,但是并发能力不如异步方式。后续将设计一个Mysql异步连接池。
三、简单异步连接池设计
实现一个Mysql连接池,其中的关键主体为Mysql连接,同时需要实现为异步连接池,所以对于“Mysql执行命令的提交”和“Mysql返回结果的获取”这两个任务阶段应该是异步实现的。所以仍复用上文单队列线程池的思想,一个线程对应一个Mysql连接,基于生产者消费者模型,连接池中创建多个线程对应多个Mysql连接,调用方作为生产者提交任务至阻塞队列,连接池中线程作为消费者消费阻塞队列中的任务。根据上述任务执行流程大概架构图如下:
其中对于上述设计中,有一个点是没有提现到图中的,就是调用方如何获取到Mysql执行结果。对于该功能的实现通过C++11中std::future和std::promise机制实现。即在需要执行的任务对象中设置一个promise,当任务执行完成后将返回的执行结果赋值给futrue。后续调用方就可以在任务执行完成之后通过future获取到最终的执行结果。
更多资料:0voice · GitHub