Yii实现多数据库主从读写分离的方法


Posted in PHP onDecember 29, 2014

本文实例讲述了Yii实现多数据库主从读写分离的方法。分享给大家供大家参考。具体分析如下:

Yii框架数据库多数据库、主从、读写分离 实现,功能描述:

1.实现主从数据库读写分离 主库:写 从库(可多个):读

2.主数据库无法连接时 可设置从数据库是否 可写

3.所有从数据库无法连接时 可设置主数据库是否 可读

4.如果从数据库连接失败 可设置N秒内不再连接

利用yii扩展实现,代码如下:

<?php

/** 

 * 主数据库 写 从数据库(可多个)读 

 * 实现主从数据库 读写分离 主服务器无法连接 从服务器可切换写功能 

 * 从务器无法连接 主服务器可切换读功 

 * by lmt 

 * */ 

class DbConnectionMan extends CDbConnection { 

    public $timeout = 10; //连接超时时间 

    public $markDeadSeconds = 600; //如果从数据库连接失败 600秒内不再连接  

    //用 cache 作为缓存全局标记 

    public $cacheID = 'cache'; 

 

    /** 

     * @var array $slaves.Slave database connection(Read) config array. 

     * 配置符合 CDbConnection. 

     * @example 

     * 'components'=>array( 

     *   'db'=>array( 

     *    'connectionString'=>'mysql://<master>', 

     *    'slaves'=>array( 

     *     array('connectionString'=>'mysql://<slave01>'), 

     *     array('connectionString'=>'mysql://<slave02>'), 

     *    ) 

     *   ) 

     * ) 

     * */ 

    public $slaves = array(); 

    /** 

     *  

     * 从数据库状态 false 则只用主数据库 

     * @var bool $enableSlave 

     * */ 

    public $enableSlave = true; 

 

    /** 

     * @var slavesWrite 紧急情况主数据库无法连接 切换从服务器(读写). 

     */ 

    public $slavesWrite = false; 

 

    /** 

     * @var masterRead 紧急情况从主数据库无法连接 切换从住服务器(读写). 

     */ 

    public $masterRead = false; 

 

    /** 

     * @var _slave 

     */ 

    private $_slave; 

 

    /** 

     * @var _disableWrite 从服务器(只读). 

     */ 

    private $_disableWrite = true; 

 

    /** 

     * 

     * 重写 createCommand 方法,1.开启从库 2.存在从库 3.当前不处于一个事务中 4.从库读数据 

     * @param string $sql 

     * @return CDbCommand 

     * */ 

    public function createCommand($sql = null) { 

        if ($this->enableSlave && !emptyempty($this->slaves) && is_string($sql) && !$this->getCurrentTransaction() && self::isReadOperation($sql) && ($slave = $this->getSlave()) 

        ) { 

            return $slave->createCommand($sql); 

        } else { 

            if (!$this->masterRead) { 

                if ($this->_disableWrite && !self::isReadOperation($sql)) { 

 

                    throw new CDbException("Master db server is not available now!Disallow write operation on slave server!"); 

                } 

            } 

            return parent::createCommand($sql); 

        } 

    } 

 

    /** 

     * 获得从服务器连接资源 

     * @return CDbConnection 

     * */ 

    public function getSlave() { 

        if (!isset($this->_slave)) { 

            shuffle($this->slaves); 

            foreach ($this->slaves as $slaveConfig) { 

                if ($this->_isDeadServer($slaveConfig['connectionString'])) { 

                    continue; 

                } 

                if (!isset($slaveConfig['class'])) 

                    $slaveConfig['class'] = 'CDbConnection'; 

 

                $slaveConfig['autoConnect'] = false; 

                try { 

                    if ($slave = Yii::createComponent($slaveConfig)) { 

                        Yii::app()->setComponent('dbslave', $slave); 

                        $slave->setAttribute(PDO::ATTR_TIMEOUT, $this->timeout); 

                        $slave->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); 

                        $slave->setActive(true); 

                        $this->_slave = $slave; 

                        break; 

                    } 

                } catch (Exception $e) { 

                    $this->_markDeadServer($slaveConfig['connectionString']); 

                    Yii::log("Slave database connection failed!ntConnection string:{$slaveConfig['connectionString']}", 'warning'); 

 

                    continue; 

                } 

            } 

 

            if (!isset($this->_slave)) { 

                $this->_slave = null; 

                $this->enableSlave = false; 

            } 

        } 

