Mysql主从复制和读写分离方案

Mysql读写分离的实现思路

目前较为常见的mysql读写分离分为两种:

1.基于程序代码内部实现:在代码中对select操作分发到从库;其它操作由主库执行;这类方法也是目前生产环境应用最广泛,知名的如DISCUZ X2。优点是性能较好,因为在程序代码中实现,不需要增加额外的设备作为硬件开支。缺点是需要开发人员来实现,运维人员无从下手。

2.基于中间代理层实现:我们都知道代理一般是位于客户端和服务器之间,代理服务器接到客户端请求后通过判断然后转发到后端数据库。在这有两个代表性程序:

amoeba:由陈思儒开发,作者曾就职于阿里巴巴,现就职于盛大。该程序由java语言进行开发,目前只听说阿里巴巴将其用于生产环境。另外,此项目严重缺少维护和推广(作者有个官方博客,很多用户反馈的问题发现作者不理睬)

mysql-proxy:mysql-proxy为mysql开源项目,通过其自带的lua脚本进行sql判断,虽然是mysql官方产品,但是mysql官方并不建议将mysql-proxy用到生产环境。

经过上述简单的比较,通过程序代码实现mysql读写分离自然是一个不错的选择。但是并不是所有的应用都适合在程序代码中实现读写分离,像大型SNS、B2C这类应用可以在代码中实现,因为这样对程序代码本身改动较小;像一些大型复杂的java应用,这种类型的应用在代码中实现对代码改动就较大了。所以,像这种应用一般就会考虑使用代理层来实现

综合上述分析,考虑到需要与应用层解耦,现采用中间件解决方案,使用Amoeba做SQL路由,实现数据库读写分离。

一、利用amoeba实现读写分离

1.amoeba概述

Amoeba是一个以MySQL为底层数据存储,并对应用提供MySQL协议接口的proxy。它集中地响应应用的请求,依据用户事先设置的规则,将SQL请求发送到特定的数据库上执行。基于此可以实现负载均衡、读写分离、高可用性等需求。与MySQL官方的MySQL Proxy相比,作者强调的是amoeba配置的方便(基于XML的配置文件,用SQLJEP语法书写规则,比基于lua脚本的MySQL Proxy简单)。

Amoeba相当于一个SQL请求的路由器,目的是为负载均衡、读写分离、高可用性提供机制,而不是完全实现它们。用户需要结合使用MySQL的 Replication等机制来实现副本同步等功能。amoeba对底层数据库连接管理和路由实现也采用了可插拨的机制,第三方可以开发更高级的策略类来替代作者的实现。这个程序总体上比较符合KISS原则的思想。

2.amoeba优势

Amoeba主要解决以下问题:

a). 数据切分后复杂数据源整合

b). 提供数据切分规则并降低数据切分规则给数据库带来的影响

c). 降低数据库与客户端连接

d). 读写分离路由

3.amoeba缺点

a)、目前还不支持事务

b)、暂时不支持存储过程(近期会支持)

c)、不适合从amoeba导数据的场景或者对大数据量查询的query并不合适(比如一次请求返回10w以上甚至更多数据的场合)

d)、暂时不支持分库分表,amoeba目前只做到分数据库实例,每个被切分的节点需要保持库表结构一致:

4.环境准备

1) Mysql主从同步

Mysql主从同步过程这里不再阐述,详情请参考《Mysql数据库企业级应用实践》。

授权用户连接mysql主从(可以为不同的用户,权限自行设置)

mysql -uroot -p123456 -S /data/3306/mysql.sock
grant all on *.* to root@'192.168.80.%' identified by '123456';
flush privileges;

1) 搭建jvm环境

cd /server/tools/
rpm -ivh jdk-7u79-linux-x64.rpm
ls -ld /usr/java/jdk1.7.0_79/
java -version
ln -s /usr/java/jdk1.7.0_79/ /usr/java/jdk
echo 'export JAVA_HOME=/usr/java/jdk' >>/etc/profile
echo 'export JAVA_BIN=${JAVA_HOME}/bin' >>/etc/profile
echo 'export CLASSPATH=${JAVA_HOME}/jre/lib:${JAVA_HOME}/lib' >>/etc/profile
echo 'export PATH=$PATH:${JAVA_HOME}/bin:${JAVA_HOME}/jre/bin' >>/etc/profile
source /etc/profile

5.Amoeba数据库代理安装

