codecamp

数据库读写分离

PHALAPI-进阶篇5(数据库读写分离以及多库使用)

前言

先在这里感谢phalapi框架创始人@dogstar,为我们提供了这样一个优秀的开源框架.

读写分离是我们常用的一种解决方案,它可以解决大量读操作的时候数据库瓶颈的问题,我们在真正开发一个项目的过程中可能会遇一个接口或者一个操作中需要用到多个数据库操作的时候怎么办,今天的教程就从这个两个问题出来,来聊聊使用phalapi怎么解决这个问题.

附上:

喵了个咪的博客:w-blog.cn

官网地址:http://www.phalapi.net/

开源中国Git地址:http://git.oschina.net/dogstar/PhalApi/tree/release

1. 读写分离

在我们的生活中无时无刻不面临着并发的问题,比如下班高峰交通问题,人们用了红绿灯来解决的一部分问题(但是很痛苦这个就和查询的时候阻塞一样的,10秒做update操作,10秒做select操作一样的),那么人们有想到了一个方法高架,高架的路线是和正常的路线一致的(数据相同) ,高架分两条路而且一条路上的车不能到另外一条路上(读写分离) 这样就解决了我们这类常规并发的问题

其实看起来上面这种解决方案是比较好的其实并不是这样的,有两条路但是其实它的宽度是不一样的,我们这里把一个叫做写路,一个叫做读路,我们如果要做一个读路并且把他成倍的变宽是一件很简单的事情(也就是多从库),但是我们要建立一条写路可以,但是我们要把这条写路变宽是比较困难的,我们一般不这样做.

1.1 mysql主从配置

我是使用了阿里云的多读库服务,这里引用一下百度经验的mysql主从处理

----------------------------------------------------------------------------------------------------

MYSQL【一台主机多个实例的主从配置】

1、建立账号

进入mysql数据库,创建一个用于从库备份的账号

mysql>GRANT REPLICATION SLAVE ON *.* TO 'slave_test'@'10.19.194.57' IDENTIFIED BY 'Password@123456';

mysql>FLUSH PRIVILEGES;   ---刷新

该命令详情参考grant创建用户命令

2、如果已经有一个实例在运行(因为开始已经安装了mysql,所以有一个默认的),那么再启动一个实例,方法如下:

(1)shell>cd /usr/local/mysql/

(2)shell>mkdir -pv /data/mysql2

(3)shell>chown -R mysql.mysql /data/mysql2

(4)shell>./scripts/mysql_install_db  --user=mysql  --datadir=/data/mysql2  这个为另外一个目录,该步骤意味初始化目录,并创建一个实例

3、配置/etc/my.cnf,配置方法如下

[mysqld_multi]

mysqld = /usr/local/mysql/bin/mysqld_safe

mysqladmin = /usr/local/mysql/bin/mysqladmin

user = root

[mysqld] ## 本来mysql配置

server-id = 1

port = 3306

socket = /data/mysql/mysql.sock

datadir = /data/mysql

pid-file = /data/mysql/wzj.pid

log-error = /data/mysql/wzj.err

log-bin = /data/mysql/log/bin-log

log-bin-index = /data/mysql/log/mysql-bin.index

user = mysql

[mysqld2]  ##创建实例配置

server-id = 2

port = 3307

socket = /tmp/mysql2.sock

datadir = /data/mysql2/                         //mysql2存储数据库的地方,也就是实例

pid-file = /data/mysql2/mysql2.pid       //mysql2的pid文件记录

log-error = /data/mysql2/mysql2.err     //为错误日志文件的地方

user = mysql

#master-host =10.19.194.57

#master-user ='wzj_slave_test'

#master-pass ='Password@123456'

#master-port =3306

#master_connect_retry=60

#replicate-do-db =mysql_wzj

#replicate-ignore-db=mysql--------该段已注释,因为新版本中,需要用CHANGE MASTER TO 命令去设置

【注意】:以上文件的目录,皆为系统创建,并非手动去创建,设置配置文件后,重启mysql,文件就会自动创建

4、重启,然后可以使用msyql_multi命令启动实例,再查看一下master主库的状态,其中Binlog_Do_DB表示要备份的数据库,Binlog_Ignore_DB表示不备份的数据库

shell>/etc/rc.d/init.d/mysqld start/stop/restart

shell>/usr/local/mysql/bin/mysqld_multi start 1-2  启动实例 (1-2表示启动1、2两个实例)

shell>mysql master: mysql -P 3306 -u root -p -S /data/mysql/mysql.sock (密码:1)

