yii连接mysql主从_Connection 数据库主从连接源码剖析
連接總體流程
隨機打亂從庫配置(master可以選擇是否打亂,slave一定會打亂;如果master沒有配置數組則直接使用$dsn和$username作為master的配置,也就是一主)
遍歷配置如果該庫的配置在serverStatusCache緩存中生效則說明過期時間內該配置不可用,直接continue
如果緩存無值則去實例化PDO(會新實例化一個類$db = Yii::createObject($config))
實例化PDO記錄info日志
記錄實例化性能分析日志(可選,根據$enableProfiling)
new PDO
設置PDO的ATTR_ERRMODE、ATTR_EMULATE_PREPARES、字符集屬性
執行afterOpen事件
實例化失敗記錄serverStatusCache緩存,標識該配置600秒(默認)內不可用
master與slave連接的差異
slave連接會先判斷是否可以使用slave從庫($this->enableSlaves)
slave如果連接不上會判斷是否進而連接master
一定會打亂slave配置數組
如果沒有master配置數組,則直接使用$this->dns和$this->root
master連接可以選擇是否打亂配置數組
涉及方法
getSlavePdo($fallbackToMaster = true),會調用getSlave(false),如果從庫連不上就去連接master(根據參數$fallbackToMaster),返回的是PDO類
getSlave($fallbackToMaster = true),會調用openFromPool,從slave配置數組中隨機連接一個slave,返回的是Connection類
getMasterPdo(),會調用open(),如果master配置數據為空則直接使用$dns進行連接,如果master配置數組不為空則遍歷連接master,返回PDO類
getMaster(),遍歷連接master,返回Connection類
openFromPool(array $pool, array $sharedConfig),隨機打亂配置信息
openFromPoolSequentially(array $pool, array $sharedConfig),不隨機打亂配置信息,遍歷配置信息,連接PDO;內部還有serverStatusCache去緩存服務器可用狀態
open(),連接master或者slave,會記錄info和Profiling日志(可選)
createPdoInstance(),實例化PDO
屬性注入
因為Connection繼承Component類,可以使用屬性注入,所以
$db->master; //和$db->getMaster();一樣
$db->slave; //和$db->getSlave();一樣
$db->masterPdo; //和$db->getMasterPdo();一樣
$db->slavePdo; //和$db->getSlavePdo();一樣
源碼細節
yii2中可以配置一主多從配置,在連接從庫方面數據庫配置如下
public function actionD(){
$db = new \yii\db\Connection([
'dsn' => 'mysql:host=192.168.124.10;dbname=test',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'enableSlaves'=>true, //可以使用從庫
'serverRetryInterval'=>600, //其中一個從庫配置不可用,將緩存不可用狀態600秒
'enableProfiling'=>true, //默認配置,將記錄連接數據庫、執行語句等的性能分析日志
'emulatePrepare'=>true, //true為開啟本地模擬prepare
'slaveConfig'=>[ //從庫slaves屬性通用配置
'username' => 'root',
'password' => '',
'attributes' => [
PDO::ATTR_TIMEOUT => 10,
],
],
'slaves'=>[ //從庫列表
["dsn"=>"mysql:host=192.168.124.11;dbname=test"],
["dsn"=>"mysql:host=192.168.124.12;dbname=test"],
[
"dsn"=>"mysql:host=192.168.124.13;dbname=test",
'username' => 'main',
'password' => '123456',
],
],
'masters'=>[ //主庫列表
["dsn"=>"mysql:host=192.168.124.11;dbname=test"],
["dsn"=>"mysql:host=192.168.124.12;dbname=test"],
[
"dsn"=>"mysql:host=192.168.124.13;dbname=test",
'username' => 'main',
'password' => '123456',
],
],
'masterConfig'=>[ //主庫master屬性通用配置
'username' => 'root',
'password' => '',
'attributes' => [
PDO::ATTR_TIMEOUT => 10,
],
],
]);
$slave = $db->getSlavePdo();
$slave = $db->getSlave();
return 123;
}
}
可以看到數據庫操作的類是\yii\db\Connection,該類繼承Component類,可見可以使用屬性注入、行為和事件
針對Connection的屬性注入,只有以下屬性是私有的,以下屬性一般不會在外部進行操作
private $_transaction;
private $_schema;
private $_driverName;
private $_master = false;
private $_slave = false;
private $_queryCacheInfo = [];
針對Connection的事件,可以注冊以下事件
const EVENT_AFTER_OPEN = 'afterOpen'; //連接數據庫后的事件
const EVENT_BEGIN_TRANSACTION = 'beginTransaction'; //開啟事務的事件
const EVENT_COMMIT_TRANSACTION = 'commitTransaction'; //提交事務的事件
const EVENT_ROLLBACK_TRANSACTION = 'rollbackTransaction'; //回滾的事件
Connection類使用的mysql操作對象是PDO,涉及方法有
public function getSlavePdo($fallbackToMaster = true)
public function getSlave($fallbackToMaster = true)
追進在getSlavePdo方法,可見當slave連接不可用時候,會默認連接主庫($fallbackToMaster=true)
public function getSlavePdo($fallbackToMaster = true){
$db = $this->getSlave(false); //進行slave連接
if ($db === null) {
return $fallbackToMaster ? $this->getMasterPdo() : null; //當slave不可用時候,是否連接主庫
}
return $db->pdo; //返回數據庫連接資源,從庫和主庫都連接不上的話會返回null
}
追進getSlave方法
public function getSlave($fallbackToMaster = true){
if (!$this->enableSlaves) {//判斷是否可以使用slave
return $fallbackToMaster ? $this : null;
}
if ($this->_slave === false) { //如果還沒有連接過slave庫,就進行連接
$this->_slave = $this->openFromPool($this->slaves, $this->slaveConfig); //將slave配置信息給openFromPool方法
}
return $this->_slave === null && $fallbackToMaster ? $this : $this->_slave;
}
追進openFromPool方法,可見該方法就是將$this->slaves從庫dsn配置打亂,讓第一次連接slave隨機化
protected function openFromPool(array $pool, array $sharedConfig){
shuffle($pool); //打亂從庫配置
return $this->openFromPoolSequentially($pool, $sharedConfig);
}
openFromPoolSequentially方法
protected function openFromPoolSequentially(array $pool, array $sharedConfig){
if (empty($pool)) { //是否有slave配置池,如果沒有的話就是最后返回給$this->_slave為null
return null;
}
if (!isset($sharedConfig['class'])) { //判斷$this->slaveConfig屬性是否有class,可以設置class將從庫的連接配置成自己重新的類
$sharedConfig['class'] = get_class($this);
}
//服務狀態緩存,使用依賴注入獲取cache緩存類
$cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache;
//遍歷slave配置池
foreach ($pool as $config) {
//合并配置
$config = array_merge($sharedConfig, $config);
if (empty($config['dsn'])) {
throw new InvalidConfigException('The "dsn" option must be specified.');
}
$key = [__METHOD__, $config['dsn']];
//這里就是判斷緩存是否有值,如果有的話說明在過期時間內該配置的slave不可用
if ($cache instanceof CacheInterface && $cache->get($key)) {
// should not try this dead server now
continue;
}
//通過依賴注入創建了一個類,該類專門是這個slave的
$db = Yii::createObject($config);
try {
$db->open();
return $db;
} catch (\Exception $e) {
//記錄日志
Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
if ($cache instanceof CacheInterface) {
//將該配置的slave服務不可用狀態存緩存,值是1,過期時間的$this->serverRetryInterval秒
$cache->set($key, 1, $this->serverRetryInterval);
}
}
}
return null;
}
在TestController控制器的配置中,可見會隨機打亂slaves屬性,如果有任何一個從庫連接上了就是直接返回,如果有連接不上的就會將不可用狀態存緩存,然后繼續循環
slaveConfig屬性是一個從庫的通用配置,會循環的去array_merge()屬性slaves
所以配置
'slaveConfig'=>[ //從庫slaves屬性通用配置
'username' => 'root',
'password' => '',
'attributes' => [
PDO::ATTR_TIMEOUT => 10,
],
],
'slaves'=>[ //從庫列表
["dsn"=>"mysql:host=192.168.124.11;dbname=test"],
["dsn"=>"mysql:host=192.168.124.12;dbname=test"],
[
"dsn"=>"mysql:host=192.168.124.13;dbname=test",
'username' => 'main',
'password' => '123456',
'class'=> yii\overload\myDB
],
]
最后生成的配置為(這個配置會被shuffle函數打亂順序)
'slaves'=>[ //從庫列表
[
"dsn"=>"mysql:host=192.168.124.11;dbname=test",
'username' => 'root',
'password' => '',
'attributes' => [
PDO::ATTR_TIMEOUT => 10,
],
'class' => 'yii\db\Connection',
],
[
"dsn"=>"mysql:host=192.168.124.12;dbname=test"
'username' => 'root',
'password' => '',
'attributes' => [
PDO::ATTR_TIMEOUT => 10,
],
'class' => 'yii\db\Connection',
],
[
"dsn"=>"mysql:host=192.168.124.13;dbname=test",
'username' => 'main',
'password' => '123456',
'class'=> yii\overload\myDB
],
]
在open方法中,因為是重新new,所以$this->pdo和$this->master都是null
public function open(){
//因為是重新new,所以$this->pdo和$this->master都是null
if ($this->pdo !== null) {
return;
}
//因為是重新new,所以$this->pdo和$this->master都是null
if (!empty($this->masters)) {
$db = $this->getMaster();
if ($db !== null) {
$this->pdo = $db->pdo;
return;
}
throw new InvalidConfigException('None of the master DB servers is available.');
}
if (empty($this->dsn)) {
throw new InvalidConfigException('Connection::dsn cannot be empty.');
}
$token = 'Opening DB connection: ' . $this->dsn;
$enableProfiling = $this->enableProfiling;
try {
//記錄日志
Yii::info($token, __METHOD__);
//如果開啟了性能分析,則記錄性能分析日志(性能分析開啟)
if ($enableProfiling) {
Yii::beginProfile($token, __METHOD__);
}
$this->pdo = $this->createPdoInstance();
$this->initConnection();
//如果開啟了性能分析,則記錄性能分析日志(性能分析關閉)
if ($enableProfiling) {
Yii::endProfile($token, __METHOD__);
}
} catch (\PDOException $e) {
if ($enableProfiling) {
Yii::endProfile($token, __METHOD__);
}
throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
}
}
在createPdoInstance方法中,這個沒什么好說的,就是執行new PDO
protected function createPdoInstance(){
$pdoClass = $this->pdoClass;
if ($pdoClass === null) {
$pdoClass = 'PDO';
if ($this->_driverName !== null) {
$driver = $this->_driverName;
} elseif (($pos = strpos($this->dsn, ':')) !== false) {
$driver = strtolower(substr($this->dsn, 0, $pos));
}
if (isset($driver)) {
if ($driver === 'mssql' || $driver === 'dblib') {
$pdoClass = 'yii\db\mssql\PDO';
} elseif ($driver === 'sqlsrv') {
$pdoClass = 'yii\db\mssql\SqlsrvPDO';
}
}
}
$dsn = $this->dsn;
if (strncmp('sqlite:@', $dsn, 8) === 0) {
$dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7));
}
return new $pdoClass($dsn, $this->username, $this->password, $this->attributes);
}
在initConnection方法中,這個也沒什么好說的,就是去設置PDO屬性和執行afterOpen事件
protected function initConnection(){
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
}
if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'], true)) {
$this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
}
$this->trigger(self::EVENT_AFTER_OPEN);
}
主庫連接getMaterPdo()方法
public function getMasterPdo(){
$this->open();
return $this->pdo;
}
主庫連接getMaster()方法
public function getMaster(){
if ($this->_master === false) {
$this->_master = $this->shuffleMasters //是否隨機打亂master配置數組
? $this->openFromPool($this->masters, $this->masterConfig)
: $this->openFromPoolSequentially($this->masters, $this->masterConfig);
}
return $this->_master;
}
總結
以上是生活随笔為你收集整理的yii连接mysql主从_Connection 数据库主从连接源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1赫兹(格力空调1赫兹多少钱?)
- 下一篇: 在家网上兼职工作(12 个最佳兼职在家工