热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

yii连接mysql主从_Connection数据库主从连接源码剖析

连接总体流程随机打乱从库配置(master可以选择是否打乱,slave一定会打乱;如果master没有配置数组则直接使用$dsn和$username作为

9c5c114a4da79cc9b874ac24b780fa6b.png

连接总体流程

随机打乱从库配置(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;

}



推荐阅读
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 本文深入探讨了NoSQL数据库的四大主要类型:键值对存储、文档存储、列式存储和图数据库。NoSQL(Not Only SQL)是指一系列非关系型数据库系统,它们不依赖于固定模式的数据存储方式,能够灵活处理大规模、高并发的数据需求。键值对存储适用于简单的数据结构;文档存储支持复杂的数据对象;列式存储优化了大数据量的读写性能;而图数据库则擅长处理复杂的关系网络。每种类型的NoSQL数据库都有其独特的优势和应用场景,本文将详细分析它们的特点及应用实例。 ... [详细]
  • 本文介绍如何使用 Python 的 DOM 和 SAX 方法解析 XML 文件,并通过示例展示了如何动态创建数据库表和处理大量数据的实时插入。 ... [详细]
  • 字节流(InputStream和OutputStream),字节流读写文件,字节流的缓冲区,字节缓冲流
    字节流抽象类InputStream和OutputStream是字节流的顶级父类所有的字节输入流都继承自InputStream,所有的输出流都继承子OutputStreamInput ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 2022年7月20日:关键数据与市场动态分析
    2022年7月20日,本文对当日的关键数据和市场动态进行了深入分析。主要内容包括:1. 关键数据的解读与趋势分析;2. 市场动态的变化及其对投资策略的影响;3. 相关经济指标的评估。通过这些分析,帮助读者更好地理解当前市场环境,为决策提供参考。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文介绍了如何利用Shell脚本高效地部署MHA(MySQL High Availability)高可用集群。通过详细的脚本编写和配置示例,展示了自动化部署过程中的关键步骤和注意事项。该方法不仅简化了集群的部署流程,还提高了系统的稳定性和可用性。 ... [详细]
  • 如何优化MySQL数据库性能以提升查询效率和系统稳定性 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • 开发日志:高效图片压缩与上传技术解析 ... [详细]
author-avatar
情感顾问世界
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有