日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

mysql 连接查询_Swoole 实战:MySQL 查询器的实现(协程连接池)

發(fā)布時(shí)間:2024/10/12 数据库 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mysql 连接查询_Swoole 实战:MySQL 查询器的实现(协程连接池) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Swoole 實(shí)戰(zhàn):MySQL 查詢器的實(shí)現(xiàn)(協(xié)程連接池)

需求分析

本篇我們將通過 Swoole 實(shí)現(xiàn)一個(gè)自帶連接池的 MySQL 查詢器:

1. 支持通過鏈?zhǔn)秸{(diào)用構(gòu)造并執(zhí)行 SQL 語句;

2. 支持連接池技術(shù);

3. 支持多協(xié)程事務(wù)并發(fā)執(zhí)行(協(xié)程安全性);

4. 支持連接對(duì)象的健康檢測(cè);

5. 支持連接對(duì)象斷線重連;

6. 程序需要可擴(kuò)展,為未來的改造留好擴(kuò)展點(diǎn);

使用示例

查詢:

$query->select(['uid', 'name'])

->from('users u')

->join('auth_users au', "u.uid=au.uid")

->where(['uid' => $uid])

->groupBy("u.phone")

->having("count(u.phone)>1")

->orderBy("u.uid desc")

->limit(10, 0)

->list();

插入:

$query->insert('users')

->values(

[

[

'name' => 'linvanda',

'phone' => '18687664562',

'nickname' => '林子',

],

[

'name' => 'xiake',

'phone' => '18989876543',

'nickname' => '俠客',

],

])->execute();// 這里是批量插入,不需要批量插入的話,傳入一維數(shù)組即可

// 延遲插入$query->insert('users')

->delayed()

->values(

[

'name' => 'linvanda',

'phone' => '18687664562',

'nickname' => '林子',

])->execute();

更新:

$query->update('users u')

->join('auth_users au', "u.uid=au.uid")

->set(['u.name' => '粽子'])

->where("u.uid=:uid", ['uid' => 123])

->execute();

刪除:

$query->delete('users')

->where("uid=:uid", ['uid' => 123])

->execute();

事務(wù):

$query->begin();$query->update('users u')

->join('auth_users au', "u.uid=au.uid")

->set(['u.name' => '粽子'])

->where("u.uid=:uid", ['uid' => 123])

->execute();...$query->commit();

模塊設(shè)計(jì)

1. 查詢模塊:

查詢器(Query,入口)

SQL構(gòu)造器(Builder)

2. 事務(wù)模塊:

事務(wù)接口(ITransaction)

協(xié)程版事務(wù)類(CoTransaction)

協(xié)程上下文(TContext)

3. 連接池模塊:

連接池接口(IPool)

協(xié)程連接池類(CoPool)

4. 數(shù)據(jù)庫(kù)連接(驅(qū)動(dòng))模塊:

連接接口(IConnector)

連接生成器接口(IConnectorBuilder)

協(xié)程連接類(CoConnector)

協(xié)程連接生成器(CoConnectorBuilder)

數(shù)據(jù)庫(kù)連接配置類(DBConfig)

數(shù)據(jù)庫(kù)連接(統(tǒng)計(jì))信息類(ConnectorInfo)

我們希望通過統(tǒng)一的入口對(duì)外提供服務(wù),將復(fù)雜性隱藏在內(nèi)部。該統(tǒng)一入口由查詢模塊提供。該模塊由查詢器和SQL 構(gòu)造器構(gòu)成,其中查詢器作為外界唯一入口,而構(gòu)造器是一個(gè) Trait,因?yàn)檫@樣可以讓外界通過查詢器入口直接使用構(gòu)造器提供的 SQL 組裝功能。

查詢器通過事務(wù)模塊執(zhí)行 SQL。這里的事務(wù)有兩個(gè)層面含義:數(shù)據(jù)庫(kù)操作的事務(wù)性(顯式或隱式事務(wù),由 CoTransaction 類保障),以及多協(xié)程下的執(zhí)行環(huán)境隔離性(由 TContext 類保障)。