Amoeba作为数据库代理,以中间件的形式存在,拓扑图如下所示:

image-20201017212522541

目前Amoeba for Mysql最新版本为amoeba-mysql-3.0.5-RC-distribution.zip。下载地址:[https://sourceforge.net/projects/amoeba/files/]{.underline}

安装过程

cd /server/tools/
unzip amoeba-mysql-3.0.5-RC-distribution.zip
cp -rf amoeba-mysql-3.0.5-RC /application/
/application/amoeba-mysql-3.0.5-RC/bin/launcher    #启动amoeba

启动时可能会报错

The stack size specified is too small, Specify at least 228k
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit

解决

vi /application/amoeba-mysql-3.0.5-RC/jvm.properties
修改 JVM_OPTIONS="-server -Xms256m -Xmx1024m -Xss196k -XX:PermSize=16m -XX:MaxPermSize=96m"
为   JVM_OPTIONS="-server -Xms1024m -Xmx1024m -Xss256k -XX:PermSize=16m -XX:MaxPermSize=96m"

再次启动Amoeba。查看是否出错,(短时间内可能不会生效,稍等一会)

6.配置mysql读写分离

若使用Amoeba完成读写分离,需要分别对dbServers.xml和amoeba.xml两个配置文件进行配置。与在应用层实现读写分离不同,使用Amoeba实现读写分离只需要修改配置文件,并不会产生硬编码耦合,有利于系统扩展和维护。

1) 配置RS

首先是配置dbServers.xml,主要是配置真实Mysql数据库连接信息。

<!-- 该dbServer节点abstractive="true",包含Mysql的公共配置信息,其他dbServer节点都继承该节点 -->
设置节点配置的继承结构,可以避免重复配置相同信息,减少配置文件冗余
<dbServer name="abstractServer" abstractive="true">
    <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory">
        <property name="connectionManager">${defaultManager}</property>
        <property name="sendBufferSize">64</property>
        <property name="receiveBufferSize">128</property>

        <!-- mysql port -->
        <!-- Mysql默认端口 -->
        <property name="port">3306</property>

        <!-- mysql schema -->
        <!-- 默认连接的数据库,若不存在需要事先创建,否则Amoeba启动报错 -->
        <property name="schema">test</property>

        <!-- mysql user -->
        <property name="user">root</property>

        <property name="password">123456</property>
    </factoryConfig>

    <poolConfig class="com.meidusa.toolkit.common.poolable.PoolableObjectPool">
        <property name="maxActive">500</property>
        <property name="maxIdle">500</property>
        <property name="minIdle">1</property>
        <property name="minEvictableIdleTimeMillis">600000</property>
        <property name="timeBetweenEvictionRunsMillis">600000</property>
        <property name="testOnBorrow">true</property>
        <property name="testOnReturn">true</property>
        <property name="testWhileIdle">true</property>
    </poolConfig>
</dbServer>
定义后面真实DB
        <dbServer name="server1"  parent="abstractServer">
                <factoryConfig>
                        <!-- mysql ip -->
                        <property name="ipAddress">192.168.80.100</property>
                        <property name="port">3306</property>
                        <property name="schema">test</property>
                        <property name="user">root</property>
                        <property name="password">123456</property>
                </factoryConfig>
        </dbServer>

        <dbServer name="server2"  parent="abstractServer">
                <factoryConfig>
                        <!-- mysql ip -->
                        <property name="ipAddress">192.168.80.100</property>
                        <property name="port">3307</property>
                        <property name="schema">test</property>
                        <property name="user">root</property>
                        <property name="password">123456</property>
                </factoryConfig>
        </dbServer>
配置server池(也可以不配置,直接调用前面定义的服务)
<dbServer name="master" virtual="true">
                <poolConfig class="com.meidusa.amoeba.server.MultipleServerPool">
                        <!-- Load balancing strategy: 1=ROUNDROBIN , 2=WEIGHTBASED , 3=HA-->
                        <property name="loadbalance">1</property>

                        <!-- Separated by commas,such as: server1,server2,server1 -->
                        <property name="poolNames">server1</property>
                </poolConfig>
        </dbServer>
        <dbServer name="slave" virtual="true">
                <poolConfig class="com.meidusa.amoeba.server.MultipleServerPool">
                        <!-- Load balancing strategy: 1=ROUNDROBIN , 2=WEIGHTBASED , 3=HA-->
                        <property name="loadbalance">1</property>

                        <!-- Separated by commas,such as: server1,server2,server1 -->
                        <property name="poolNames">server2</property>
                </poolConfig>
        </dbServer>

2) 配置代理数据库连接信息及读写池

配置代理

<property name="port">8066</property>
<property name="ipAddress">192.168.80.100</property>
<property name="user">root</property>
<property name="password">liwenbin</property>

配置读写池

                <property name="defaultPool">master</property>

                <property name="writePool">master</property>
                <property name="readPool">slave</property>

启动amoeba

/application/amoeba-mysql-3.0.5-RC/bin/launcher &

7.测试读写分离

登录amoeba创建表
use test;
create table sxit (id int(10) ,name varchar(10));
登录从库停止从库复制
slave stop;
登录amoeba插入记录
insert into sxit values('1','zhangsan');
select * from sxit    #查不到记录
登录从库启动从库复制
slave start;
登录amoeba插入记录
select * from sxit    #查看到记录

注意:

对于show databases;等之类的查看可能会查询主库

二、利用mysql-proxy实现读写分离

1.MySQL Proxy介绍

MySQL Proxy是一个处于你的client端和MySQL server端之间的简单程序,它可以监测、分析或改变它们的通信。它使用灵活,没有限制,常见的用途包括:负载平衡,故障、查询分析,查询过滤和修改等等。

MySQL Proxy就是这么一个中间层代理,简单的说,MySQL Proxy就是一个连接池,负责将前台应用的连接请求转发给后台的数据库,并且通过使用lua脚本,可以实现复杂的连接控制和过滤,从而实现读写分离和负载平衡。对于应用来说,MySQL Proxy是完全透明的,应用则只需要连接到MySQL Proxy的监听端口即可。当然,这样proxy机器可能成为单点失效,但完全可以使用多个proxy机器做为冗余,在应用服务器的连接池配置中配置到多个proxy的连接参数即可。

MySQL Proxy更强大的一项功能是实现"读写分离",基本原理是让主数据库处理事务性查询,让从库处理SELECT查询。数据库复制被用来把事务性查询导致的变更同步到集群中的从库。

2.MySQL Proxy原理图

  1. image-20201017212811168

3.Mysql proxy环境搭建

关于mysql、mysql主从的搭建,在此不再演示

useradd  mysql-proxy -s /sbin/nologin -M
cd /server/tools
wegt http://mirrors.sohu.com/mysql/MySQL-Proxy/mysql-proxy-0.8.4-linux-glibc2.3-x86-64bit.tar.gz
tar zxvf mysql-proxy-0.8.4-linux-glibc2.3-x86-64bit.tar.gz
mv mysql-proxy-0.8.4-linux-glibc2.3-x86-64bit /application/
ln -s /application/mysql-proxy-0.8.4-linux-glibc2.3-x86-64bit/ /application/mysql-proxy
echo 'export PATH=$PATH:/application/mysql-proxy/bin/' >>/etc/profile
tail -1 /etc/profile
source /etc/profile
which mysql-proxy

4.mysql-proxy命令的一些参数解释:

--help-all   :获取全部帮助信息;
--proxy-address=host:port  :代理服务监听的地址和端口;
--admin-address=host:port  :管理模块监听的地址和端口;
--proxy-backend-addresses=host:port :后端mysql服务器的地址和端口;
--proxy-read-only-backend-addresses=host:port :后端只读mysql服务器的地址和端口;
--proxy-lua-script=file_name :完成mysql代理功能的Lua脚本;
--daemon  :以守护进程模式启动mysql-proxy;
--keepalive  :在mysql-proxy崩溃时尝试重启之;
--log-file=/path/to/log_file_name :日志文件名称;
--log-level=level :日志级别;
--log-use-syslog :基于syslog记录日志;
--plugins=plugin:在mysql-proxy启动时加载的插件;
--user=user_name  :运行mysql-proxy进程的用户;
--defaults-file=/path/to/conf_file_name : 默认使用的配置文件路径;其配置段使用[mysql-proxy]标识;
--proxy-skip-profiling : 禁用profile;
--pid-file=/path/to/pid_file_name :进程文件名;

5.编写简单启动脚本

vi /etc/init.d/mysql-proxy
############################################
#!/bin/sh
export LUA_PATH=/application/mysql-proxy/share/doc/mysql-proxy/?.lua:/applicaton/mysql-proxy/lib/mysql-proxy/lua/?.lua

