热门标签 | 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;

}



推荐阅读
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
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社区 版权所有