事務(wù)模塊需要通過數(shù)據(jù)庫(kù)連接對(duì)象執(zhí)行具體的 SQL。連接對(duì)象由連接池模塊提供。

連接池模塊維護(hù)(創(chuàng)建、回收、銷毀)數(shù)據(jù)庫(kù)連接對(duì)象,具體是通過數(shù)據(jù)庫(kù)連接模塊的連接生成器生成新數(shù)據(jù)庫(kù)連接。

模塊之間依賴于接口而非具體實(shí)現(xiàn):查詢模塊依賴事務(wù)模塊的 ITransaction 接口;事務(wù)模塊依賴連接池模塊的 IPool 接口和數(shù)據(jù)庫(kù)連接模塊的 IConnector 接口;連接池模塊依賴數(shù)據(jù)庫(kù)連接模塊的 IConnectorBuilder 接口。

UML 類圖

下面,我們分模塊具體講解。

入口

由查詢模塊對(duì)外提供統(tǒng)一的使用入口。查詢模塊由兩部分構(gòu)成:查詢器和 SQL 構(gòu)造器。為了讓調(diào)用方可以直接通過查詢器來構(gòu)造 SQL(而不用先實(shí)例化一個(gè)構(gòu)造器構(gòu)造 SQL 然后傳給查詢器),我將構(gòu)造器設(shè)計(jì)成 Trait 供查詢器 Query 使用。

該入口類做了以下幾件事情:

· 提供 list()、one()、page()、execute() 等方法執(zhí)行 SQL 語句,其內(nèi)部是通過 transaction 實(shí)現(xiàn)的;

· 通過 Builder 這個(gè) Trait 對(duì)外提供 SQL 構(gòu)造功能;

· 委托 transaction 實(shí)現(xiàn)事務(wù)功能;

構(gòu)造器主要提供和 SQL 子句對(duì)應(yīng)的方法來構(gòu)造和編譯 SQL,并提供對(duì)原生 SQL 的支持。
該構(gòu)造器并未對(duì)所有的 SQL 語句做方法上的實(shí)現(xiàn)(比如子查詢),只對(duì)最常用的功能提供了支持,復(fù)雜的 SQL 建議直接寫 SQL 語句(一些框架對(duì)復(fù)雜 SQL 構(gòu)造也提供了方法級(jí)別的支持,但這其實(shí)會(huì)帶來使用和維護(hù)上的復(fù)雜性,它導(dǎo)致 SQL 不夠直觀)。

完整的查詢模塊代碼

事務(wù)

事務(wù)是集中管理 SQL 執(zhí)行上下文的地方,所有的 SQL 都是在事務(wù)中執(zhí)行的(沒有調(diào) begin() 則是隱式事務(wù))。

我們的查詢器是協(xié)程安全的,即一個(gè) Query 實(shí)例可以在多個(gè)協(xié)程中并發(fā)執(zhí)行事務(wù)而不會(huì)相互影響。協(xié)程安全性是通過事務(wù)模塊保證的,這里需要處理兩個(gè)維度的“事務(wù)”:數(shù)據(jù)庫(kù)維度和協(xié)程維度。不但需要保證數(shù)據(jù)庫(kù)事務(wù)的完整執(zhí)行,還要保證多個(gè)協(xié)程間的 SQL 執(zhí)行不會(huì)相互影響。

我們先看一個(gè)多協(xié)程并發(fā)執(zhí)行事務(wù)的例子(在兩個(gè)子協(xié)程中使用同一個(gè) Query 實(shí)例執(zhí)行事務(wù):先從數(shù)據(jù)庫(kù)查詢用戶信息,然后更新姓名):

$query = new Query(...);

for ($i = 0; $i < 2; $i++) {

go(function () use ($query) {

$query->begin();

$user = $query->select("uid,name")->from("users")->where("phone=:phone", ["phone" => "13908987654"])->one();

$query->update('users')->set(['name' => "李四"])->where("uid=:uid", ['uid' => $user['uid']])->execute();

$query->commit();

});}