mode=$1
if [ -z "$mode" ] ; then
  mode="start"
fi

case $mode in
  'start')
mysql-proxy --daemon \
--log-level=debug \
--user=mysql-proxy \
--keepalive \
--log-file=/var/log/mysql-proxy.log \
--plugins="proxy" \
--proxy-backend-addresses=192.168.80.100:3306 \
--proxy-read-only-backend-addresses=192.168.80.100:3307 \
--proxy-lua-script=/application/mysql-proxy/share/doc/mysql-proxy/rw-splitting.lua \
--plugins="admin" \
--admin-username="admin" \
--admin-password="admin" \
--admin-lua-script="/application/mysql-proxy/lib/mysql-proxy/lua/admin.lua"
    ;;

  'stop')
    killall mysql-proxy
    ;;

  'restart')
    if $0 stop ; then
      $0 start
    else
      echo  "retart failed!!!"
      exit 1
    fi
    ;;
esac
exit 0

更改为700权限

chmod 700 /etc/init.d/mysql-proxy

启动mysql-proxy

[root@mysql-proxy /]# /etc/init.d/mysql-proxy start   
[root@mysql-proxy /]# netstat -lnpt |grep mysql-proxy 
tcp        0      0 0.0.0.0:4040         0.0.0.0:*          LISTEN      4261/mysql-proxy    
tcp        0      0 0.0.0.0:4041         0.0.0.0:*          LISTEN      4261/mysql-proxy  
4040是proxy端口;4041是admin端口,也就是管理

mysql-master端操作

#添加测试用户:

mysql> grant all on *.* to 'root'@'10.0.0.%' identified by '123456';
多开几个终端,测试连接:
[root@mysql-master ~]# mysql -uroot -p -h 10.0.0.103 --port 4040 
Enter password:
mysql> select user,host from mysql.user;
+--------+-------------+
| user   | host        |
+--------+-------------+
| root   | 127.0.0.1   |
| rep    | 192.168.1.% |
| root   | 192.168.1.% |
| root   | localhost   |
+--------+-------------+
5 rows in set (0.01 sec)
mysql>

登录管理admin查看:

[root@mysql-master ~]# mysql -uadmin -p -h 10.0.0.103 --port 4041
mysql> select * from backends;
+-------------+-------------------+---------+------+------+-------------------+
| backend_ndx | address           | state   | type | uuid | connected_clients |
+-------------+-------------------+---------+------+------+-------------------+
|           1 | 192.168.1.11:3306 | up      | rw   | NULL |                 0 |
|           2 | 192.168.1.12:3306 | unknown | ro   | NULL |                 0 |
+-------------+-------------------+---------+------+------+-------------------+
2 rows in set (0.00 sec)
注:因为rw-splitting.lua脚本默认有4个链接才启用分离;所以多开启几个终端;多测试几下;你也可以去修改里面的相关值;

最终结果:

mysql> select * from backends;
+-------------+-------------------+-------+------+------+-------------------+
| backend_ndx | address           | state | type | uuid | connected_clients |
+-------------+-------------------+-------+------+------+-------------------+
|           1 | 192.168.1.11:3306 | up    | rw   | NULL |                 0 |
|           2 | 192.168.1.12:3306 | up    | ro   | NULL |                 0 |
+-------------+-------------------+-------+------+------+-------------------+
2 rows in set (0.00 sec)
##出现两个up,那么分离成功了。

mysql-proxy端:

[root@mysql-proxy /]# lsof -i:4040
COMMAND    PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
mysql-pro 4299 mysql-proxy   10u  IPv4  19401      0t0  TCP *:yo-main (LISTEN)
mysql-pro 4299 mysql-proxy   12u  IPv4  19510      0t0  TCP 192.168.1.10:yo-main->192.168.1.11:54046 (ESTABLISHED)
mysql-pro 4299 mysql-proxy   16u  IPv4  19602      0t0  TCP 192.168.1.10:yo-main->192.168.1.11:54048 (ESTABLISHED)
mysql-pro 4299 mysql-proxy   18u  IPv4  19604      0t0  TCP 192.168.1.10:yo-main->192.168.1.12:54371 (ESTABLISHED)
mysql-pro 4299 mysql-proxy   20u  IPv4  19606      0t0  TCP 192.168.1.10:yo-main->192.168.1.12:54372 (ESTABLISHED)

分离成功了。\~\~\~\~

服务器技术交流群请加微信 YJZyjz