        return $this->_slave; 

    } 

 

    public function setActive($value) { 

        if ($value != $this->getActive()) { 

            if ($value) { 

                try { 

                    if ($this->_isDeadServer($this->connectionString)) { 

                        throw new CDbException('Master db server is already dead!'); 

                    } 

                    //PDO::ATTR_TIMEOUT must set before pdo instance create 

                    $this->setAttribute(PDO::ATTR_TIMEOUT, $this->timeout); 

                    $this->open(); 

                } catch (Exception $e) { 

                    $this->_markDeadServer($this->connectionString); 

                    $slave = $this->getSlave(); 

                    Yii::log($e->getMessage(), CLogger::LEVEL_ERROR, 'exception.CDbException'); 

                    if ($slave) { 

                        $this->connectionString = $slave->connectionString; 

                        $this->username = $slave->username; 

                        $this->password = $slave->password; 

                        if ($this->slavesWrite) { 

                            $this->_disableWrite = false; 

                        } 

                        $this->open(); 

                    } else { //Slave also unavailable 

                        if ($this->masterRead) { 

                            $this->connectionString = $this->connectionString; 

                            $this->username = $this->username; 

                            $this->password = $this->password; 

                            $this->open(); 

                        } else { 

                            throw new CDbException(Yii::t('yii', 'CDbConnection failed to open the DB connection.'), (int) $e->getCode(), $e->errorInfo); 

                        } 

                    } 

                } 

            } else { 

                $this->close(); 

            } 

        } 

    } 

 

    /** 

     * 检测读操作 sql 语句 

     *  

     * 关键字: SELECT,DECRIBE,SHOW ... 

     * 写操作:UPDATE,INSERT,DELETE ... 

     * */ 

    public static function isReadOperation($sql) { 

        $sql = substr(ltrim($sql), 0, 10); 

        $sql = str_ireplace(array('SELECT', 'SHOW', 'DESCRIBE', 'PRAGMA'), '^O^', $sql); //^O^,magic smile 

        return strpos($sql, '^O^') === 0; 

    } 

 

    /** 

     * 检测从服务器是否被标记 失败. 

     */ 

    private function _isDeadServer($c) { 

        $cache = Yii::app()->{$this->cacheID}; 

        if ($cache && $cache->get('DeadServer::' . $c) == 1) { 

            return true; 

        } 

        return false; 

    } 

 

    /** 

     * 标记失败的slaves. 

     */ 

    private function _markDeadServer($c) { 

        $cache = Yii::app()->{$this->cacheID}; 

        if ($cache) { 

            $cache->set('DeadServer::' . $c, 1, $this->markDeadSeconds); 

        } 

    } 

}

main.php配置:components 数组中,代码如下:
'db'=>array( 

        'class'=>'application.extensions.DbConnectionMan',//扩展路径 

        'connectionString' => 'mysql:host=192.168.1.128;dbname=db_xcpt',//主数据库 写 

        'emulatePrepare' => true, 

        'username' => 'root', 

        'password' => 'root', 

        'charset' => 'utf8', 

        'tablePrefix' => 'xcpt_', //表前缀 

        'enableSlave'=>true,//从数据库启用 

   'urgencyWrite'=>true,//紧急情况 主数据库无法连接 启用从数据库 写功能 

    'masterRead'=>true,//紧急情况 从数据库无法连接 启用主数据库 读功能 

        'slaves'=>array(//从数据库 

            array(   //slave1 

                'connectionString'=>'mysql:host=localhost;dbname=db_xcpt', 

                'emulatePrepare' => true, 

                'username'=>'root', 

                'password'=>'root', 

                'charset' => 'utf8', 

                'tablePrefix' => 'xcpt_', //表前缀 

            ), 

   array(   //slave2 

                'connectionString'=>'mysql:host=localhost;dbname=db_xcpt', 

                'emulatePrepare' => true, 

                'username'=>'root', 

                'password'=>'root', 

                'charset' => 'utf8', 

                'tablePrefix' => 'xcpt_', //表前缀 

            ), 

 

        ), 

),

希望本文所述对大家基于Yii框架的php程序设计有所帮助。

