-- 数据库代理 客户 | | web程序,游戏程序,其他应用程序(c,php,java.......) | | 代理层 (程序开发人员在程序里实现代理,mysqlproxy,amoeba,cobar等软件) | | mysql主 ----> mysql从 写 读 水平切分(如:按用户ID范围切分,id值为1000W前的和id值为1000W后的切分) 垂直切分(表与表之间来切分) 读写分离 (所有读操作和所有写操作来切分) 姓名 性别 部门号 部门号 部门名 张三 男 1 1 技术 李四 女 2 2 销售 王五 男 1 马六 女 2 ====================================================================== amoeba 变形虫 参考网址: http://docs.hexnova.com/amoeba/ 下图架构说明: 1,client只需要有mysql这个客户端登录命令就可以了(实际情况,数据库客户端应该是指web,游戏或其它应用程序),所以这里用同桌的电脑就可以 2,amoeba用宿主机就OK了 3,两台mysql,开两台虚拟机,安装rpm版的mysql就OK了 client 10.1.1.x | | amoeba 10.1.1.35 | |------------------------| mysql mysql 10.1.1.6 10.1.1.7 架构前所有节点准备: 1,主机名三步,互相绑定 10.1.1.35 li.cluster.com 10.1.1.6 mysqlA.cluster.com 10.1.1.7 mysqlB.cluster.com 2,时间同步 rdate -s 10.1.1.35 3,关闭iptables,selinux iptables -F iptables -F -t nat iptables -F -t mangle /etc/init.d/iptables save chkconfig iptables on setenforce 0 # vim /etc/selinux/config SELINUX=disabled --把enforcing改为diabled(下一次重启后才会生效) 4, 配置好yum 5, 静态ip地址 /etc/init.d/NetworkManager stop chkconfig NetworkManager off 配置静态IP地址在下面的配置文件里(eth0对应好你的网卡名,如果你的虚拟机是eth1,则拷贝一个配置文件并手动修改成类似如下示例) # cat /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 ONBOOT=yes NM_CONTROLLED=no BOOTPROTO=static IPADDR=10.1.1.6 NETMASK=255.255.255.0 # cat /etc/sysconfig/network-scripts/ifcfg-eth1 DEVICE=eth1 ONBOOT=yes NM_CONTROLLED=no BOOTPROTO=static IPADDR=10.1.1.7 NETMASK=255.255.255.0 修改完后 /etc/init.d/network restart chkconfig network on 第一步:在10.1.1.35安装amoeba 先把宿主机上已经装过的删除 # rm /usr/local/java -rf # rm /usr/local/amoeba -rf 1,因为是java开发的,需要java运行环境,也就是jdk; 相关软件包在笔记目录下/amoeba_soft jdk-6u45-linux-i586.bin amoeba-mysql-binary-2.2.0.tar.gz ./jdk-6u45-linux-i586.bin --直接./执行它,就可以安装,会自动解压成一个目录在当前目录下 --如果是在64位系统版本上安装这个jdk,可能会报缺少ld-linux.so.2的库,是因为这个jdk是32位的 --解决方法:在64位系统上安装32位的glibc,或者用下面的命令,直接yum安装库就可以了 # yum install ld-linux.so.2 # mv jdk1.6.0_45 /usr/local/java --把jdk的目录移到/usr/local下叫java(这一步不是必要的,只是把名字改短一点,方便而已) 2,解压安装amoeba 软件包 amoeba-mysql-binary-2.2.0.tar.gz 因为这个软件打包的习惯不好,解压后,不会给你建立一个目录 所以: # mkdir /usr/local/amoeba # tar xf amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba # ls /usr/local/amoeba/ --amoeba的所有文件在此目录下 benchmark changelogs.txt lib README.html bin conf LICENSE.txt # cd /usr/local/amoeba/ # ./bin/amoeba --直接用此命令启动,会报找不到java_home Error: JAVA_HOME environment variable is not set. # vim /usr/local/amoeba/bin/amoeba --加上下面两行(把这两句加到最上面,不要加到最下面,因为这个脚本要用到这个变量的),让这个命令在启动时,就会去找/usr/local/java的jdk export JAVA_HOME=/usr/local/java export PATH=$PATH:$JAVA_HOME/bin # ./bin/amoeba start --尝试启动,可以启动,但是我们并没有做任何配置,所以在这里ctrl+c取消掉 -------------------- amoeba的配置文件介绍 Amoeba有哪些主要的配置文件? 想象Amoeba作为数据库代理层,它一定会和很多数据库保持通信,因此它必须知道由它代理的数据库如何连接,比如最基础的:主机IP、端口、Amoeba使用的用户名和密码等等。这些信息存储在$AMOEBA_HOME/conf/dbServers.xml中。 Amoeba为了完成数据切分提供了完善的切分规则配置,为了了解如何分片数据、如何将数据库返回的数据整合,它必须知道切分规则。与切分规则相关的信息存储在$AMOEBA_HOME/conf/rule.xml中。 当我们书写SQL来操作数据库的时候,常常会用到很多不同的数据库函数,比如:UNIX_TIMESTAMP()、SYSDATE()等等。这些函数如何被Amoeba解析呢?$AMOEBA_HOME/conf/functionMap.xml描述了函数名和函数处理的关系。 对$AMOEBA_HOME/conf/rule.xml进行配置时,会用到一些我们自己定义的函数,比如我们需要对用户ID求HASH值来切分数据,这些函数在$AMOEBA_HOME/conf/ruleFunctionMap.xml中定义。 Amoeba可以制定一些可访问以及拒绝访问的主机IP地址,这部分配置在$AMOEBA_HOME/conf/access_list.conf中 Amoeba允许用户配置输出日志级别以及方式,配置方法使用log4j的文件格式,文件是$AMOEBA_HOME/conf/log4j.xml。 ------------------------------- 第二大步: 1,把两台mysql,关闭AB复制,把先前所有的内容都清空,然后两台mysql都新建下面的测试表 mysql> create database aaa; Query OK, 1 row affected (0.00 sec) mysql> use aaa; Database changed mysql> create table emp (emp_id int,ename varchar(20)); Query OK, 0 rows affected (0.01 sec) 2,两台mysql都对mysql进行授权,授权的IP为amoeba的IP mysql> grant all on aaa.* to 'amoeba'@'10.1.1.35' identified by '123'; Query OK, 0 rows affected (0.04 sec) mysql> flush privileges; Query OK, 0 rows affected (0.00 sec) 3,授权后,可以去amoeba(10.1.1.35)这台去连接一下这两个mysql验证授权是否OK,需要能连才行 # mysql -h 10.1.1.6 -u amoeba -p123 -P 3306 # mysql -h 10.1.1.7 -u amoeba -p123 -P 3306 第三大步: # vim /usr/local/amoeba/conf/dbServers.xml --修改此配置文件,定义代理哪些后台数据库 --下面我做了修改的行有23行(写上要操作的后台数据库的schema);26行和29行(授权的用户名和密码);48行和55行(写上后台数据库的IP) 19 20 3306 21 22 23 aaa 24 25 26 amoeba 27 28 29 123 45 46 47 48 10.1.1.6 49 50 51 52 53 54 55 10.1.1.7 56 57 # vim /usr/local/amoeba/conf/amoeba.xml --再修改这个文件,修改了30行和32行(授权的用户和密码),注意这里的用户名和密码跟数据库授权的用户名和密码无关,可以写成不一样的用户和密码来测试 --上一个配置文件里配置的用户名和密码是数据库授权给amoeba连接的用户名和密码,而这个用户名和密码是amoeba给客户端(php,java,web程序等)连接的 30 haha 31 32 456 # cp /usr/local/amoeba/conf/rule.xml /usr/local/amoeba/conf/rule.xml.bak --这里先备份一份 # cat /usr/local/amoeba/conf/rule.xml --修改第三个配置文件,定义数据切分的规则 --下面我是按aaa.emp表里的emp_id这一列来切分,当它为奇数就给server2,为偶数就给server1 emp_id --直接复制粘贴上去,格式空格之类的太多;可以先在vim里:set paste再insert模式粘贴上去就可以了 第四大步: 在amoeba的代理服务器上启动amoeba服务 # ./bin/amoeba start & 第五大步: 客户端测试 在客户端连接进行测试,连接的IP为amoeba的IP,端口为8066 # mysql -h 10.1.1.35 -u haha -p456 -P 8066 注意:amoeba是根据sql解析来进行数据切分的,所以需要把切分的关键字段(这里是emp_id),加入到sql中.否则切分规则无效。无效后,会在 server1,server2 均都插入数据。 mysql> insert into aaa.emp values (1,'aaa'); --这样做是错误的,会在两个后台数据库里都插入数据 --正确的插入方法 mysql> insert into aaa.emp(emp_id,ename) values (1,'aaa'); mysql> insert into aaa.emp(emp_id,ename) values (2,'bbb'); mysql> insert into aaa.emp(emp_id,ename) values (3,'ccc'); mysql> insert into aaa.emp(emp_id,ename) values (4,'ddd'); 然后去server1(10.1.1.6)上查看,只有1,3两条数据 去server2(10.1.1.7)上查看,只有2,4两条数据 数据切分成功 在客户端select去查这张表,得到的是两个数据库数据的综合 mysql> select * from aaa.emp; mysql> select * from emp; delete和update操作经测试也都OK ================================================================== --按照上面的配置文件,只修改rule.xml规则文件如下,做成以emp_id值的范围来进行水平切分 --下面实现的是emp_id小于等于100的任何操作都在server1,大于100的任何操作都是在server2 emp_id server1 server1 server1 emp_id 100]]> server2 server2 server2 ====================================================================== 做数据垂直切分的实验: 架构图和上面的一样 client 10.1.1.x | | amoeba 10.1.1.35 | |------------------------| mysql mysql 10.1.1.6 10.1.1.7 第一大步: 1,先把两个mysql(不做AB复制的)以前的数据删掉,新建两个表来做测试 第一个mysql mysql> create database aaa; mysql> use aaa; mysql> create table aaa (id int); mysql> create table bbb (id int); 第二个mysql mysql> create database aaa; mysql> use aaa; mysql> create table aaa (id int); mysql> create table bbb (id int); 第二大步: 在昨天的配置基础上修改配置文件 在amoba上 # vim /usr/local/amoeba/conf/rule.xml --清空原来的配置,然后复制粘贴下面一段 第三大步: 重启amoeba # ./bin/amoeba stop # ./bin/amoeba start & 第四大步: 在客户端测试 # mysql -h 10.1.1.35 -u haha -p456 -P 8066 mysql> use aaa; mysql> insert into aaa values (1); --这一条被插入到第一个mysql上的aaa.aaa表 mysql> insert into bbb values (2); --这一条被插入到第二个mysql上的aaa.bbb表 mysql> select * from aaa; +------+ | id | +------+ | 1 | +------+ 1 row in set (0.01 sec) mysql> select * from bbb; +------+ | id | +------+ | 2 | +------+ 1 row in set (0.00 sec) 思考: 上面不管是水平切也好,还是垂直切也好,因为我们现在只讨论的是有且仅有两台mysql数据库,这种情况为了保证一台数据丢了,另一台能够保留所有数据 所以最好做一个mysql双主架构 做了双主后,要考虑一个问题 如果server1挂了,如何把server1的读写操作转给server2? 做法: 在dbServer.xml里 把server1,server2做一个ha算法的池叫pool1 把server2,server1做一个ha算法的池叫pool2 3 server1,server2 3 server2,server1 然后把下面的规则改成pool1,pool2就可以了,如下 =============================================================== 使用amoeba实现读写分离功能(queryrouter) 参考网址: http://docs.hexnova.com/amoeba/rw-splitting.html 在上面的基础上,把垂直切分,改成读写分离的架构 client 10.1.1.x | | amoeba 10.1.1.35 | |------------------------|------------| mysql mysql mysql 10.1.1.6 10.1.1.7 10.1.1.35 第一大步: 1,先把三个mysql(读写分离应该要做AB复制,但我们在这里不做AB复制,是为了方便测试) 把以前的数据删掉,新建三个相同的表来做测试,插入三条不同的数据 第一台mysql mysql> create database aaa; mysql> use aaa; mysql> create table aaa (id int); mysql> insert into aaa values (1); 第二台mysql mysql> create database aaa; mysql> use aaa; mysql> create table aaa (id int); mysql> insert into aaa values (2); 第三台mysql mysql> create databases aaa; mysql> use aaa; mysql> create table aaa (id int); mysql> insert into aaa values (3); 第二大步: 在昨天的配置基础上修改配置文件 # vim /usr/local/amoeba/conf/dbServers.xml --在原来配置的两个dbserver的情况下,再增加一段server3,对应的IP为10.1.1.35 --然后把两个从(server2,server3)加入到一个pool,我这里poolname为slavepool 10.1.1.35 1 server2,server3 # vim /usr/local/amoeba/conf/amoeba.xml --打开amoeba.xml文件,在原来的基础上修改,把117行和120行的注释去掉,修改115行和118行为server1(也就是mysql主,默认池和写池) --改119行为server(也就是mysql从,读池) 114 1500 115 server1 116 117 118 server1 119 slavepool 120 121 true --注意:如果只读写分离的话,不做数据切分,那么rule.xml文件就不要配置,保持刚装完amoeba时的原始配置文件就好了(如果清空rule.xml,启动时会报错) 第三大步: 重启amoeba # ./bin/amoeba stop # ./bin/amoeba start 第四大步: 在客户端测试 # mysql -h 10.1.1.35-u amoeba -p123 -P8066 mysql> use aaa; mysql> insert into aaa values (3); --写入的数据只会被插入到server1(这里是没有做复制的,如果做复制会被复制到server2和server2) --读的测试,一次读server2的数据,一次读server3的数据 mysql> select * from aaa; +------+ | id | +------+ | 2 | +------+ mysql> select * from aaa; +------+ | id | +------+ | 3 | +------+ 读写分离成功 扩展讨论: 把dbServer.xml配置文件池的配置修改一下,把server2写两个,server3写一个,算法还是rr server2,server2,server3 --测试的结果是select查询时,两次得到server2的结果,一次得到server3的结果 ================================ 数据切分加读写分离综合讨论 client 10.1.1.x | | amoeba 10.1.1.35 | |--------------|--------------|----------| mysqlA mysqlB mysqlC mysqlD 10.1.1.6 10.1.1.7 10.1.1.8 10.1.1.9 写 读 写 读 --上图中: 对数据进行水平切分,并做读写分离 我们这里测试是用aaa.aaa表的id列来进行水平切分 id为奇数的查询路由mysqlA和mysqlB(mysqlA写,mysqlB读) id为偶数的查询路由mysqlC和mysqld(mysqlC写,mysqlD读) 第一大步: 四台mysql都建立aaa.aaa表,这里不做AB复制(实际环境应该做) mysql> create databases aaa; mysql> use aaa; mysql> create table aaa (id int); --四个mysql都要对amoeba服务器授权 mysql> grant all on aaa.* to 'amoeba'@'10.1.1.35' identified by '123'; mysql> flush privileges; 第二大步: # vim /usr/local/amoeba/conf/dbServers.xml --下面四段是配置四台mysql服务器,名字分别为mysqla,mysqlb,mysqlc,mysqld; 10.1.1.6 10.1.1.7 10.1.1.8 10.1.1.9 --下面一段定义了1个pool叫ddlpool(也就是说除了DML操作外的其它操作大部分为ddl操作,如create表或库等操作就给这个池),用的是1算法,所以一次给mysqla,一次给mysqlc;如果其中一个挂了,它会把DDL操作全给另一个 1 mysqla,mysqlc # vim /usr/local/amoeba/conf/amoeba.xml --定义DML的读写之外的所有操作给ddlpool(由上面的dbServer.xml定义),DML的读写操作在这里不定义(所以下面把wirtePool和readPool这两句给注释掉不用),因为我们要定义到rule.xml里 114 1500 115 ddlpool 116 117 # vim /usr/local/amoeba/conf/rule.xml --定义DML的读写操作的规则文件如下 id 第三大步: 启动amoeba # ./bin/amoeba start 第四大步: 在客户端测试 # mysql -h 10.1.1.35 -u haha -p456 -P 8066 测试1: 测试DDL写操作 mysql > create table bbb(id int); mysql > create table ccc(id int); mysql > create table ddd(id int); mysql > create table eee(id int); ...... 测试结果为:一次在mysqla,一次在mysqlc轮循操作;如果mysqla挂掉,则只会在mysqlc上操作,如果mysqla再启起来,则又会回到1算法的轮循 测试2: 测试DML写操作时,最好奇偶和大小顺序搞乱来测 mysql> insert into aaa(id) values (2); mysql> insert into aaa(id) values (4); mysql> insert into aaa(id) values (1); mysql> insert into aaa(id) values (6); mysql> insert into aaa(id) values (3); mysql> insert into aaa(id) values (5); 测试结果为:奇写在mysqla,偶写在mysqlc 测试3; 测试DML读操作 在mysqlb上插入一条奇数据,一条偶数据 mysql> insert into aaa(id) values (99); mysql> insert into aaa(id) values (100); 同理在mysqld上也插入一条奇数据,一条偶数据 mysql> insert into aaa(id) values (199); mysql> insert into aaa(id) values (200); 在客户端连上amoeba,测试 mysql > select * from aaa where id=99; --结果可以查出来 mysql > select * from aaa where id=100; --结果查不出来,因为你这是偶读操作,会给mysqld,但mysqld上没有这条记录 mysql > select * from aaa where id=199; --结果查不出来,因为你这是奇读操作,会给mysqlb,但mysqlb上没有这条记录 mysql > select * from aaa where id=200; --结果可以查出来 测试结果:从上面这样的验证,可以确认奇读给mysqlb,偶读给mysqld,一切OK ============================== 最后总结 看图说原理: amoeba 代理 奇写 mysqlA mysqlC 偶写 奇读 mysqlB mysqlD 偶读 主要是要考虑数据切分(无论是水平还是垂直),这里还是以奇偶为例 1,上图中A和B做主从,C和D做主从 做法就是,把A和C做一个写池,B和D做一个读池 这样的话,四台都正常的情况下: 奇写给A,偶写给C 奇读给B,偶读给D(或者无论奇偶读在B和D中轮循) 但这样的话,如果A挂掉,那么奇数的读写完全挂掉了 2,为了解决上面的问题,可考虑在上面的基础上把A和C做双主 把A和C做一个3算法(也就是高可用的算法的)池 把C和A反着再做一个3算法的池 用上面的这种rule来做 case division when 0 then (isReadStatement?'read':'ha2'); when 1 then (isReadStatement?'read':'ha1'); 这样做的话,写的操作解决了,但读的操作做有问题 3,当B挂掉,你希望奇读要给A才行, 当D挂掉,你希望偶读要给C才行; 解决:BA 做3算法的池 ha3 DC做 3算法的池 ha4 把上面的规则改成 case division when 0 then (isReadStatement?'ha4':'ha2'); when 1 then (isReadStatement?'ha3':'ha1'); 4, 还有一个重要的问题是: 如果A挂掉后,奇数的写转到了C,但奇数的读还是可能会转给B(这样就会出现问题,因为A挂,B也就没用了) 解决方法:使用监控或脚本的方式,让A挂掉后B也跟着关闭掉 那么A一挂,A和B都要挂掉,奇写给了C,但是奇读没人给了 所以把上面的ha3这个池改成BAD三个成员的3算法池 把上在的ha4这个池改成DCB三个成员的3算法池 其它不变 5,有没有不做双主也能尽量的数据切分,IO分散的方法呢? client amoeba 代理 mysqlA mysqlC mysqlE mysqlG AB3写(主) mysqlB mysqlD mysqlF mysqlH BA3读(主,平时不写,只有A挂时才写)