favouriter 2020-08-18
当表的数量达到几百上千张表时, 众多的业务模块都访问这个数据库, 压力会非常的大, 考虑对其进行分库
数据气氛根据其切分规则的类型, 可以分为两种切分模式:
垂直切分:按照业务模块进行切分, 将不同模块的表切分到不同的数据库中。
水平切分的原则:
按照ID取模:对ID进行取模, 余数决定该行数据切分到哪个表或者库中
按照日期:按照年月日, 将数据切分到不同的表或者库中
第一:能不切分尽量不要切分
第二:如果要切分一定要选择合适的切片规则, 提前规划好
第三:数据切分尽量通过数据冗余或者表分组来降低扩库join的可能
强一直性事务
最终一致性事务
Redis incr命令
数据生成主键
UUID
通过业务分析, 将不同的join查询拆分成多个select
建立全局表
冗余字段, 避免跨库查询
E-R分片(将有ER关系的记录都存储在一个库中)
阿里的TDDL, Cobar
基于阿里Cobar开发的mycat
Mycat数据库分库分表中间件
活跃的、性能好的开源数据库中间件!
一个彻底开源的,面向企业应用开发的大数据库集群
支持事务、ACID、可以替代MySQL的加强版数据库
一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群
一个融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server
结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品
一个新颖的数据库中间件产品
支持SQL92标准
支持MySQL、Oracle、DB2、SQL Server、PostgreSQL等DB的常见SQL语法
遵守Mysql原生协议,跨语言,跨平台,跨数据库的通用中间件代理。
基于心跳的自动故障切换,支持读写分离,支持MySQL主从,以及galera cluster集群。
支持Galera for MySQL集群,Percona Cluster或者MariaDB cluster
基于Nio实现,有效管理线程,解决高并发问题。
支持数据的多片自动路由与聚合,支持sum,count,max等常用的聚合函数,支持跨库分页。
支持单库内部任意join,支持跨库2表join,甚至基于caltlet的多表join。
支持通过全局表,ER关系的分片策略,实现了高效的多表join查询。
支持多租户方案。
支持分布式事务(弱xa)。
支持XA分布式事务(1.6.5)。
支持全局序列号,解决分布式下的主键生成问题。
分片规则丰富,插件化开发,易于扩展。
强大的web,命令行监控。
支持前端作为MySQL通用代理,后端JDBC方式支持Oracle、DB2、SQL Server 、 mongodb 、巨杉。
支持密码加密
支持服务降级
支持IP白名单
支持SQL黑名单、sql注入***拦截
支持prepare预编译指令(1.6)
支持非堆内存(Direct Memory)聚合计算(1.6)
支持PostgreSQL的native协议(1.6)
支持mysql和oracle存储过程,out参数、多结果集返回(1.6)
支持zookeeper协调主从切换、zk序列、配置zk化(1.6)
支持库内分表(1.6)
集群基于ZooKeeper管理,在线升级,扩容,智能优化,大数据处理(2.0开发版)。
Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL 语句做了 一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库, 并将返回的结果做适当的处理,最终再返回给用户。
前面讲了 Mycat 是一个开源的分布式数据库系统,但是由于真正的数据库需要存储引擎,而 Mycat 并没有 存储引擎,所以并不是完全意义的分布式数据库系统。那么 Mycat 是什么?Mycat 是数据库中间件,就是介于数据库与应用之间,进行数据处理与交互的中间服务。由于前面讲的对数据进行分片处理之后,从原有的一个库,被切分为多个分片数据库,所有的分片数据库集群构 成了整个完整的数据库存储。
如上图所表示,数据被分到多个分片数据库后,应用如果需要读取数据,就要需要处理多个数据源的数据。如果没有数据库中间件,那么应用将直接面对分片集群,数据源切换、事务处理、数据聚合都需要应用直接处理, 原本该是专注于业务的应用,将会花大量的工作来处理分片后的问题,最重要的是每个应用处理将是完全的重复 造轮子。
所以有了数据库中间件,应用只需要集中与业务处理,大量的通用的数据聚合,事务,数据源切换都由中间 件来处理,中间件的性能与处理能力将直接决定应用的读写性能,所以一款好的数据库中间件至关重要。
前面一节讲了数据库中间件,通常对实际应用来说,并不需要知道中间件的存在,业务开发人员只需要知道 数据库的概念,所以数据库中间件可以被看做是一个或多个数据库集群构成的逻辑库。
在云计算时代,数据库中间件可以以多租户的形式给一个或多个应用提供服务,每个应用访问的可能是一个 独立或者是共享的物理库,常见的如阿里云数据库服务器 RDS。
注意:需要先安装jdk
下载mycat
wget http://dl.mycat.io/1.6-RELEASE/Mycat-server-1.6-RELEASE-20161028204710- linux.tar.gz
tar -zxvf Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz
- 启动命令:./mycat start - 停止命令:./mycat stop - 重启命令:./mycat restart - 查看状态:./mycat status
使用mysql的客户端直接连接mycat服务。默认服务端口为【8066】
schema.xml介绍 schema.xml作为Mycat中重要的配置文件之一,管理着Mycat的逻辑库、表、分片规则、DataNode以及
DataHost之间的映射关系。弄懂这些配置,是正确使用Mycat的前提。
schema 标签用于定义MyCat实例中的逻辑库 Table 标签定义了MyCat中的逻辑表 dataNode 标签定义了MyCat中的数据节点,也就是我们通常说所的数据分片。dataHost标签在mycat逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写分离配置 和心跳语句。
schema.xml配置
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100"> <!-- auto sharding by id (long) --> <table name="item" dataNode="dn1,dn2,dn3" rule="mod-long" /> <!-- 一个库中拆成多个表 --> <table name="user" dataNode="dn1" rule="mod-long" subTables="user$1-3" /> </schema> <!-- <dataNode name="dn1$0-743" dataHost="localhost1" database="db$0-743" /> --> <dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataNode name="dn2" dataHost="localhost1" database="db2" /> <dataNode name="dn3" dataHost="localhost1" database="db3" /> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.10.135:3306" user="root" password="111111" > </writeHost> </dataHost> </mycat:schema>
server.xml几乎保存了所有mycat需要的系统配置信息。最常用的是在此配置用户名、密码及权限。
Server.xml配置
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mycat:server SYSTEM "server.dtd"> <mycat:server xmlns:mycat="http://io.mycat/"> <system> <property name="defaultSqlParser">druidparser</property> </system> <user name="mycat"> <property name="password">mycat</property> <property name="schemas">TESTDB</property> </user> </mycat:server>
配置rule.xml
rule.xml里面就定义了我们对表进行拆分所涉及到的规则定义。我们可以灵活的对表使用不同的分片算法,或者对 表使用相同的算法但具体的参数不同。这个文件里面主要有tableRule和function这两个标签。在具体使用过程中可 以按照需求添加tableRule和function。
此配置文件可以不用修改,使用默认即可。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mycat:rule SYSTEM "rule.dtd"> <mycat:rule xmlns:mycat=”http://io.mycat/“ > <tableRule name="sharding-by-intfile"> <rule> <columns>sharding_id</columns> <algorithm>hash-int</algorithm> </rule> </tableRule> <function name="hash-int" class="io.mycat.route.function.PartitionByFileMap"> <property name="mapFile">partition-hash-int.txt</property> </function> </mycat:rule>
<tableRule name="sharding-by-intfile"> <rule> <columns>user_id</columns> <algorithm>hash-int</algorithm> </rule> </tableRule> <function name="hash-int" class="io.mycat.route.function.PartitionByFileMap"> <property name="mapFile">partition-hash-int.txt</property> <property name="type">0</property> <property name="defaultNode">0</property> </function>
配置说明:
columns :标识将要分片的表字段
algorithm :指定分片函数
默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点。如果不配置默认节点(defaultNode值小于0表示不配置默认节点),碰到不识别的枚举值就会报错:
can‘t find datanode for sharding column:column_name val:ffffffff
partition-hash-int.txt 配置:
10000=0 10010=1
<tableRule name="rule1"> <rule> <columns>user_id</columns> <algorithm>func1</algorithm> </rule> </tableRule> <function name="func1" class="io.mycat.route.function.PartitionByLong"> <property name="partitionCount">2,1</property> <property name="partitionLength">256,512</property> </function>
配置说明: tableRule标签:
columns :标识将要分片的表字段 algorithm :指定分片函数
function标签: partitionCount :指定分片个数列表 partitionLength : 分片范围列表,分区长度:默认为最大2^n=1024 ,即最大支持1024分区
约束 : count,length 两个数组的长度必须是一致的。1024 = sum((count[i]*length[i]))
用法例子:
@Test public void testPartition() { // 本例的分区策略:希望将数据水平分成3份,前两份各占25%,第三份占50%。(故本例非均匀分区) // |<---------------------1024------------------------>| // |<----256--->|<----256--->|<----------512---------->| // | partition0 | partition1 | partition2 | // | 共2份,故count[0]=2 | 共1份,故count[1]=1 | int[] count = new int[] { 2, 1 }; int[] length = new int[] { 256, 512 }; PartitionUtil pu = new PartitionUtil(count, length); // 下面代码演示分别以offerId字段或memberId字段根据上述分区策略拆分的分配结果 int DEFAULT_STR_HEAD_LEN = 8; // cobar默认会配置为此值 long offerId = 12345; String memberId = "qiushuo"; // 若根据offerId分配,partNo1将等于0,即按照上述分区策略,offerId为12345时将会被分配到 partition0中 int partNo1 = pu.partition(offerId); // 若根据memberId分配,partNo2将等于2,即按照上述分区策略,memberId为qiushuo时将会被分到 partition2中 int partNo2 = pu.partition(memberId, 0, DEFAULT_STR_HEAD_LEN); Assert.assertEquals(0, partNo1); Assert.assertEquals(2, partNo2); }
如果需要平均分配设置:平均分为4分片,partitionCount*partitionLength=1024
<function name="func1" class="org.opencloudb.route.function.PartitionByLong"> <property name="partitionCount">4</property> <property name="partitionLength">256</property> </function>
<tableRule name="auto-sharding-long"> <rule> <columns>user_id</columns> <algorithm>rang-long</algorithm> </rule> </tableRule> <function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong"> <property name="mapFile">autopartition-long.txt</property> </function>
配置说明:
columns :标识将要分片的表字段
algorithm :指定分片函数
autopartition-long.txt文件内容: 所有的节点配置都是从0开始,及0代表节点1,此配置非常简单,即预先制定可能的id范围对应某个分片
# range start-end ,data node index # K=1000,M=10000. 0-500M=0 500M-1000M=1 1000M-1500M=2 # 或以下写法 # 0-10000000=0 # 10000001-20000000=1
此种配置非常明确,即根据id与count(你的结点数)进行求模运算,相比方式1,此种在批量插入时需要切换数据
源,id不连续
<tableRule name="mod-long"> <rule> <columns>user_id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule> <function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!-- how many data nodes --> <property name="count">3</property> </function>
配置说明:
columns :标识将要分片的表字段
algorithm :指定分片函数
count :节点数量
<tableRule name="sharding-by-date"> <rule> <columns>create_time</columns> <algorithm>sharding-by-date</algorithm> </rule> </tableRule> <function name="sharding-by-date" class="io.mycat.route.function..PartitionByDate"> <property name="dateFormat">yyyy-MM-dd</property> <property name="sBeginDate">2014-01-01</property> <property name="sPartionDay">10</property> </function>
配置说明:
columns :标识将要分片的表字段
algorithm :指定分片函数
<tableRule name="sharding-by-pattern"> <rule> <columns>user_id</columns> <algorithm>sharding-by-pattern</algorithm> </rule> </tableRule> <function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern"> <property name="patternValue">256</property> <property name="defaultNode">2</property> <property name="mapFile">partition-pattern.txt</property> </function>
配置说明:
columns :标识将要分片的表字段
algorithm :指定分片函数
patternValue:求模基数
defaultNode :默认节点,如果不配置了默认,则默认是0即第一个结点
mapFile :配置文件路径
partition-pattern.txt文件内容:
配置文件中, 1-32 即代表 id%256 后分布的范围,如果在1-32则在分区1,其他类推,如果id非数字数据,则会
分配在defaultNode 默认节点
# id partition range start-end ,data node index ###### first host configuration 1-32=0 33-64=1 65-96=2 97-128=3 ######## second host configuration 129-160=4 161-192=5 193-224=6 225-256=7 0-0=7
<tableRule name="sharding-by-prefixpattern"> <rule> <columns>user_id</columns> <algorithm>sharding-by-prefixpattern</algorithm> </rule> </tableRule> <function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPrefixPattern"> <property name="patternValue">256</property> <property name="prefixLength">5</property> <property name="mapFile">partition-pattern.txt</property> </function>
配置说明:
columns :标识将要分片的表字段
algorithm :指定分片函数
patternValue :求模基数
prefixLength: ASCII 截取的位数
mapFile :配置文件路径
partition-pattern.txt文件内容:
配置文件中, 1-32 即代表 id%256 后分布的范围,如果在1-32则在分区1,其他类推
此种方式类似方式6,只不过采取的是将列中前prefixLength位所有ASCII码的和与patternValue 进行求模,即 sum%patternValue ,获取的值在通配范围内的,即分片数。
ASCII编码:
48-57=0-9阿拉伯数字
64、、A-Z
97-122=a-z
# range start-end ,data node index # ASCII # 48-57=0-9 # 64、、A-Z # 97-122=a-z ###### first host configuration 1-4=0 5-8=1 9-12=2 13-16=3 ###### second host configuration 17-20=4 21-24=5 25-28=6 29-32=7 0-0=7
<tableRule name="sharding-by-substring"> <rule> <columns>user_id</columns> <algorithm>sharding-by-substring</algorithm> </rule> </tableRule> <function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString"> <property name="startIndex">0</property> <!-- zero-based --> <property name="size">2</property> <property name="partitionCount">8</property> <property name="defaultPartition">0</property> </function>
配置说明:
columns :标识将要分片的表字段
algorithm :指定分片函数
startIndex :字符串截取的起始索引位置
size :截取的位数
partitionCount :分区数量
defaultPartition :默认分区
此方法为直接根据字符子串(必须是数字)计算分区号(由应用传递参数,显式指定分区号)。例如id=05-100000002
在此配置中代表根据id中从startIndex=0,开始,截取siz=2位数字即05,05就是获取的分区,如果没传默认分配 到defaultPartition
<tableRule name="sharding-by-stringhash"> <rule> <columns>user_id</columns> <algorithm>sharding-by-stringhash</algorithm> </rule> </tableRule> <function name="sharding-by-substring" class="io.mycat.route.function.PartitionByString"> <property name="length">512</property> <!-- zero-based --> <property name="count">2</property> <property name="hashSlice">0:2</property> </function>
配置说明:
columns :标识将要分片的表字段
algorithm :指定分片函数
length :代表字符串hash求模基数
count :分区数
hashSlice : hash预算位,即根据子字符串 hash运算
"2" -> (0,2)
"1:2" -> (1,2
"1:" -> (1,0)
"-1:" -> (-1,0)
":-1" -> (0,-1)
":" -> (0,0)
public class PartitionByStringTest { @Test public void test() { PartitionByString rule = new PartitionByString(); String idVal=null; rule.setPartitionLength("512"); rule.setPartitionCount("2"); rule.init(); rule.setHashSlice("0:2"); // idVal = "0"; // Assert.assertEquals(true, 0 == rule.calculate(idVal)); // // idVal = "45a"; Assert.assertEquals(true, 1 == rule.calculate(idVal)); //last 4 rule = new PartitionByString(); rule.setPartitionLength("512"); rule.setPartitionCount("2"); rule.init(); //last 4 characters rule.setHashSlice("-4:0"); idVal = "aaaabbb0000"; Assert.assertEquals(true, 0 == rule.calculate(idVal)); idVal = "aaaabbb2359"; Assert.assertEquals(true, 0 == rule.calculate(idVal)); }
<tableRule name="sharding-by-murmur"> <rule> <columns>user_id</columns> <algorithm>murmur</algorithm> </rule> </tableRule> <function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash"> <!-- 默认是0 --> <property name="seed">0</property> <!-- 要分片的数据库节点数量,必须指定,否则没法分片 --> <property name="count">2</property> <!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的 160倍 --> <property name="virtualBucketTimes">160</property> <!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指定权 重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节 点权重值为值。所有权重值必须是正整数,否则以1代替 --> <!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property> 用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 --> </function>
一致性hash预算有效解决了分布式数据的扩容问题,前1-9中id规则都多少存在数据扩容难题,而10规则解决了数 据扩容难点
需求:把商品表分片存储到三个数据节点上
创建表 配置完毕后,重新启动mycat。使用mysql客户端连接mycat,创建表。
CREATE TABLE `item` ( `id` int(11) NOT NULL, `name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
分片策略指定为“auto-sharding-long”
分片规则指定为“mod-long”
需求
需求:把订单信息分片,商家要查询出售的订单,用户要查询自己的订单。
表设计:用户、商家订单、订单明细、用户订单
分片规则:
“用户表”---user_id 取模,“商家订单”----seller_user_id 取模
“订单明细”----“商家订单”ER 分片,“买家订单”----buyer_user_id 取模
业务表
CREATE TABLE tb_user ( login_name VARCHAR (32) comment ‘登录名‘, user_id BIGINT comment ‘用户标识‘, TYPE INT comment ‘用户类型 1 商家,2买家‘, passwd VARCHAR (128) comment ‘密码‘, primary key (user_id) ); alter table tb_user comment ‘用户表‘; CREATE TABLE tb_seller_order ( seller_user_id BIGINT comment ‘商家标识‘, buyer_user_id BIGINT comment ‘用户标识‘, order_id BIGINT comment ‘订单标识‘, price BIGINT comment ‘价格‘, STATUS INT comment ‘状态‘, primary key (order_id) ); alter table tb_seller_order comment ‘商家订单表‘; CREATE TABLE tb_order_detail( seller_user_id BIGINT comment ‘商家标识‘, order_detail_id BIGINT comment ‘订单明细标识‘, order_id BIGINT comment ‘订单标识‘, goods_id BIGINT comment ‘标识‘, goods_name VARCHAR(32) comment ‘商品名称‘, cnt INT comment ‘数量‘, unit_price INT comment ‘单价‘, primary key (order_detail_id) ); alter table tb_order_detail comment ‘订单明细‘; CREATE TABLE tb_buyer_order ( buyer_user_id BIGINT comment ‘用户标识‘, seller_user_id BIGINT comment ‘商家标识‘, order_id BIGINT comment ‘订单标识‘, price BIGINT comment ‘价格‘, STATUS INT comment ‘状态‘, primary key (order_id) );
alter table tb_buyer_order comment ‘买家订单‘;
服务器分配:
mysql
主 192.168.10.135 主 192.168.10.136 主 192.168.10.137
mycat:
192.168.10.137
表主键生成策略
使用mycat全局序列生成 mycat 全局序列号:http://blog.csdn.net/convict_eva/article/details/51917499)
USER_ID_SQUE=dn_master ORDER_ID_SQUE=dn_master DETAIL_ID_SQUE=dn_master
insert into MYCAT_SEQUENCE(NAME,current_value,increment) values(‘USER_ID_SQUE‘,1,100),(‘ORDER_ID_SQUE‘,1,100),(‘DETAIL_ID_SQUE‘,1,100);
SELECT NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE; SELECT NEXT VALUE FOR MYCATSEQ_ORDER_ID_SQUE; SELECT NEXT VALUE FOR MYCATSEQ_DETAIL_ID_SQUE;
rule.xml配置
配置function 标签
找到function 标签name="mod-long" 的function 配置,修改总结点数为3(和使用的mysql 结点数一致)。
<function name="mod-long" class="org.opencloudb.route.function.PartitionByMod"> <!-- how many data nodes --> <property name="count">3</property> </function>
配置tableRule标签
注意事项:name 要全局唯一
rule.xml 配置添加如下配置
<!-- user分片规则 --> <tableRule name="tb_user_mod-long"> <rule> <!-- 分片使用的字段 --> <columns>user_id</columns> <!-- 分片使用的方法,对应function 名称 --> <algorithm>mod-long</algorithm> </rule> </tableRule> <!-- 卖家订单分片规则 --> <tableRule name="seller_order_mod-long"> <rule> <columns>seller_user_id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule> <!-- 买家订单分片规则 --> <tableRule name="buyer_order_mod-long"> <rule> <columns>buyer_user_id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule>
schema.xml 配置
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://org.opencloudb/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100"> <!-- auto sharding by id (long) --> <table name="tb_a" dataNode="dn_master" /> <!-- 全局表,使用type属性指定,多个结点要都执行建表语句。所有结点的数据一致。 --> <table name="tb_global_test" dataNode="dn_master,dn_master2,dn_master3" type="global" /> <!-- 配置表所在的分片结点,指定主键和分片规则。指定主键是为了使用主键查询时mycat什么缓存 主键对应的dn,提高查询效率。 --> <table name="tb_user" rule="tb_user_mod-long" primaryKey="user_id" dataNode="dn_master,dn_master2,dn_master3" /> <table name="tb_seller_order" rule="seller_order_mod-long" primaryKey="order_id" dataNode="dn_master,dn_master2,dn_master3"> <!-- 配置ER 分片,子表的存储依赖于主表,并且物理上紧邻存放。 --> <childTable name="tb_order_detail" primaryKey="order_detail_id" joinKey="order_id" parentKey="order_id" /> </table> <table name="tb_buyer_order" rule="buyer_order_mod-long" primaryKey="order_id" dataNode="dn_master,dn_master2,dn_master3" /> </schema> <!-- 配置数据结点 --> <dataNode name="dn_master" dataHost="master" database="test_db" /> <dataNode name="dn_master2" dataHost="master2" database="test_db" /> <dataNode name="dn_master3" dataHost="master3" database="test_db" /> <!-- ddata node host 配置 --> <dataHost name="master" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <!-- 主从心跳语句配置 --> <heartbeat>show slave status</heartbeat> <writeHost host="hostM1" url="192.168.109.128:3306" user="root" password="#"> <!-- 从库 --> <readHost host="hostS2" url="192.168.109.130:3306" user="root" password="#" /> </writeHost> </dataHost> <dataHost name="master2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM2" url="192.168.109.131:3306" user="root" password="#"></writeHost> </dataHost> <dataHost name="master3" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM3" url="192.168.109.132:3306" user="root" password="#"></writeHost> </dataHost> </mycat:schema>
重启mycat,使用SQLyog连接到mycat,并执行建表语句。
测试
插入user
INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-1‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-2‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-3‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-4‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-5‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-6‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-7‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-8‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-9‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-10‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-11‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-12‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-13‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-14‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-15‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-16‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-17‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-18‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-19‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-20‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-21‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-22‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘name-23‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,1,‘passwd-A‘);
查看插入的数据是否按照id取模分片。
下单测试:
上面创建的是商家用户, 下面创建买家用户
INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘buyer-1‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,2,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘buyer-2‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,2,‘passwd-A‘); INSERT INTO `tb_user`(`login_name`,`user_id`,`TYPE`,`passwd`) VALUES (‘buyer-3‘,NEXT VALUE FOR MYCATSEQ_USER_ID_SQUE,2,‘passwd-A‘);
所有的用户如下:
mysql> select * from tb_user order by login_name; +------------+---------+------+----------+ | login_name | user_id | TYPE | passwd | +------------+---------+------+----------+ | buyer-1 | 248 | 2 | passwd-A | | buyer-2 | 249 | 2 | passwd-A | | buyer-3 | 250 | 2 | passwd-A | | name-1 | 225 | 1 | passwd-A | | name-10 | 234 | 1 | passwd-A | | name-11 | 235 | 1 | passwd-A | | name-12 | 236 | 1 | passwd-A | | name-13 | 237 | 1 | passwd-A | | name-14 | 238 | 1 | passwd-A | | name-15 | 239 | 1 | passwd-A | | name-16 | 240 | 1 | passwd-A | | name-17 241 | 1 | passwd-A | | name-18 | 242 | 1 | passwd-A | | name-19 | 243 | 1 | passwd-A | | name-2 | 226 | 1 | passwd-A | | name-20 | 244 | 1 | passwd-A | | name-21 | 245 | 1 | passwd-A | | name-22 | 246 | 1 | passwd-A | | name-23 | 247 | 1 | passwd-A | | name-3 227 | 1 | passwd-A | | name-4 | 228 | 1 | passwd-A | | name-5 | 229 | 1 | passwd-A | | name-6 | 230 | 1 | passwd-A | | name-7 | 231 | 1 | passwd-A | | name-8 | 232 | 1 | passwd-A | | name-9 | 233 | 1 | passwd-A | +------------+---------+------+----------+
下单:
SELECT NEXT VALUE FOR MYCATSEQ_ORDER_ID_SQUE; INSERT INTO `tb_seller_order`(`seller_user_id`,`buyer_user_id`,`order_id`,`price`) VALUES (225,248,201,1222); INSERT INTO `tb_order_detail` (`seller_user_id`,`order_detail_id`,`order_id`,`goods_id`,`goods_name`,`cnt`,`unit_ price`) VALUES (225, NEXT VALUE FOR MYCATSEQ_DETAIL_ID_SQUE,201,11,‘goods_name‘,1,1220); INSERT INTO `tb_order_detail` (`seller_user_id`,`order_detail_id`,`order_id`,`goods_id`,`goods_name`,`cnt`,`unit_ price`) VALUES (225, NEXT VALUE FOR MYCATSEQ_DETAIL_ID_SQUE,201,11,‘goods_name2‘,1,2);
说明:商家225 在109.128 上,tb_seller_order表根据seller_user_id 取模分片,所有此订单数据存储在与user id 为225 的商家同一分片 tb_order_detail 表使用的是与tb_seller_order ER 分片,使用order_id 关联,所以 tb_order_detail 存储的分片与相同的order_id 的tb_seller_order 的数据在同一分片。
再测试一条数据:user_id为238 的用户存储在109.131 分片上。
INSERT INTO `tb_seller_order`(`seller_user_id`,`buyer_user_id`,`order_id`,`price`) VALUES (238,248,203,1222); INSERT INTO `tb_order_detail` (`seller_user_id`,`order_detail_id`,`order_id`,`goods_id`,`goods_name`,`cnt`,`unit_pr ice`) VALUES (238, NEXT VALUE FOR MYCATSEQ_DETAIL_ID_SQUE,203,11,‘goods_name‘,1,1220); INSERT INTO `tb_order_detail` (`seller_user_id`,`order_detail_id`,`order_id`,`goods_id`,`goods_name`,`cnt`,`unit_pr ice`) VALUES (238, NEXT VALUE FOR MYCATSEQ_DETAIL_ID_SQUE,203,11,‘goods_name2‘,1,2);
测试结果: 109.128数据为:
mysql> select * from tb_user; +------------+---------+------+----------+ | login_name | user_id | TYPE | passwd | +------------+---------+------+----------+ | name-1 | 225 | 1 | passwd-A | | name- 4 | 228 | 1 | passwd-A | | name-7 | 231 | 1 | passwd-A | | name-10 | 234 | 1 | passwd-A | | name-13 | 237 | 1 | passwd-A | | name-16 | 240 | 1 | passwd-A | | name-19 | 243 | 1 | passwd-A | | name-22 | 246 | 1 | passwd-A | | buyer-2 | 249 | 2 | passwd-A | +------------+------- --+------+----------+ 9 rows in set (0.00 sec) mysql> select * from tb_seller_order; +----------------+---------------+----------+-------+--------+ | seller_user_id | buyer_user_id | order_id | price | STATUS | +----------------+---------------+----------+-------+--------+ | 225 | 248 | 201 | 1222 | NULL | +----------------+---------------+----------+-------+--------+ 1 row in set (0.00 sec) mysql> select * from tb_order_detail; +----------------+-----------------+----------+----------+-------------+------+------------+ | seller_user_id | order_detail_id | order_id | goods_id | goods_name | cnt | unit_price | +----------------+-------- ---------+----------+----------+-------------+------+------------+ | 225 | 201 | 201 | 11 | goods_name | 1 | 1220 | | 225 | 202 | 201 | 11 | goods_name2 | 1 | 2 | +----------------+------- ----------+----------+----------+-------------+------+------------+ 2 rows in set (0.00 sec)
109.131数据为:
mysql> select * from tb_user; +------------+---------+------+----------+ | login_name | user_id | TYPE | passwd | +------------+---------+------+----------+ | name-2 | 226 | 1 | passwd-A | | name-5 | 229 | 1 | passwd-A | | name-8 | 232 | 1 | passwd-A | | name-11 | 235 | 1 | passwd-A | | name-14 | 238 | 1 | passwd-A | | name-17 | 241 | 1 | passwd-A | | name-20 | 244 | 1 | passwd-A | | name-23 | 247 | 1 | passwd-A | | buyer-3 | 250 | 2 | passwd-A | +------------+--------- +------+----------+ 9 rows in set (0.00 sec) mysql> select * from tb_seller_order; +----------------+---------------+----------+-------+--------+ | seller_user_id | buyer_user_id | order_id | price | STATUS | +----------------+---------------+----------+-------+--------+ | 238 | 248 | 203 | 1222 | NULL | +----------------+---------------+----------+-------+--------+ 1 row in set (0.00 sec) mysql> select * from tb_order_detail; +----------------+-----------------+----------+----------+-------------+------+------------+ | seller_user_id | order_detail_id | order_id | goods_id | goods_name | cnt | unit_price | +----------------+-------- ---------+----------+----------+-------------+------+------------+ | 238 | 203 | 203 | 11 | goods_name | 1 | 1220 | | 238 | 204 | 203 | 11 | goods_name2 | 1 | 2 | +----------------+------- ----------+----------+----------+-------------+------+------------+ 2 rows in set (0.00 sec)
MyCat的读写分离是建立在MySQL主从复制基础之上实现的,所以必须先搭建MySQL的主从复制。
数据库读写分离对于大型系统或者访问量很高的互联网应用来说,是必不可少的一个重要功能。对于MySQL来说, 标准的读写分离是主从模式,一个写节点Master后面跟着多个读节点,读节点的数量取决于系统的压力,通常是1- 3个读节点的配置
Mycat实现的读写分离和自动切换机制,需要mysql的主从复制机制配合。
Mycat配置
Mycat 1.4 支持MySQL主从复制状态绑定的读写分离机制,让读更加安全可靠,配置如下:
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost1" database="db2" />
<dataNode name="dn3" dataHost="localhost1" database="db3" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="2"
slaveThreshold="100">
<heartbeat>show slave status</heartbeat>
<writeHost host="hostM" url="192.168.25.134:3306" user="root"
password="root">
<readHost host="hostS" url="192.168.25.166:3306" user="root"
password="root" />
</writeHost>
</dataHost>
(1) 设置 balance="1"与writeType="0" Balance参数设置:
balance=“0”, 所有读操作都发送到当前可用的writeHost上。
balance=“1”,所有读操作都随机的发送到readHost。
WriteType参数设置:
writeType=“0”, 所有写操作都发送到可用的writeHost上。
writeType=“1”,所有写操作都随机的发送到readHost。
“readHost是从属于writeHost的,即意味着它从那个writeHost获取同步数据,因此,当它所属的writeHost宕机 了,则它也不会再参与到读写分离中来,即“不工作了”,这是因为此时,它的数据已经“不可靠”了。基于这个考 虑,目前mycat 1.3和1.4版本中,若想支持MySQL一主一从的标准配置,并且在主节点宕机的情况下,从节点还能 读取数据,则需要在Mycat里配置为两个writeHost并设置banlance=1。”
(2) 设置 switchType="2" 与slaveThreshold="100" switchType 目前有三种选择: -1:表示不自动切换 1:默认值,自动切换 2:基于MySQL主从同步的状态决定是否切换
Mycat心跳检查语句配置为 show slave status ,dataHost 上定义两个新属性: switchType="2" 与 slaveThreshold="100",此时意味着开启MySQL主从复制状态绑定的读写分离与切换机制。Mycat心跳机制通过 检测 show slave status 中的 "Seconds_Behind_Master", "Slave_IO_Running", "Slave_SQL_Running" 三个 字段来确定当前主从同步的状态以及Seconds_Behind_Master主从复制时延。