上面代碼執(zhí)行步驟如圖:

在上圖兩個(gè)協(xié)程不斷切換過程中,各自的事務(wù)是在獨(dú)立執(zhí)行的,互不影響。

現(xiàn)實(shí)中,我們會(huì)在倉(cāng)儲(chǔ)中使用查詢器,每個(gè)倉(cāng)儲(chǔ)持有一個(gè)查詢器實(shí)例,而倉(cāng)儲(chǔ)是單例模式,多協(xié)程共享的,因而查詢器也是多協(xié)程共享的。如下:

/**

* MySQL 倉(cāng)儲(chǔ)基類

* 倉(cāng)儲(chǔ)是單例模式(通過容器實(shí)現(xiàn)單例),多協(xié)程會(huì)共享同一個(gè)倉(cāng)儲(chǔ)實(shí)例

*/abstract class MySQLRepository extends Repository implements ITransactional{

/**

* 查詢器

*/

protected $query;

public function __construct()

{

if (!$this->dbAlias()) {

throw new Exception('dbName can not be null');

}

// 通過工廠創(chuàng)建查詢器實(shí)例

$this->query = MySQLFactory::build($this->dbAlias());

}

...}

事務(wù)模塊是如何實(shí)現(xiàn)協(xié)程并發(fā)事務(wù)的隔離性呢?我們用協(xié)程上下文 TContext 類實(shí)現(xiàn)協(xié)程間數(shù)據(jù)的隔離,事務(wù)類 CoTransaction 持有 TContext 實(shí)例,事務(wù)中所有的狀態(tài)信息都通過 TContext 存取,以實(shí)現(xiàn)協(xié)程間狀態(tài)數(shù)據(jù)互不影響。

我們先看看協(xié)程上下文類:

class TContext implements ArrayAccess{

private $container = [];

...

public function offsetGet($offset)

{

if (!isset($this->container[Co::getuid()])) {

return null;

}

return $this->container[Co::getuid()][$offset] ?? null;

}

public function offsetSet($offset, $value)

{

$cuid = Co::getuid();

if (!isset($this->container[$cuid])) {

$this->init();

}

$this->container[$cuid][$offset] = $value;

}

private function init()

{

$this->container[Co::getuid()] = [];

// 協(xié)程退出時(shí)需要清理當(dāng)前協(xié)程上下文

Co::defer(function () {

unset($this->container[Co::getuid()]);

});

}}

協(xié)程上下文內(nèi)部通過 $container 數(shù)組維護(hù)每個(gè)協(xié)程的數(shù)據(jù)。該類實(shí)現(xiàn)了 ArrayAccess 接口,可以通過下標(biāo)訪問,如:

// 創(chuàng)建上下文實(shí)例$context = new TContext();// 設(shè)置當(dāng)前協(xié)程的數(shù)據(jù)$context["model"] = "write";// 訪問當(dāng)前協(xié)程的數(shù)據(jù)$context["model"];

再看看事務(wù)。

事務(wù)接口定義:

interface ITransaction{

public function begin(string $model = 'write', bool $isImplicit = false): bool;

/**

* 發(fā)送 SQL 指令

*/

public function command(string $preSql, array $params = []);

/**

* 提交事務(wù)

* @param bool $isImplicit 是否隱式事務(wù),隱式事務(wù)不會(huì)向 MySQL 提交 commit (要求數(shù)據(jù)庫(kù)服務(wù)器開啟了自動(dòng)提交的配置)

* @return bool

* @throws Exception

*/

public function commit(bool $isImplicit = false): bool;

public function rollback(): bool;

/**

* 獲取或設(shè)置當(dāng)前事務(wù)執(zhí)行模式

* @param string 讀/寫模式 read/write

* @return string 當(dāng)前事務(wù)執(zhí)行模式

*/

public function model(?string $model = null): string;

...

/**

* 獲取一次事務(wù)中執(zhí)行的 SQL 列表

* @return array

*/

public function sql():array;}