mysql>show master status; or show master status \G;   显示主库的状态,其中File 和Position 数值要记住,下面的mysql语句要用到,用于设置从库

5、进入从库,并设置从库的一些参数

shell>slave:mysql -P 3307 -u root -p -S /tmp/mysql2.sock (密码:回车)   ---另外开个远程

mysql>CHANGE MASTER TO

mysql> MASTER_HOST='10.19.194.57',  ---主库的地址

mysql>MASTER_USER='wzj_slave_test',  ---主库用户

mysql>MASTER_PASSWORD='Password@123456',   ---主库的密码

mysql>MASTER_LOG_FILE='bin-log.000013' ,  ---此参数为master status中的File值

mysql>MASTER_LOG_POS=120;   ---此数值为Position数值

6、设置好后,运行start slave,如果下图红线地方是YES,则成功,否则,查看配置文件是否有错或有误。

slave_io_running  :yes

slave_sql_running :yes

7、可以查看从库下mysql进程信息

mysql>show processlist;

8、测试

在主库上面建新的数据库,然后在从库中查看是否有同步~

----------------------------------------------------------------------------------------------------

1.2 开始实现

思想我们已经讲完了相信大家已经多多少少的理解了,但是我们要如何在phalapi去实现读写分离的操作

1.首先我们要在初始化一个数据库连接,我们需要建立一个配置文件read.php和dbs.php一样只是连接这读库的配置项,并且注册

//读库
DI()->read = function (){
    $debug = !empty($_GET['__sql__']) ? true : false;
    return new PhalApi_DB_NotORM(DI()->config->get('read'), $debug);
};

2.对PhalApi_Model_NotORM文件中的getORM进行修改

/**
 * 快速获取ORM实例,注意每次获取都是新的实例
 */
protected function getORM($id = NULL){
    $table = $this->getTableName($id);
    if($id == 'read'){
        return DI()->read->$table;
    }
    return DI()->notorm->$table;
}

3.当我们在model层使用$this->getORM('read') 的时候就会使用读库,使用$this->getORM() 会默认使用写库

//select操作
$this->getORM('read')->select('*')->where('aId', $aId)->fetchAll();
//insert,update,delete操作
$this->getORM()->insert();

2. 多库处理

有些人会问,为什么需要进行多库处理,所有的表放到一个库不就好了吗?

在原来重构别人外包项目是遇到过一个库100来张表,没有备注没有文档,几乎没法看,在我自己做一个比较大的项目的时候我也是做了一些考虑把库根据业务分解成一个一个子库,可以很好的避免表太多结构混乱的问题,而且我们可以随时把任何一个子库移植出去,当我一个mysql承载了很多压力,我可以把这个mysql上面的子库分解到多个mysql上面来实现压力分流.

看了上面的这一段介绍相信大家不言而喻的了解了这样做的好处,但是如何实现呢,聪明的大家可能在想不久和上面一样在多注册一个数据库连接嘛,其实大家说对了一半,我们是需要注册一个连接,但是我们需要一种统一的规范来使用这类操作

1.初始化数据库连接,一样的复制一份dbs.php为developers.php配置连接开发者库

//开发者库
DI()->developers = function (){
    $debug = !empty($_GET['__sql__']) ? true : false;
    return new PhalApi_DB_NotORM(DI()->config->get('developers'), $debug);
};

2.对PhalApi_Model_NotORM文件中的getORM进行修改

/**
 * 快速获取ORM实例,注意每次获取都是新的实例
 */
protected function getORM($id = NULL){
    $table = $this->getTableName($id);
    if($id == 'developers'){
        return DI()->developers->$table;
    }
    return DI()->notorm->$table;
}

3.在之前我们不是建立了我们的公用General文件我们在构建这样的结构

--General                   //公用目录
    --developers                //通用项目库目录
        --Domain                //项目库Domain层
        --Model             //项目库Model层

然后我们在这里的Model进行的操作都是使用$this->getORM('developers')进行操作

这样规范的好处就是在与共享模块化 让正常的一个Model层不会使用到任何不同库的操作,我们多个项目用到其他的一个库可以进行代码复用,

3. 总结

其实实现今天介绍的两种解决问题的方案还有很多很多,不同的业务可能需求不同实现也就不一样希望今天的介绍对大家有帮助,后面两篇教程将基于我写的两个拓展分别介绍分表分库操作和redis实际的使用,希望大家进一步关注!

注:笔者能力有限有说的不对的地方希望大家能够指出,也希望多多交流!

官网QQ交流群:421032344 欢迎大家的加入!

notrom进阶以及事务操作
解决大量数据存储数据库分表分库拓展
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

八、视频教程

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }