什么是連接池?
顧名思義,連接池就是一堆預先創建好的連接,跟容器會有點像。連接池主要是在某種需要網絡連接的服務,提前把連接建立好存起來,然后存放在一個池子里面,需要用到的時候取出來用,用完之后再還回去。
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 SwooleCoroutineChannel;
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 SwooleHttpServer("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(SwooleCoroutine::getCid() . '獲取前:');
$mysql = MysqlPool::getInstance()->getConnection();
MysqlPool::getInstance()->printLenth(SwooleCoroutine::getCid() . '歸還前:');
$result = $mysql->query("select * from sunny_member");
$row = $result->fetch(MYSQLI_ASSOC);
MysqlPool::getInstance()->printLenth(SwooleCoroutine::getCid() . '歸還后:');
$response->end($row['content']);
} catch (Exception $e) {
$response->end($e->getMessage());
}
});
$http->start();
本案例實現:
- 最小連接數
- 最大連接數
- 當前連接數
- 連接池對象
- 獲取連接池超時時間
思考:怎么實現健康度檢查?