上面接口定義了事務(wù)管理器的主要工作:開啟事務(wù)、執(zhí)行 SQL、提交/回滾事務(wù)以及和本次事務(wù)執(zhí)行相關(guān)的信息。

我們?cè)賮砜纯此膶?shí)現(xiàn)類 CoTransaction,該類是整個(gè)查詢器中最重要的類,我們把整個(gè)類的代碼完整貼出來:

/**

* 協(xié)程版事務(wù)管理器

* 注意:事務(wù)開啟直到提交/回滾的過程中會(huì)一直占用某個(gè) IConnector 實(shí)例,如果有很多長(zhǎng)事務(wù),則會(huì)很快耗完連接池資源

*/class CoTransaction implements ITransaction{

private $pool;

// 事務(wù)的所有狀態(tài)信息(運(yùn)行狀態(tài)、SQL、運(yùn)行模式、運(yùn)行結(jié)果等)都是存儲(chǔ)在上下文中

private $context;

/**

* 創(chuàng)建事務(wù)實(shí)例時(shí)需要提供連接池,并在內(nèi)部創(chuàng)建該事物的協(xié)程上下文實(shí)例

*/

public function __construct(IPool $pool)

{

$this->pool = $pool;

$this->context = new TContext();

}

public function __destruct()

{

// 如果事務(wù)沒有結(jié)束,則回滾

if ($this->isRunning()) {

$this->rollback();

}

}

/**

* 開啟事務(wù)

*/

public function begin(string $model = 'write', bool $isImplicit = false): bool

{

// 如果事務(wù)已經(jīng)開啟了,則直接返回

if ($this->isRunning()) {

return true;

}

// 事務(wù)模式(決定從讀連接池還是寫連接池拿連接對(duì)象)

$this->model($model);

// 設(shè)置事務(wù)運(yùn)行狀態(tài)

$this->isRunning(true);

// 獲取數(shù)據(jù)庫(kù)連接

try {

if (!($connector = $this->connector())) {

throw new ConnectException("獲取連接失敗");

}

} catch (Exception $exception) {

$this->isRunning(false);

throw new TransactionException($exception->getMessage(), $exception->getCode());

}

// 開啟新事務(wù)前,需要清除上一次事務(wù)的數(shù)據(jù)

$this->resetLastExecInfo();

$this->clearSQL();

// 調(diào)用數(shù)據(jù)庫(kù)連接對(duì)象的 begin 方法開始事務(wù)(如果是隱式事務(wù)則不調(diào)用)

return $isImplicit || $connector->begin();

}

/**

* 執(zhí)行 SQL 指令

* 如果是隱式事務(wù),則在該方法中自動(dòng)調(diào)用 begin 和 commit 方法

*/

public function command(string $preSql, array $params = [])

{

if (!$preSql) {

return false;

}

// 是否隱式事務(wù):外界沒有調(diào)用 begin 而是直接調(diào)用 command 則為隱式事務(wù)

$isImplicit = !$this->isRunning();

// 如果是隱式事務(wù),則需要自動(dòng)開啟事務(wù)

if ($isImplicit && !$this->begin($this->calcModelFromSQL($preSql), true)) {

return false;

}

// 執(zhí)行 SQL

$result = $this->exec([$preSql, $params]);

// 隱式事務(wù)需要及時(shí)提交

if ($isImplicit && !$this->commit($isImplicit)) {

return false;

}

return $result;

}

/**

* 提交事務(wù)

*/

public function commit(bool $isImplicit = false): bool

{

if (!$this->isRunning()) {

return true;

}

$result = true;

if (!$isImplicit) {

// 顯式事務(wù)才需要真正提交到 MySQL 服務(wù)器

if ($conn = $this->connector(false)) {

$result = $conn->commit();

if ($result === false) {

// 執(zhí)行失敗,試圖回滾

$this->rollback();

return false;

}

} else {

return false;

}

}

// 釋放事務(wù)占用的資源

$this->releaseTransResource();

return $result;

}

/**

* 回滾事務(wù)

* 無論是提交還是回滾,都需要釋放本次事務(wù)占用的資源

*/

public function rollback(): bool

{

if (!$this->isRunning()) {

return true;

}

if ($conn = $this->connector(false)) {

$conn->rollback();

}

$this->releaseTransResource();

return true;

}

/**

* 獲取或設(shè)置當(dāng)前事務(wù)執(zhí)行模式

*/

public function model(?string $model = null): string

{

// 事務(wù)處于開啟狀態(tài)時(shí)不允許切換運(yùn)行模式

if (!isset($model) || $this->isRunning()) {

return $this->context['model'];

}

$this->context['model'] = $model === 'read' ? 'read' : 'write';

return $model;

}

public function lastInsertId()

{

return $this->getLastExecInfo('insert_id');

}

public function affectedRows()

{

return $this->getLastExecInfo('affected_rows');

}

public function lastError()

{

return $this->getLastExecInfo('error');

}

public function lastErrorNo()

{

return $this->getLastExecInfo('error_no');

}

/**

* 本次事務(wù)執(zhí)行的所有 SQL

* 該版本并沒有做記錄

*/

public function sql(): array

{

return $this->context['sql'] ?? [];

}

/**

* 釋放當(dāng)前事務(wù)占用的資源

*/

private function releaseTransResource()

{

// 保存本次事務(wù)相關(guān)執(zhí)行結(jié)果供外界查詢使用

$this->saveLastExecInfo();

// 歸還連接資源

$this->giveBackConnector();

unset($this->context['model']);

$this->isRunning(false);

}

/**

* 保存事務(wù)最終執(zhí)行的一些信息

*/

private function saveLastExecInfo()

{

if ($conn = $this->connector(false)) {

$this->context['last_exec_info'] = [

'insert_id' => $conn->insertId(),

'error' => $conn->lastError(),

'error_no' => $conn->lastErrorNo(),

'affected_rows' => $conn->affectedRows(),

];

} else {

$this->context['last_exec_info'] = [];

}

}

private function resetLastExecInfo()

{

unset($this->context['last_exec_info']);

}

private function getLastExecInfo(string $key)

{

return isset($this->context['last_exec_info']) ? $this->context['last_exec_info'][$key] : '';

}

/**

* 執(zhí)行指令池中的指令

* @param $sqlInfo

* @return mixed

* @throws

*/

private function exec(array $sqlInfo)

{

if (!$sqlInfo || !$this->isRunning()) {

return true;

}

return $this->connector()->query($sqlInfo[0], $sqlInfo[1]);

}

private function clearSQL()

{

unset($this->context['sql']);

}

private function calcModelFromSQL(string $sql): string

{

if (preg_match('/^(update|replace|delete|insert|drop|grant|truncate|alter|create)s/i', trim($sql))) {

return 'write';

}

return 'read';

}

/**

* 獲取連接資源

*/

private function connector(bool $usePool = true)

{

if ($connector = $this->context['connector']) {

return $connector;

}

if (!$usePool) {

return null;

}

$this->context['connector'] = $this->pool->getConnector($this->model());

return $this->context['connector'];

}

/**

* 歸還連接資源

*/

private function giveBackConnector()

{

if ($this->context['connector']) {

$this->pool->pushConnector($this->context['connector']);

}

unset($this->context['connector']);

}

private function isRunning(?bool $val = null)

{

if (isset($val)) {

$this->context['is_running'] = $val;

} else {

return $this->context['is_running'] ?? false;

}

}}

