MySQL读写分离实战

【分布式架构初探】

第四节-MySQL读写分离实战

4.1 预备工具以及环境
4.2 虚拟机的准备
4.3 CentOS7的安装
4.4 MySQL数据库的安装
4.5 MySQL主从配置(一主一从)
4.6 实现读写分离代码:驱动方式
4.7 实现读写分离代码:SQL解析方式

4.1 预备工具以及环境

这个实战我们是在CentOS7操作系统下面进行的,所以我们需要准备:
1. VMWare 虚拟机           [http://sw.bos.baidu.com/sw-search-sp/software/61e22b5779e96/VMware_workstation_full_12.5.0.11529.exe](下载链接)
2. CentOS7 GHost 镜像文件 [http://mirrors.aliyun.com/centos/7/isos/x86_64/](下载链接)
3. MySQL5.7 数据库        
4. Java/php的编译环境开发

4.2 虚拟机的准备

如果有裸机的童鞋可以略过这一节,这里假设VMWare 安装在Windows操作系统上
VMWare 的安装很简单,下一步、下一步,完成,最后启动就是这样的
4.2-01.png

4.3 CentOS7的安装

我们在VMWare 上面来安装CentOS7操作系统,因为我们要做一主一从的数据库架构,
所以我们需要准备两个CentOS7 的节点,一个作为主库,另外一个作为从库。
安装步骤如下:

1,选择文件->新建虚拟机

4.3-01.png

2,选择安装来源,选择下载的 Centos7 ios 文件目录

4.3-02.png

3,选择客户机操作系统,(Linux CentOS 64 位)

4.3-03.png

4,填写虚拟系统名称及存放位置(可以默认)

4.3-04.png

5,指定磁盘容量(视宿主机硬盘大小)

4.3-06.png

6,成功创建之后

4.3-07.png

7, 点击开启虚拟机

4.3-08.png

8,选择 Install CentOS 7 安装

4.3-09.png

9,继续之后如图,选择安装位置为自动分区,点击网络和主机名进入并配置,点击开始安装

4.3-10.png

10,配置Root账号密码,等待安装即可

4.3-11.png

4.4 MySQL数据库的安装

  1. 下载mysql的repo源
    如果没有 wget 先yum install -y wget
$ wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
  1. 安装mysql-community-release-el7-5.noarch.rpm包
    非 root 用户 加上 sudo
$ rpm -ivh mysql-community-release-el7-5.noarch.rpm
  1. 安装mysql
$ yum install -y mysql-server
  1. 启动mysql
$ service mysqld start
  1. 进入mysql and 修改 mysql 密码
$ mysql
$ UPDATE mysql.user SET password=PASSWORD("your password") WHERE user="root" AND Host="localhost";

4.5 MySQL主从配置(一主一从)


环境说明:

  • 系统环境:Centos 7.
  • Mysql版本:5.6.33
  • Master-Server : 192.168.157.88
  • Slave-Server : 192.168.157.89
  • 主从数据库都建立测试库(如:testdatabase)

异步配置

Master 主库配置

1, 增加从库同步账号

mysql> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.*
-> TO repl@'192.168.157.89' IDENTIFIED BY 'repl_password';

2, 配置my.cnf

[mysqld]
log-bin=mysql-bin
server-id=1

3, 重启 Mysql , 查看 Master Status

# Service mysqld restart;
//进入mysql
mysql> SHOW MASTER STATUS;
4.5-01.png

Slave 从库配置

1, 配置 my.cnf

[mysqld]
log_bin           = mysql-bin
server_id         = 2
relay_log         = mysql-relay-bin
log_slave_updates = 1
read_only         = 1

# Service Mysqld restart;

2, 链接Master ,启动Slave

mysql> CHANGE MASTER TO MASTER_HOST='server1',

    -> MASTER_USER='repl',

    -> MASTER_PASSWORD='repl_password',

    -> MASTER_LOG_FILE='mysql-bin.000001',

    -> MASTER_LOG_POS=0;
//启动 Slave
mysql> START SLAVE;
4.5-02.png

如图:
Slave_IO_Running:Yes
Slave_SQL_Running:Yes
同步配置成功,可以在主库插入数据库测试!


半同步配置

Master 主库配置

1, 安装插件:semisync_master.so

mysql> INSTALL PLUGIN rpl_semi_sync_master soname 'semisync_master.so';
//配置全局变量
mysql> SET GLOBAL rpl_semi_sync_master_enabled = 1;
mysql> SET GLOBAL rpl_semi_sync_master_timeout = 1000;
mysql> SHOW VARIBALES LIKE '%semi%';
4.5-03.png

2,配置 my.cnf 让 Mysql 启动生效

[mysqld]
rpl_semi_sync_master_enabled = 1
rpl_semi_sync_master_timeout = 1000;

# Service mysqld restart

Slave 从库配置

1, 安装插件

mysql> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';  
mysql> SET GLOBAL rpl_semi_sync_slave_enabled = 1;  
mysql> STOP SLAVE;
mysql> START SLAVE;
mysql> SHOW VARIABLES LIKE '%semi%';
4.5-04.png

2,配置 my.cnf 让 Mysql 启动生效

[mysqld]
rpl_semi_sync_slave_enabled = 1

# Service mysqld restart

检查半同步是否生效

Master:

mysql> show global status like 'rpl_semi%';
4.5-05.png

4.6 实现读写分离代码:驱动方式

通过驱动的方式来实现应用层的读写分离,前提还是要完成上面的步骤:
MySQL主从配置同步复制

然后通过

jdbc:mysql:replication://192.168.157.88:3306,192.168.157.89:3306/DB_TEST7?roundRobinLoadBalance=true&characterEncoding=UTF-8

实现读写分离
或者
负载均衡

jdbc:mysql:loadbalance

这里先简单介绍下MySQL驱动下面,loadbalance 与 replication 的区别:
当MySQL架构采用Master-Master 主主架构时候,我们就可以用jdbc:mysql:loadbalance这种方式
应用层任意读写任何一个节点都不会出现问题,因为是双向同步复制机制的。

但是当你的架构是一主一从/一主多从的情况下(Master-Slave) ,再使用loadbalance就会出现问题,
因为loadbalance 是采用随机或者轮询的策略来做负载均衡算法,如果这个时候正好insert/update 随机到一个slave从机上面的时候,
你的数据就无法与Master进行同步了。

因此这种情况下,需要实现“主读写,从只读”,则使用jdbc:mysql:replication 可以完成这个目的。
replication 可以实现insert/update 写操作发送到 Master执行, 而读操作 从Slave 上面执行。

下面用Java来开发一个程序进行测试:
工程的目录如下:

4.6-01.png

Master-Server : 192.168.157.88
Slave-Server : 192.168.157.89

  1. mysqlDB.properties 的配置
DBDriver=com.mysql.jdbc.Driver
url=jdbc\:mysql\:replication\://192.168.157.88\:3306,192.168.157.89\:3306/DB_TEST7?roundRobinLoadBalance\=true&characterEncoding\=UTF-8
name=root
pass=123456
characterEncoding=utf8

2.initdatabase.sql

create database DB_TEST7;
use DB_TEST7;
CREATE TABLE users (
  id int(5) NOT NULL auto_increment,
  name varchar(20)NOT NULL,
  PRIMARY KEY  (`id`)
); 

3.TestMysql.java

package com.xiaozhangwangxiao.test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Test;
import com.xiaozhangwangxiao.utils.DBUtil;

/**
 * 测试数据库读写类
 * 
 * @author 小张网校 学员:张瑞超
 * 
 */
public class TestMysql {
    @Test
    public void testSelectAndWrite() throws SQLException {
        DBUtil uW = new DBUtil();
        DBUtil uR = new DBUtil();
        Connection connR = uR.getConn(); // connectionsToHostsMap()
        Connection connW = uW.getConn(); // connectionsToHostsMap()
        connW.setAutoCommit(false); // 自动提交为False
        connR.setAutoCommit(false); // 自动提交为False
        String inSql = "insert into users(name) values('copy');";
        String sql = "select name,count(1) as count_num from users where name = 'copy'";
        Statement sW = connW.createStatement();
        Statement sR = connR.createStatement();
        ResultSet r = null;
        int l = 1;
        try {
            for(int i = 0; i < 100; i++){
                sW.execute(inSql);
                connW.commit();
                System.out.println("sql = " + sql);
                r = sR.executeQuery(sql);
                while (r.next()) {
                    System.out.println("name" + r.getString("name") + " 第:" + l
                             + "条");
                }
                l++;
            }
        } catch (Exception e) {
            connW.rollback();
            e.printStackTrace();
        }
        r.close();
        sW.close();
        sR.close();
        connW.close();
        connR.close();
    }
}

测试类主要模拟批量数据提交到Master ,然后从 Slave读取数据,实现读写分离,测试主从库数据是否同步一致。

4.7 实现读写分离代码:SQL解析方式

另外我们还有一种方式来实现读写分离,这种方式也是现在一些中间件的做法, 通过SQL解析器解析出select,或者insert
送到对应的Master/Slave的数据库节点进行执行,下面我们show出代码,在select还采用了 随机负载算法来进行 负载均衡。

php代码实现如下:

备注:
Mysql 主库 :192.168.157.88
Mysql 从库 :192.168.157.89

<?  
/** 
* mysql读写分离 
* @author 小张网校 学员:nemo
*/  
class db   
{   
    //定义链接
    pbulic $host = array(
                'read' =>array(
                    'host'=>'192.168.157.89:3306',
                    'username' => 'root',
                    'password' => 'root'
                ),
                'write' =>array(
                    'host'=>'192.168.157.89:3306',
                    'username' => 'root',
                    'password' => 'root'
                )
            );
    public function __construct($sql)   
    {   
        $chestr = strtolower(trim($sql));  
        
        //根据 sql 判断链接库  
        if(substr($chestr,0,6)=='select')   
        {   
       
            echo 'I am using select db..<br>';  
            $rand = rand(0,1);
            $middleArr = array('read','write');//读库分流
            $link = mysql_connect(join(',',$this->host[$middleArr[$rand]])) or die("Could not connect: " . mysql_error());   
            mysql_select_db("test");   
            $result = mysql_query($sql);   
            while ($row = mysql_fetch_array($result, MYSQL_NUM))   
            {   
                printf("%s %s", $row[0],$row[1]);   
            }   
            echo mysql_get_host_info($link).mysql_get_server_info($link).mysql_get_proto_info($link).mysql_get_client_info().'<br>';          
        }   
        else  
        {   
            echo 'I am using insert db..<br>';  
            $link = mysql_connect(join(',',$this->host['write'])) or die("Could not connect: " . mysql_error());   
            mysql_select_db("test");   
            $result = mysql_query($sql);   
            echo @mysql_affected_rows($result);   
            echo mysql_get_host_info($link).mysql_get_server_info($link).mysql_get_proto_info($link).mysql_get_client_info().'<br>';         
        }   
            
    }   
}   
   
$d = new db(" INSERT INTO nemo values('nemo','420818119') ");   
$d2 = new db("SELECT * from `nemo`");  

总结一下:自己实现读写分离以及负载均衡需要

  1. 配置Master-Slave 的MySQL数据同步的机制(打开binlog ,relaylog 等)
  2. 应用层实现读写分离(通过jdbc:mysql:replication驱动实现 or 中间件SQL解析器实现)

下一节我们学习Mycat中间件,也是采用这种方式对SQL进行解析,完成读写分离和负载均衡的逻辑实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容