目录
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作为数据库代理,以中间件的形式存在,拓扑图如下所示:
目前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原理图
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)
分离成功了。\~\~\~\~
发表评论