該類中,一次 SQL 執(zhí)行(無論是顯式事務(wù)還是隱式事務(wù))的步驟:

begin -> exec -> commit/rollback

1. begin:

判斷是否可開啟新事務(wù)(如果已有事務(wù)在運(yùn)行,則不可開啟);

設(shè)置事務(wù)執(zhí)行模式(read/write);

將當(dāng)前事務(wù)狀態(tài)設(shè)置為 running;

獲取連接對(duì)象;

清理本事務(wù)實(shí)例中上次事務(wù)的痕跡(上下文、SQL);

調(diào)連接對(duì)象的 begin 啟動(dòng)數(shù)據(jù)庫(kù)事務(wù);

2. exec:

調(diào)用連接對(duì)象的 query 方法執(zhí)行 SQL(prepare 模式);

3. commit:

判斷當(dāng)前狀態(tài)是否可提交(running 狀態(tài)才可以提交);

調(diào)用連接對(duì)象的 commit 方法提交數(shù)據(jù)庫(kù)事務(wù)(如果失敗則走回滾);

釋放本次事務(wù)占用的資源(保存本次事務(wù)執(zhí)行的相關(guān)信息、歸還連接對(duì)象、清除上下文里面相關(guān)信息)

4. rollback:

判斷當(dāng)前狀態(tài)是否可回滾;

