PHP进阶教程-实现一个简单的MySQL连接池
什么是连接池?
顾名思义,连接池就是一堆预先创建好的连接,跟容器会有点像。连接池主要是在某种需要网络连接的服务,提前把连接建立好存起来,然后存放在一个池子里面,需要用到的时候取出来用,用完之后再还回去。
MySQL连接过程
client 建立连接的认证过程
1、server 监听端口
2、client 向server建立TCP连接
3、server 向client发送挑战码报文(报文详细内容在下文中有分析)
4、client 使用挑战码加密密码,将加密后的密码包含在回包中,发送给server
5、server 根据client的回包,验证密码的有效性,给client发送ok包或error包
6、client发送SQL执行
7、关闭MySQL关闭、TCP连接
为什么使用连接池?
从图可以看到想要执行一条SQL语句每次都要走 图:3.5-1都过程,至少要7步才可以成功。MySQL会有连接数上限的限制,而且每次都执行那么多都步骤,频繁把时间浪费在了网络IO上。
没有连接池的做法类似我们买菜做饭,比如我们要做十个菜,每做一个菜就跑一趟菜市场,挑菜、讨价还价、回家、洗菜、下锅、起锅、洗锅;这样是不是很浪费时间?那我们想要做十个菜,提前把这十个菜的材料都买回来,都洗好备用,然后每次炒都时候直接下锅炒就好了。连接池就是提前买好菜,洗好菜(创建连接、验证账号密码),在要炒菜的时候直接下锅(执行SQL)炒。
使用连接池之后,只有在连接池初始化的时候就进行连接然后存到一个容器里面。每次要执行SQL语句的时候先来这个池获取连接对象,然后再发送SQL语句,当SQL语句执行完之后,再把这个连接归还给连接池。
使用连接池每次执行SQL语句只需要执行 图:3.5-1 的第6步就行了,复用了MySQL连接,在高并发情况下,节省了每次连接带来的其他开销。
连接池有什么?
最小连接数
最大连接数
当前连接数
连接池对象
获取连接池超时时间
健康度检查
实战:Swoole实现连接池
MysqlPool.php
/**
* 1、设置配置信息
* 2、创建连接对象
* 3、获取连接对象
* 4、获取连接对象,空闲连接不够创建到最大连接数
* 5、执行sql语句
* 6、归还连接
*/
use Swoole\Coroutine\Channel;
class MysqlPool
{
// 最小连接数
private $min;
// 最大连接数
private $max;
// 当前连接数
private $count = 0;
// 获取超时时间
private $timeOut = 0.2;
// 连接池对象容器
private $connections;
// 配置信息
private $config = [];
// 连接池对象
private static $instance;
public function __construct(array $config)
{
$this->config = $config;
$this->min = $this->config['min'] ?? 2;
$this->max = $this->config['max'] ?? 4;
$this->timeOut = $this->config['time_out'] ?? 0.2;
$this->connections = new Channel($this->max);
}
/**
* 获取连接池对象
* @param null $config
* @return MysqlPool
*/
public static function getInstance($config = null)
{
if (empty(self::$instance)) {
if (empty($config)) {
throw new RuntimeException("mysql config empty");
}
self::$instance = new static($config);
}
return self::$instance;
}
/**
* 初始化连接池
* @throws Exception
*/
public function init()
{
for ($i = 0; $i < $this->min; $i++) {
$this->count++;
$mysql = $this->createDb();
$this->connections->push($mysql);
}
}
/**
* 创建数据库连接对象
* @return PDO
* @throws Exception
*/
private function createDb()
{
$dsn = "mysql:dbname={$this->config['database']};host={$this->config['db_host']}";
try {
$mysql = new PDO($dsn, $this->config['db_user'], $this->config['db_passwd']);
return $mysql;
} catch (\PDOException $e) {
throw new \Exception($e->getMessage());
}
}
/**
* 获取数据库连接
* @return mixed|null|PDO
* @throws Exception
*/
public function getConnection()
{
$mysql = null;
// 判断是否为空,如果池空了,判断当前连接数是否下于最大连接数
// 如果小于最大连接数创建新连接数
if ($this->connections->isEmpty()) {
if ($this->count < $this->max) {
$this->count++;
$mysql = $this->createDb();
} else {
$mysql = $this->connections->pop($this->timeOut);
}
} else {
$mysql = $this->connections->pop($this->timeOut);
}
// 获取不到数据库连接抛出异常
if (!$mysql) {
throw new \Exception('没有连接了');
}
// 当协程结束之后归还连接池
defer(function () use ($mysql) {
$this->connections->push($mysql);
});
return $mysql;
}
/**
* 调试打印连接池的容量,非主要代码
* @param $str
*/
public function printLenth($str)
{
echo $str . $this->connections->length() . "\n";
}
}
server.php
include './MysqlPool.php';
//创建http server
$http = new Swoole\Http\Server("0.0.0.0", 9501);
$http->set(["worker_num" => 2]);
$http->on('WorkerStart', function ($serv, $worker_id) {
$config = [
'min' => 3,
'max' => 5,
'time_out' => 1,
'db_host' => '127.0.0.1',
'db_user' => 'root',
'db_passwd' => 'sunny123',
'database' => 'lv'
];
MysqlPool::getInstance($config)->init();
});
$http->on('request', function ($request, $response) {
try {
MysqlPool::getInstance()->printLenth(Swoole\Coroutine::getCid() . '获取前:');
$mysql = MysqlPool::getInstance()->getConnection();
MysqlPool::getInstance()->printLenth(Swoole\Coroutine::getCid() . '归还前:');
$result = $mysql->query("select * from sunny_member");
$row = $result->fetch(MYSQLI_ASSOC);
MysqlPool::getInstance()->printLenth(Swoole\Coroutine::getCid() . '归还后:');
$response->end($row['content']);
} catch (\Exception $e) {
$response->end($e->getMessage());
}
});
$http->start();
本案例实现:
最小连接数
最大连接数
当前连接数
连接池对象
获取连接池超时时间
思考:怎么实现健康度检查?