PHP 相关文章推荐
真正面向对象编程:PHP5.01发布
Oct 09 PHP
打造计数器DIY三步曲(上)
Oct 09 PHP
解决phpmyadmin 乱码,支持gb2312和utf-8
Nov 20 PHP
PHP表单提交后引号前自动加反斜杠的原因及三种办法关闭php魔术引号
Sep 30 PHP
基于PHPexecl类生成复杂的报表表头示例
Oct 14 PHP
关于Laravel Route重定向的一个注意点
Jan 16 PHP
数组任意位置插入元素,删除特定元素的实例
Mar 02 PHP
PHP针对中英文混合字符串长度判断及截取方法示例
Mar 31 PHP
详解PHP处理字符串类似indexof的方法函数
Jun 11 PHP
php利用云片网实现短信验证码功能的示例代码
Nov 18 PHP
PHP使用PDO抽象层获取查询结果的方法示例
May 10 PHP
PHP实现获取ip地址的5种方法,以及插入用户登录日志操作示例
Feb 28 PHP
php调用mysql存储过程实例分析
Dec 29 #PHP
php生成excel列名超过26列大于Z时的解决方法
Dec 29 #PHP
php+mysqli实现批量替换数据库表前缀的方法
Dec 29 #PHP
PHP跨平台获取服务器IP地址自定义函数分享
Dec 29 #PHP
PHP中使用xmlreader读取xml数据示例
Dec 29 #PHP
php读取远程gzip压缩网页的方法
Dec 29 #PHP
php导入大量数据到mysql性能优化技巧
Dec 29 #PHP
You might like
一个程序下载的管理程序(四)
2006/10/09 PHP
Fatal error: session_start(): Failed to initialize storage module: files问题解决方法
2014/05/04 PHP
学习php设计模式 php实现访问者模式(Visitor)
2015/12/07 PHP
优化WordPress中文章与评论的时间显示
2016/01/12 PHP
Yii2框架可逆加密简单实现方法
2017/08/25 PHP
js实现刷新iframe的方法汇总
2015/04/27 Javascript
js+css实现超简洁的二级下拉菜单效果代码
2015/09/07 Javascript
jQuery绑定事件-多种实现方式总结
2016/05/09 Javascript
JavaScript简单获取页面图片原始尺寸的方法
2016/06/21 Javascript
jQuery中deferred对象使用方法详解
2016/07/14 Javascript
JavaScript数据结构之数组的表示方法示例
2017/04/12 Javascript
Avalonjs双向数据绑定与监听的实例代码
2017/06/23 Javascript
让你彻底掌握es6 Promise的八段代码
2017/07/26 Javascript
jquery+css实现下拉列表功能
2017/09/03 jQuery
详解ES6 Symbol 的用途
2018/10/14 Javascript
微信小程序常见页面跳转操作简单示例
2019/05/01 Javascript
[56:00]2018DOTA2亚洲邀请赛 4.6 淘汰赛 VP vs TNC 第二场
2018/04/10 DOTA
Python中不同进制互相转换(二进制、八进制、十进制和十六进制)
2015/04/05 Python
python机器学习实战之最近邻kNN分类器
2017/12/20 Python
Django中Forms的使用代码解析
2018/02/10 Python
Python实现读取机器硬件信息的方法示例
2018/06/09 Python
Python操作mongodb数据库的方法详解
2018/12/08 Python
Python eval的常见错误封装及利用原理详解
2019/03/26 Python
Django处理多用户类型的方法介绍
2019/05/18 Python
查看端口并杀进程python脚本代码
2019/12/17 Python
python datetime处理时间小结
2020/04/16 Python
使用python实现CGI环境搭建过程解析
2020/04/28 Python
小学英语教学反思案例
2014/02/04 职场文书
党员公开承诺践诺书
2014/03/25 职场文书
班干部学习委员竞选稿
2015/11/20 职场文书
2016春季运动会前导词
2015/11/25 职场文书
HTML+CSS+JS实现图片的瀑布流布局的示例代码
2021/04/22 HTML / CSS
Nginx四层负载均衡的配置指南
2021/06/11 Servers
动漫APP软件排行榜前十名,半次元上榜,第一款由腾讯公司推出
2022/03/18 杂记
Windows下载并安装MySQL8.0.x 版本的完整教程
2022/04/10 MySQL
Navicat Premium自定义 sql 标签的创建方式
2022/09/23 数据库