調(diào)用連接對(duì)象的 rollback 回滾數(shù)據(jù)庫(kù)事務(wù);

釋放本次事務(wù)占用的資源(同上);

優(yōu)化:

類 CoTransaction 依賴 IPool 連接池,這種設(shè)計(jì)并不合理(違反了迪米特法則)。從邏輯上說,事務(wù)管理類真正依賴的是連接對(duì)象,而非連接池對(duì)象,因而事務(wù)模塊應(yīng)該依賴連接模塊而不是連接池模塊。讓事務(wù)管理類依賴連接池,一方面向事務(wù)模塊暴露了連接管理的細(xì)節(jié), 另一方面意味著如果使用該事務(wù)管理類,就必須使用連接池技術(shù)。

一種優(yōu)化方案是,在連接模塊提供一個(gè)連接管理類供外部(事務(wù)模塊)取還連接:

interface IConnectorManager{

public function getConnector() IConnector;

public function giveBackConnector(IConnector $conn);}

將 IConnectorManager 注入到 CoTransaction 中:

class CoTransaction implements ITransaction{

...

public function __construct(IConnectorManager $connMgr)

{

...

}}

連接管理器 IConnectorManager 承擔(dān)了工廠方法角色,至此,事務(wù)模塊僅依賴連接模塊,而不用依賴連接池。

連接池

連接池模塊由 IPool 接口和 CoPool 實(shí)現(xiàn)類組成。

連接池模塊和連接模塊之間的關(guān)系比較巧妙(上面優(yōu)化后的方案)。從高層(接口層面)來說,連接池模塊依賴連接模塊:連接池操作(取還)IConnector 的實(shí)例;從實(shí)現(xiàn)上來說,連接模塊同時(shí)又依賴連接池模塊:PoolConnectorManager(使用連接池技術(shù)的連接管理器)依賴連接池模塊來操作連接對(duì)象(由于該依賴是實(shí)現(xiàn)層面的而非接口層面,因而它不是必然的,如果連接管理器不使用連接池技術(shù)則不需要依賴連接池模塊)。“連接管理器”這個(gè)角色很重要:它對(duì)外(事務(wù)模塊)屏蔽了連接池模塊的存在,代價(jià)是在內(nèi)部引入了對(duì)連接池模塊的依賴(也就是用內(nèi)部依賴換外部依賴)。

經(jīng)過上面的分析我們得出,連接池模塊和連接模塊具有較強(qiáng)的耦合性,連接模塊可以對(duì)外屏蔽掉連接池模塊的存在,因而在設(shè)計(jì)上我們可以將這兩個(gè)模塊看成一個(gè)大模塊放在一個(gè)目錄下面,在該目錄下再細(xì)分成兩個(gè)內(nèi)部模塊即可。

我們可以先去看看連接池接口:

這里有幾點(diǎn)需要注意:

