使用amoeba实现mysql读写分离
amoeba是一个以MySQL为底层数据存储,并对应用提供MySQL协议接口的proxy。它集中地响应应用的请求,依据用户事先设置的规则,将SQL请求发送到特定的数据库上执行。基于此可以实现负载均衡、读写分离、高可用性等需求。与MySQL官方的MySQL Proxy相比,作者强调的是amoeba配置的方便
amoeba使用java所研发,但是amoeba不支持分布式事物(据说现在最新版本已经支持事物),如果用到分布式事物的话不建议使用amoeba。
mysql-proxy支持分布式事物 但需要自己开发样本,如果必须支持事物的话,必须将其大事物切割成小事物并且都在单机上完成,不建议用分布式,那么amoeba就可以发挥作用了,此外amoeba还提供了监控机制,可以监听后端数据库是否存活
Amoeba主要解决以下问题:
·数据切分后复杂数据源整合
·提供数据切分规则并降低数据切分规则给数据库带来的影响
·降低数据库与客户端连接
·读写分离路由
原理就不再进行描述了,我们直接来部署amoeba
规划
首先mysql一主一从都工作在内网中,面向用户的只有amoeba节点,amoeba需要监听在3306端口,以模拟mysql的方式监听用户的请求
服务器角色 | 服务器IP |
Amoeba | 10.12.33.57 |
Mysql-Master | 10.12.33.58 |
Mysql-Slave | 10.12.33.59 |
配置JDK环境
由于amoeba是由java所研发,所以首先需要安装jdk环境
[root@test tools]#chmod +x jdk-6u31-linux-x64-rpm.bin
[root@test tools]#./jdk-6u31-linux-x64-rpm.bin
配置环境变量
[root@test tools]# cat/etc/profile.d/java.sh
exportJAVA_HOME=/usr/java/latest
export PATH=$JAVA_HOME/bin:$PATH
[root@test tools]#source /etc/profile
[root@test tools]# echo $JAVA_HOME
/usr/java/latest
[root@test tools]# java-version
java version"1.6.0_31"
Java(TM) SE RuntimeEnvironment (build 1.6.0_31-b04)
Java HotSpot(TM) 64-BitServer VM (build 20.6-b01, mixed mode)
安装amoeba
[root@test ~]# cd/usr/local/amoeba-mysql-2.2.0/
[root@test ~]# cd /usr/local/amoeba-mysql-2.2.0
[root@testamoeba-mysql-2.2.0]# tar xf amoeba-mysql-binary-2.2.0.tar.gz
[root@node3amoeba-mysql-2.2.0]# ll
total 3148
-rw-r--r-- 1 root root 3161433 Jul 31 10:33 amoeba-mysql-binary-2.2.0.tar.gz
drwxr-xr-x 2 root root 4096 Jul 31 10:33 benchmark #压力测试工具
drwxr-xr-x 2 root root 4096 Feb 29 2012 bin #二进制程序所在位置
-rw-r--r-- 1 root root 3976 Aug 29 2012 changelogs.txt
drwxr-xr-x 2 root root 4096 Jul 31 10:33 conf #配置文件路径
drwxr-xr-x 3 root root 4096 Jul 31 10:33 lib #jar包所在路径
-rw-r--r-- 1 root root 34520 Aug 29 2012 LICENSE.txt
-rw-r--r-- 1 root root 2031 Aug 29 2012 README.html
对我们而言只需要 dbServers.xml以及dbServers.xml 这两个文件即可
将amoeba加入至环境变量
[root@testamoeba-mysql-2.2.0]# cat /etc/profile.d/amoeba.sh
exportAMOEBA_HOME=/usr/local/amoeba-mysql-2.2.0/
exportPATH=$AMOEBA_HOME/bin:$PATH
[root@test amoeba-mysql-2.2.0]#!source
source /etc/profile
配置Mysql主从环境
[root@node2 tools]# tarxf mysql-5.6.13-linux-glibc2.5-x86_64.tar.gz
[root@node2 tools]# mv mysql-5.6.13-linux-glibc2.5-x86_64 /usr/local/mysql
[root@node2 mysql]#useradd -r mysql
[root@node2 mysql]#chown -R root.mysql ./*
创建数据目录
[root@node2 mysql]#mkdir /data/mydata -p
更改数据目录权限
[root@node2 mysql]#chown -R mysql.mysql /data/
[root@node1 mysql]# cpsupport-files/mysql.server /etc/init.d/mysqld
[root@node1 mysql]#chmod +x /etc/init.d/mysqld
复制配置文件,但是注意的是5.6的my-default.cnf 中没有任何的内容,如果有需求,则需要将5.5的配置文件复制到5.6上即可,否则要自行提供配置
如果我们不提供配置也可以启动,因为默认都是有设定好的参数
修改my.cnf
[root@node1 ~]# grep -v'#' /etc/my.cnf | grep -v "^$"
[client]
port = 3306
socket = /tmp/mysql.sock
[mysqld]
port = 3306
socket = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 256M
max_allowed_packet = 1M
table_open_cache = 256
sort_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size =4M
myisam_sort_buffer_size= 64M
thread_cache_size = 8
query_cache_size= 16M
thread_concurrency = 2
datadir = /mydata/data
log-bin=mysql-bin
binlog_format=row
server-id= 58
log-slave-updates=true
gtid-mode=on
enforce-gtid-consistency=true
master-info-repository=TABLE
relay-log-info-repository=TABLE
sync-master-info=1
slave-parallel-workers=4
binlog-checksum=CRC32
master-verify-checksum=1
slave-sql-verify-checksum=1
binlog-rows-query-log_events=1
report-port=3306
report-host=node1
[mysqldump]
quick
max_allowed_packet =16M
[mysql]
no-auto-rehash
[myisamchk]
key_buffer_size = 128M
sort_buffer_size = 128M
read_buffer = 2M
write_buffer = 2M
[mysqlhotcopy]
interactive-timeout
将配置文件复制到slave节点并修改参数:
server-id= 59
log-slave-updates=true
gtid-mode=on
enforce-gtid-consistency=true
master-info-repository=TABLE
relay-log-info-repository=TABLE
sync-master-info=1
slave-parallel-workers=4
binlog-checksum=CRC32
master-verify-checksum=1
slave-sql-verify-checksum=1
binlog-rows-query-log_events=1
report-port=3306
report-host=node2
保存退出,并在各节点初始化mysql数据库
[root@node1 mysql]#scripts/mysql_install_db --user=mysql --datadir=/mydata/data/
[root@node1 mysql]# ls/mydata/data/
ibdata1 ib_logfile0 ib_logfile1 mysql mysql-bin.000001 mysql-bin.000002 mysql-bin.index performance_schema test
启动mysql数据库
[root@node2 mysql]#/etc/init.d/mysqld start
连接mysql 查看主节点状态
mysql> show masterstatus;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB |Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000003| 151 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
在主库授权
mysql> grantreplication slave,replication client on *.* to 'reluser'@'10.12.33.%'identified by 'replpass';
Query OK, 0 rowsaffected (0.00 sec)
mysql> flushprivileges;
Query OK, 0 rowsaffected (0.00 sec)
基于GTID模型的时候启动从节点比较独特
切换至从节点
mysql> CHANGE MASTERTOMASTER_HOST='10.12.33.58',MASTER_USER='reluser',MASTER_PASSWORD='replpass',MASTER_AUTO_POSITION=1;
Query OK, 0 rowsaffected, 2 warnings (0.03 sec)
开启slave功能
mysql> start slave;
Query OK, 0 rows affected,1 warning (0.00 sec)
查看是否同步
mysql> show slavestatus\G
***************************1. row ***************************
Slave_IO_State: Waiting formaster to send event
Master_Host: 10.12.33.58
Master_User: reluser
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000003
Read_Master_Log_Pos: 556
Relay_Log_File:node2-relay-bin.000002
Relay_Log_Pos: 766
Relay_Master_Log_File: mysql-bin.000003
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
验证主从是否同步
在主库创建数据库或表,并在从库上查看是否被同步
mysql> createdatabase hello;
Query OK, 1 rowaffected (0.00 sec)
在从库上查看是否被同步
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| hello |
| mysql |
| performance_schema |
| test |
+--------------------+
5 rows in set (0.01sec)
验证完毕,目前主从同步已没有问题,接下来配置Amoeba
Amoeba配置文件简解
[root@node3 local]# cp-fra amoeba-mysql-2.2.0 amoeba-mysql-2.2.0.bak
[root@node3 local]# cdamoeba-mysql-2.2.0/conf
amoeba.xml配置说明
首先<proxy>段 说明代理参数
而后有很多<service> 来定义服务,所以<proxy>段中会定义很多<service>
当amoeba代理用户向后端访问的时候,其自己指定以哪个用户和密码向后端访问,而我们向后端访问的用户是否应该具有前端用户所访问具有相关属性及权限
为了使其简化,后端的数据库节点需保持一致,但是这样也有一定风险,如果其中一台机器被攻破,所有其他服务器都会有风险
修改面向连接的端口
[root@node3 conf]# vimamoeba.xml
#将端口改为3306
<property name="port">3306</property>
#监听端口,将端口只监听于内网
<propertyname="ipAddress">10.12.33.57</property>
查询路由
#因为需要配置读写分离,由谁读谁写都需要路由的
服务名称来自于dbserver.xml
编辑dbserver
[root@node3 conf]# vimdbServers.xml
每个名称都是由dbserver参数进行定义的,如下所示:
<dbServer name="abstractServer"abstractive="true">
而且多个dbserver可以放到一个组内,可对其实现负载均衡的效果,如下所示:
<dbServer name="server1" parent="abstractServer">
<factoryConfig>
<property name="ipAddress">127.0.0.1</property>
</factoryConfig>
</dbServer>
<dbServername="server2" parent="abstractServer">
<factoryConfig>
<property name="ipAddress">127.0.0.1</property>
</factoryConfig>
</dbServer>
说明定义了组内包含了多个服务器,而且服务器是虚拟机的
<dbServer name="multiPool" virtual="true">
<poolConfigclass="com.meidusa.amoeba.server.MultipleServerPool">
<!-- Load balancing strategy: 1=ROUNDROBIN , 2=WEIGHTBASED , 3=HA-->
多个服务器之间还可以实现loadbalance ,算法为1 ,1为rr 2为基于权重rr 3为sver1 和 sver2 做高可用
<property name="loadbalance">1</property>
<!-- Separated bycommas,such as: server1,server2,server1 -->
<propertyname="poolNames">server1</property>
</poolConfig>
</dbServer>
对我们现在而言,我们暂时不需要负载均衡
配置Amoeba并实现单点读操作
准备环境
服务器角色 | 服务器IP |
Amoeba | 10.12.33.57 |
Mysql-Master | 10.12.33.58 |
首先登陆Mysql服务器对其数据库授权使其拥有读写权限
mysql> grant all on*.* to 'root'@'10.12.33.%' identified by 'mypass';
Query OK, 0 rowsaffected (0.00 sec)
mysql> flushprivileges;
Query OK, 0 rows affected (0.00 sec)
查看用户信息
mysql> show grants for 'root'@'10.12.33.%';
+-----------------------------------------------------------------------------------------------------------------------+
| Grants forroot@10.12.33.% |
+-----------------------------------------------------------------------------------------------------------------------+
| GRANT ALL PRIVILEGESON *.* TO 'root'@'10.12.33.%' IDENTIFIED BY PASSWORD'*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4' |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
修改amoeba配置,编辑dbServer.xml
配置监听端口以及公共继承用户名
<property name="port">3306</property> #启动端口
<propertyname="schema">test</property> #默认打开的库
<property name="user">root</property> #用户名
<property name="password">mypass</property> #定义的连接密码
配置后端mysql节点
<dbServername="node1" parent="abstractServer">
<factoryConfig>
<property name="ipAddress">10.12.33.58</property>
</factoryConfig>
</dbServer>
修改调用server
<property name="poolNames">node1</property>
保存退出并更改amoeba配置文件
修改queryRouter 默认将我们的请求路由到定义的组内
<queryRouterclass="com.meidusa.amoeba.mysql.parser.MysqlQueryRouter">
<propertyname="ruleLoader">
<beanclass="com.meidusa.amoeba.route.TableRuleFileLoader">
<propertyname="ruleFile">${amoeba.home}/conf/rule.xml</property>
<propertyname="functionFile">${amoeba.home}/conf/ruleFunctionMap.xml</property>
</bean>
</property>
<propertyname="sqlFunctionFile">${amoeba.home}/conf/functionMap.xml</property>
<propertyname="LRUMapSize">1500</property>
<propertyname="defaultPool">server1</property>
#加入以下参数,或将参数注释去掉
<propertyname="readPool">node1</property>
<!--
<propertyname="writePool">server1</property>
<propertyname="readPool">server1</property>
-->
<propertyname="needParse">true</property>
</queryRouter>
定义客户端连接amoeba的时候的密码
#注意的是这里不是链接至数据库的密码而是链接amoeaba的密码,而后amoeaba代理用户去连接mysql,因此在dbServer中定义的是amoeba本身到mysql服务器中请求使用的账号密码
这里定义用户名为root密码为hello
<propertyname="authenticator">
<beanclass="com.meidusa.amoeba.mysql.server.MysqlClientAuthenticator">
<property name="user">root</property>
<property name="password">hello</property>
<property name="filter">
<beanclass="com.meidusa.amoeba.server.IPAccessController">
<property name="ipFile">${amoeba.home}/conf/access_list.conf</property>
</bean>
</property>
</bean>
</property>
保存退出并启动amoeba
[root@node3 conf]#amoeba start
查看3306是否被监听
[root@node3 ~]# netstat-lntp | grep 3306
tcp 0 0 ::ffff:10.12.33.0:3306 :::* LISTEN 8444/java
连接amoeba后端mysql测试
登录到任意一台机器前提是必须装有mysql客户端
[root@node2 mysql]#mysql -uroot -h10.12.33.57 -p'hello'
Welcome to the MySQLmonitor. Commands end with ; or \g.
Your MySQL connectionid is 279633047
Serverversion: 5.1.45-mysql-amoeba-proxy-2.2.0 MySQL Community Server (GPL)
Copyright (c) 2000,2013, Oracle and/or its affiliates. All rights reserved.
Oracle is a registeredtrademark of Oracle Corporation and/or its
affiliates. Other namesmay be trademarks of their respective
owners.
Type 'help;' or '\h'for help. Type '\c' to clear the current input statement.
mysql>
可以看到amoeba的提示信息,说明连接amoeba并通过其连接数据库登录成功
查看服务器版本,可看到依然是后端服务器的版本
mysql> selectversion();
+------------+
| version() |
+------------+
| 5.6.13-log |
+------------+
1 row in set (0.00 sec)
可说明依旧是发往后端服务器执行操作命令,如果后端服务器都想让其读写,那么只要定义一个池即可
实现读写分离
目前我们的需求有两台服务器,我们要求其中一台只能读,而另一台能读又能写
或者主库只能写 从库只能读,那么我们可以做以下操作:
因为之前我们是在主库上创建用户,按理来说会同步到从库上,那么首先确保从库的用户是否存在
mysql> show grantsfor root@'10.12.33.%';
+-----------------------------------------------------------------------------------------------------------------------+
| Grants forroot@10.12.33.% |
+-----------------------------------------------------------------------------------------------------------------------+
| GRANT ALL PRIVILEGESON *.* TO 'root'@'10.12.33.%' IDENTIFIED BY PASSWORD'*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4' |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
编辑配置
[root@node3 conf]# vimdbServers.xml
加入配置信息,将node2作为读节点
<dbServername="node2" parent="abstractServer">
<factoryConfig>
<propertyname="ipAddress">10.12.33.59</property>
</factoryConfig>
</dbServer>
编辑amoeba.xml配置文件,到最下行可看到以下信息:
<propertyname="defaultPool">node1</property>
默认节点是node1,node1能读又能写,因为是主节点,所以默认是没有问题的
但是按理说读操作应该让从库来实现,写的话肯定是主库
所以该为:
<propertyname="writePool">node1</property>
<propertyname="readPool">node2</property>
如果有多个从库,或者又想让主库也进行读操作,可以改为:
<propertyname="readPool">node2,node2,node1</property>
这样node2的优先级就比较大一些,如果有其他新节点则依次添加即可
启动amoeba
[root@node3 conf]#amoeba start
再次连接amoeba
[root@node2 ~]# mysql -uroot -h10.12.33.57 -p'hello'
mysql> select version();
+------------+
| version() |
+------------+
| 5.6.13-log |
+------------+
1 row in set (0.01 sec)
这个操作应当在读服务器上操作
至于如何查看在哪个服务器上执行的操作,可以查看当前UUID即可
mysql> show global variables like '%uuid%';
+---------------+--------------------------------------+
| Variable_name | Value |
+---------------+--------------------------------------+
| server_uuid |26ba3a66-1b85-11e4-bba5-525400abdf2b |
+---------------+--------------------------------------+
1 row in set (0.01 sec)
发现这是主服务器的UUID,说明我们的default参数发挥作用了,我们给了其默认库,很显然默认库应该指向主的,因为默认库有些时候会执行写的操作
如果不想使用默认库,则将其注释掉即可,这样读写依然是分开的
为了验证效果,查看读写是否一定写到主服务器上,这里我们使用tcpdump来抓包并对其分析
只要我们抓住跟amoeba跟mysql通信的相关报文即可
[root@node1 mysql]# tcpdump -i eth0 -nn -s0 -XX tcp dstport 3306
于是我们使用amoeba连接进数据库并创建数据库查看抓到的报文
mysql> create database test1;
查看报文
14:37:49.200243 IP 10.12.33.57.51653 >10.12.33.58.3306: Flags [P.], seq 19:45, ack 178, win 244, options [nop,nop,TSval 360909164 ecr 522883778], length 26
0x0000: 5254 00ab df2b 5254 005a edbc 0800 4500 RT...+RT.Z....E.
0x0010: 004e f6bd 4000 4006 ed61 0a0c 2139 0a0c .N..@.@..a..!9..
0x0020: 213a c9c5 0cea 7e28 7241 c601 620d 8018 !:....~(rA..b...
0x0030: 00f4 6418 0000 0101 080a 1583 096c 1f2a ..d..........l.*
0x0040: 92c2 1600 0000 0363 7265 6174 6520 6461 .......create.da
0x0050: 7461 6261 7365 2074 6573 7431 tabase.test1
设定hello1为默认库,并创建表尝试
mysql> use test1;
Database changed
mysql> create table tb1 (NAME CHAR(10));
Query OK, 0 rows affected (0.03 sec)
再次查看主库抓包信息
.O....T[!....create table tb1 (NAMECHAR(10))
11:08:05.996328 IP 10.12.33.57.51345 > 10.12.33.58.3306: Flags [.], ack1143, win 156, options [nop,nop,TS val 89125960 ecr 251120332], length 0
E..4..@.@.:x
.!9
,....dN......
.O.H....
同样的插入数据并查看
mysql> insert into tb1 values ('test'),('tom');
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
再次查看主库抓包信息
.Q....D.(....insert into tb1 values ('test'),('tom')
11:09:55.151462 IP 10.12.33.59.50767 > 10.12.33.58.3306: Flags [.], ack 511,win 477, options [nop,nop,TS val 251148119 ecr 251229487], length 0
实现主库读写/从库只读
编辑dbServer,修改参数如下所示
定义组内可读节点,两个node2表示node2的优先级比较高,优先次数高于主库,而node1也能读 但是读请求稍微比较少一点
<dbServer name="ReadPool" virtual="true">
<poolConfigclass="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 -->
<propertyname="poolNames">node2,node2,node1</property>
</poolConfig>
</dbServer>
保存退出,使amodeba调用池,因此还需编辑amoeaba.xml
<property name="writePool">node1</property>
<property name="readPool">ReadPool</property>
进入数据库执行select语句并再次抓包查看
mysql> select * from tb1;
+------+
| NAME |
+------+
| test |
| tom |
+------+
2 rows in set (0.01 sec)
可看到主库没有回显任何信息,在从库显示抓包信息如下:
14:53:10.169782 IP 10.12.33.59.34299 >10.12.33.57.3306: Flags [P.], seq 473:495, ack 3700, win 455, options[nop,nop,TS val 523743485 ecr 361824589], length 22
E..J..@.@.(/
.!;
.!9....Q,.{B/.......w.....
.7.....M.....select * from tb1
14:53:10.171609 IP 10.12.33.57.55409 >10.12.33.59.3306: Flags [P.], seq 77:99, ack 235, win 115, options [nop,nop,TSval 361830482 ecr 523737591], length 22
E..JZ.@.@..u
.!9
.!;.q....+[.YW....s.......
...R.7.......select * from tb1
14:53:10.172382 IP 10.12.33.57.55409 >10.12.33.59.3306: Flags [.], ack 320, win 115, options [nop,nop,TS val 361830483ecr 523743487], length 0
E..4Z.@.@...
.!9
.!;.q....+q.YX=...sF?.....
...S.7..
14:53:10.174525 IP 10.12.33.59.34299 >10.12.33.57.3306: Flags [.], ack 3785, win 455, options [nop,nop,TS val523743490 ecr 361830485], length 0
E..4..@.@.(D
.!;
.!9....Q,..B/.R....F&.....
.7.....U
以上,读写分离配置大致完成,感谢各位