1. 連接池使用的是偽單例模式,同一個(gè)生成器對(duì)應(yīng)的是同一個(gè)連接池實(shí)例;

2. 連接池內(nèi)部維護(hù)了讀寫兩個(gè)池子,生成器生成的讀寫連接對(duì)象分別放入對(duì)應(yīng)的池子里面;

3. 從連接池取連接對(duì)象的時(shí)候,如果連接池為空,則根據(jù)情況決定是創(chuàng)建新連接還是等待。此處并非是在池子滿了的情況下就等待,而是會(huì)超額創(chuàng)建,為的是應(yīng)對(duì)峰值等異常情況。當(dāng)然一個(gè)優(yōu)化點(diǎn)是,將溢出比例做成可配置的,由具體的項(xiàng)目決定溢出多少。另外,如果創(chuàng)建新連接的時(shí)候數(shù)據(jù)庫(kù)服務(wù)器報(bào)連接過多的錯(cuò)誤,也需要轉(zhuǎn)為等待連接歸還;

4. 如果多次等待連接失敗(超時(shí)),則后面的請(qǐng)求會(huì)直接拋出異常(直到池子不為空)。這里有個(gè)優(yōu)化點(diǎn):目前的實(shí)現(xiàn)沒有區(qū)分是讀池子超時(shí)還是寫池子超時(shí);

5. 歸還連接時(shí),如果池子滿了,或者連接壽命到期了,則直接關(guān)閉連接;

后面在連接模塊會(huì)講解連接生成器,到時(shí)我們會(huì)知道一個(gè)連接池實(shí)例到底維護(hù)的是哪些連接對(duì)象。

連接

連接模塊負(fù)責(zé)和數(shù)據(jù)庫(kù)建立連接并發(fā)出 SQL 請(qǐng)求,其底層使用 Swoole 的 MySQL 驅(qū)動(dòng)。連接模塊由連接對(duì)象和連接生成器構(gòu)成,對(duì)外暴露 IConnector 和 IConnectorBuilder 接口。

(在我們的優(yōu)化版本中,一方面引入了連接管理器 IConnectorManager,另一方面將連接模塊和連接池模塊合并成一個(gè)大模塊,因而整個(gè)連接模塊對(duì)外暴露的是 IConnectorManager 和 IConnector 兩個(gè)接口。)

連接對(duì)象的實(shí)現(xiàn)比較簡(jiǎn)單,我們重點(diǎn)看下 CoConnector 里面查詢的處理:

該生成器是針對(duì)一主多從數(shù)據(jù)庫(kù)架構(gòu)的(包括未走讀寫分離的),如果使用是是其他數(shù)據(jù)庫(kù)架構(gòu)(如多主架構(gòu)),則創(chuàng)建其他生成器即可。

同一套讀寫配置使用同一個(gè)生成器,對(duì)應(yīng)的連接池也是同一個(gè)。

DBConfig 是一個(gè) DTO 對(duì)象,不再闡述。

查詢器的組裝

使用工廠組裝查詢器實(shí)例:

class MySQLFactory{

/**

* @param string $dbAlias 數(shù)據(jù)庫(kù)配置別名,對(duì)應(yīng)配置文件中數(shù)據(jù)庫(kù)配置的 key

*/

public static function build(string $dbAlias): Query

{

// 從配置文件獲取數(shù)據(jù)庫(kù)配置

$dbConf = Config::getInstance()->getConf("mysql.$dbAlias");

if (!$dbConf) {

throw new ConfigNotFoundException("mysql." . $dbAlias);

}

if (!isset($dbConf['read']) && !isset($dbConf['write'])) {

$writeConf = $dbConf;

$readConfs = [$writeConf];

} else {

$writeConf = $dbConf['write'] ?? [];

$readConfs = $dbConf['read'] ?? [$writeConf];

}

$writeConfObj = self::createConfObj($writeConf);

$readConfObjs = [];

foreach ($readConfs as $readConf) {

$readConfObjs[] = self::createConfObj($readConf);

}

// 創(chuàng)建生成器、連接池、事務(wù)管理器

// 在優(yōu)化后版本中,用連接管理器代替連接池的位置即可

$mySQLBuilder = CoConnectorBuilder::instance($writeConfObj, $readConfObjs);

$pool = CoPool::instance($mySQLBuilder, $dbConf['pool']['size'] ?? 30);

$transaction = new CoTransaction($pool);

return new Query($transaction);

}

private static function createConfObj(array $config): DBConfig

{

if (!$config) {

throw new Exception("config is null");

}

return new DBConfig(

$config['host'],

$config['user'],

$config['password'],

$config['database'],

$config['port'] ?? 3306,

$config['timeout'] ?? 3,

$config['charset'] ?? 'utf8'

);

}}

至此,整個(gè)查詢器的編寫、創(chuàng)建和使用就完成了。

總結(jié)

1. 項(xiàng)目的開發(fā)需要?jiǎng)澐帜K,模塊之間盡量減少耦合,通過接口通信(模塊之間依賴接口而不是實(shí)現(xiàn));

2. 如果兩個(gè)模塊之間具有強(qiáng)耦合性,則往往意味著兩者本身應(yīng)該歸并到同一個(gè)模塊中,在其內(nèi)部劃分子模塊,對(duì)外屏蔽內(nèi)部細(xì)節(jié),如本項(xiàng)目的連接模塊和連接池模塊;

3. 如果模塊之間存在不合常理的依賴關(guān)系,則意味著模塊劃分有問題,如本項(xiàng)目中的事務(wù)模塊依賴連接池模塊;

4. 有問題的模塊劃分往往違反第一點(diǎn)(也就是迪米特法則),會(huì)造成模塊暴露細(xì)節(jié)、過多的依賴關(guān)系,影響設(shè)計(jì)的靈活性、可擴(kuò)展性,如本項(xiàng)目中事務(wù)模塊依賴連接池模塊(雖然是實(shí)現(xiàn)層面的依賴而非接口層面),造成要使用 CoTransaction 時(shí)必須同時(shí)使用連接池;

5. 編寫生產(chǎn)可用的項(xiàng)目時(shí)需要注意處理異常場(chǎng)景,如本項(xiàng)目中從連接池獲取連接對(duì)象,以及在連接對(duì)象上執(zhí)行 SQL 時(shí)的斷線重連;

6. 設(shè)計(jì)本身是迭代式的,并非一蹴而就、一次性設(shè)計(jì)即可完成的,本項(xiàng)目在開發(fā)過程中已經(jīng)經(jīng)歷過幾次小重構(gòu),在本次分析時(shí)仍然發(fā)現(xiàn)一些設(shè)計(jì)上的缺陷。重構(gòu)屬于項(xiàng)目開發(fā)的一部分;

優(yōu)化版 UML 圖:

以上內(nèi)容希望幫助到大家,很多PHPer在進(jìn)階的時(shí)候總會(huì)遇到一些問題和瓶頸,業(yè)務(wù)代碼寫多了沒有方向感,不知道該從那里入手去提升,對(duì)此我整理了一些資料,包括但不限于:分布式架構(gòu)、高可擴(kuò)展、高性能、高并發(fā)、服務(wù)器性能調(diào)優(yōu)、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優(yōu)化、shell腳本、Docker、微服務(wù)、Nginx等多個(gè)知識(shí)點(diǎn)高級(jí)進(jìn)階干貨需要的可以免費(fèi)分享給大家,PHP進(jìn)階學(xué)習(xí)交流群

關(guān)注:架構(gòu)師學(xué)習(xí)路線圖,每日更新互聯(lián)網(wǎng)最新技術(shù)文章與你不斷前行,實(shí)戰(zhàn)資料,筆試面試。

總結(jié)

以上是生活随笔為你收集整理的mysql 连接查询_Swoole 实战:MySQL 查询器的实现(协程连接池)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。