数据切分
简单的说,就是通过某种条件,将我们之前存储在一台数据库上的数据,分散到多台数 据库中,从而达到降低单台数据库负载的效果。数据切分,根据其切分的规则,大致分为两种类 型,垂直切分和水平切分。
垂直切分
就是按照不同的表或者Schema切分到不同的数据库中。
优点:
- 拆分后业务清晰,拆分规则明确;
- 系统之间容易扩展和整合;
- 数据维护简单
缺点:
- 部分业务表无法join,只能通过接口调用,提升了系统的复杂度;
- 跨库事务难以处理;
- 垂直切分后,某些业务数据过于庞大,仍然存在单体性能瓶颈
水平切分
将一个表中的数据,根据某种规则拆分到不同的数据库中
优点:
- 解决了单库大数据、高并发的性能瓶颈;
- 拆分规则封装好,对应用端几乎透明,开发人员无需关心拆分细节;
- 提高了系统的稳定性和负载能力;
缺点:
- 拆分规则很难抽象;
- 分片事务一致性难以解决;
- 二次扩展时,数据迁移、维护难度大。
总结
共同缺点有:
- 分布式的事务问题;
- 跨库join问题;
- 多数据源的管理问题
针对多数据源的管理问题,主要有两种思路:
1. 客户端模式,在每个应用模块内,配置自己需要的数据源,直接访问数据库,在 各模块内完成数据的整合;客户端模式:sharding-jdbc
2. 中间代理模式,中间代理统一管理所有的数据源,数据库层对开发人员完全透 明,开发人员无需关注拆分的细节。中间代理模式:MyCat
数据库读写分离
读写分离的弊端
当同步挂掉,或者同步延迟比较大时,写库和读库的数据 不一致,这个数据的不一致,用户能不能接受。
MyCat概述
https://github.com/MyCATApache/Mycat-Server/wiki
- 它是一个开源的分布式数据库系统,前端的用户可以把它看成 一个数据库代理。
- MyCat的核心功能是分库分表,即将一个大表水平切分成N个小表,然 后存放在后端的MySql数据当中
- 后端支持MySql,Oracle,SqlS erver,DB2等主流的数据库,也支持MongoDB这种NoSql数据库
- 不仅仅可以用作读写分离、分库分表,还可以用于容灾备份, 云平台建设等,让你的架构具备很强的适应性和灵活性
MyCat的应用场景
- 单纯的读写分离,此时配置最为简单,支持读写分离,主从切换;
- 分库分表,对于超过1000w的表进行分片,最大支持1000亿的数据;
- 多租户应用,每个应用一个数据库,应用只连接MyCat,程序本身不需要改造;
- 代替HBase,分析大数据
MyCat中的基本概念
逻辑库(Schema)
在实际的开发中,开发人员不需要知道数据库中间件的存在,开发人员只需要有数据库的概念就可以了。
所以数据库中间件可以被看做是一个或者多个数据库集群构成的逻辑库
逻辑表(table)
对于应用系统来说,读写数据的表,就是逻辑表。而逻辑表中的数据,则是被水平切分后,分布在不同的分片库中
分片节点(dataNode)
数据被切分后,一张大表被分到不同的分片数据库上面,每个分片表所在的数据库就叫做分片节点
节点主机(dataHost)
数据切分后,每一个分片节点不一定都会占用一个真正的物理主机,会存在多个分片节点在同一个 物理主机上的情况,这些分片节点所在的主机就叫做节点主机
分片规则(rule)
按照某种业务逻辑,将数据分到一个确定的分 片当中,这个规则就叫做分片规则
全局序列号(sequence)
制保证数据的唯一标识,这种保证数据唯一标 识的机制,我们叫做全局序列号。
mycat的配置
server.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License. - You
may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0
- - Unless required by applicable law or agreed to in writing, software -
distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the
License for the specific language governing permissions and - limitations
under the License. -->
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<property name="nonePasswordLogin">0</property> <!-- 0为需要密码登陆、1为不需要密码登陆 ,默认为0,设置为1则需要指定默认账户-->
<property name="useHandshakeV10">1</property>
<property name="useSqlStat">0</property> <!-- 1为开启实时统计、0为关闭 -->
<property name="useGlobleTableCheck">0</property> <!-- 1为开启全加班一致性检测、0为关闭 -->
<property name="sqlExecuteTimeout">300</property> <!-- SQL 执行超时 单位:秒-->
<property name="sequnceHandlerType">2</property>
<!--<property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property>-->
<!--必须带有MYCATSEQ_或者 mycatseq_进入序列匹配流程 注意MYCATSEQ_有空格的情况-->
<property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property>
<property name="subqueryRelationshipCheck">false</property> <!-- 子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false -->
<!-- <property name="useCompression">1</property>--> <!--1为开启mysql压缩协议-->
<!-- <property name="fakeMySQLVersion">5.6.20</property>--> <!--设置模拟的MySQL版本号-->
<!-- <property name="processorBufferChunk">40960</property> -->
<!--
<property name="processors">1</property>
<property name="processorExecutor">32</property>
-->
<!--默认为type 0: DirectByteBufferPool | type 1 ByteBufferArena | type 2 NettyBufferPool -->
<property name="processorBufferPoolType">0</property>
<!--默认是65535 64K 用于sql解析时最大文本长度 -->
<!--<property name="maxStringLiteralLength">65535</property>-->
<!--<property name="sequnceHandlerType">0</property>-->
<!--<property name="backSocketNoDelay">1</property>-->
<!--<property name="frontSocketNoDelay">1</property>-->
<!--<property name="processorExecutor">16</property>-->
<!--
<property name="serverPort">8066</property> <property name="managerPort">9066</property>
<property name="idleTimeout">300000</property> <property name="bindIp">0.0.0.0</property>
<property name="dataNodeIdleCheckPeriod">300000</property> 5 * 60 * 1000L; //连接空闲检查
<property name="frontWriteQueueSize">4096</property> <property name="processors">32</property> -->
<!--分布式事务开关,0为不过滤分布式事务,1为过滤分布式事务(如果分布式事务内只涉及全局表,则不过滤),2为不过滤分布式事务,但是记录分布式事务日志-->
<property name="handleDistributedTransactions">0</property>
<!--
off heap for merge/order/group/limit 1开启 0关闭
-->
<property name="useOffHeapForMerge">0</property>
<!--
单位为m
-->
<property name="memoryPageSize">64k</property>
<!--
单位为k
-->
<property name="spillsFileBufferSize">1k</property>
<property name="useStreamOutput">0</property>
<!--
单位为m
-->
<property name="systemReserveMemorySize">384m</property>
<!--是否采用zookeeper协调切换 -->
<property name="useZKSwitch">false</property>
<!-- XA Recovery Log日志路径 -->
<!--<property name="XARecoveryLogBaseDir">./</property>-->
<!-- XA Recovery Log日志名称 -->
<!--<property name="XARecoveryLogBaseName">tmlog</property>-->
<!--如果为 true的话 严格遵守隔离级别,不会在仅仅只有select语句的时候在事务中切换连接-->
<property name="strictTxIsolation">false</property>
<property name="useZKSwitch">true</property>
</system>
<!-- 全局SQL防火墙设置 -->
<!--白名单可以使用通配符%或着*-->
<!--例如<host host="127.0.0.*" user="root"/>-->
<!--例如<host host="127.0.*" user="root"/>-->
<!--例如<host host="127.*" user="root"/>-->
<!--例如<host host="1*7.*" user="root"/>-->
<!--这些配置情况下对于127.0.0.1都能以root账户登录-->
<!--
<firewall>
<whitehost>
<host host="1*7.0.0.*" user="root"/>
</whitehost>
<blacklist check="false">
</blacklist>
</firewall>
-->
<user name="root" defaultAccount="true">
<property name="password">root</property>
<property name="schemas">foodie-shop-dev</property>
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<schema name="TESTDB" dml="0110" >
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges>
-->
</user>
<user name="user">
<property name="password">user</property>
<property name="schemas">foodie-shop-dev</property>
<property name="readOnly">true</property>
</user>
</mycat:server>
schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="foodie-shop-dev" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn131">
<!-- auto sharding by id (long)
<table name="user" dataNode="dn131,dn132" rule="mod-long" />
<table name="province" dataNode="dn131,dn132" type="global"/>
<table name="o_order" autoIncrement="true" primaryKey="id" dataNode="dn131,dn132" rule="mod-long">
<childTable name="order_item" joinKey="order_id" parentKey="id"/>
</table>
-->
<!-- <table name="oc_call" primaryKey="ID" dataNode="dn1$0-743" rule="latest-month-calldate"
/> -->
<table name="orders" dataNode="dn131,dn132" rule="sharding-by-murmur">
<childTable name="order_items" joinKey="order_id" parentKey="id"/>
<childTable name="order_status" joinKey="order_id" parentKey="id" />
</table>
</schema>
<dataNode name="dn131" dataHost="db131" database="foodie-shop-dev" />
<dataNode name="dn132" dataHost="db132" database="foodie-shop-dev" />
<dataHost name="db131" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="M1" url="192.168.73.131:3306" user="imooc"
password="Imooc@123456">
</writeHost>
</dataHost>
<dataHost name="db132" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="M1" url="192.168.73.132:3306" user="imooc"
password="Imooc@123456">
</writeHost>
</dataHost>
</mycat:schema>
Mycat 全局 ID
Mycat 全局序列实现方式主要有 4 种:本地文件方式、数据库方式、本地时间戳 算法、ZK。也可以自定义业务序列。 注意获取全局 ID 的前缀都是:MYCATSEQ_
配置文件 server.xml sequnceHandlerType 值: 0 文件 1 数据库 2 本地时间戳 3 ZK
文件方式
<property name="sequnceHandlerType">0</property>
配置 conf/sequence_conf.properties
CUSTOMER.HISIDS= CUSTOMER.MINID=10000001 CUSTOMER.MAXID=20000000 CUSTOMER.CURID=10000001
语法:select next value for MYCATSEQ_CUSTOMER
INSERT INTO `customer` (`id`, `name`) VALUES (next value for MYCATSEQ_CUSTOMER, 'yan');
数据库方式
<property name="sequnceHandlerType">1</property>
配置: sequence_db_conf.properties
#sequence stored in datanode GLOBAL=dn1 CUSTOMER=dn1
在第一个数据库节点上创建 MYCAT_SEQUENCE 表
注:可以在 schema.xml 配置文件中配置这张表,供外部访问。
<table name="mycat_sequence" dataNode="dn1" autoIncrement="true" primaryKey="id"></table>
创建存储过程——获取当前 sequence 的值
本地时间戳方式
ID= 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加) ,长度为 18 位
<property name="sequnceHandlerType">2</property>
配置文件 sequence_time_conf.propertie
#sequence depend on TIME WORKID=01 DATAACENTERID=01
select next value for MYCATSEQ_GLOBAL
ZK 方式
修改 conf/myid.properties 设置 loadZk=true(启动时会从 ZK 加载配置,一定要注意备份配置文件,并且先 用 bin/init_zk_data.sh,把配置文件写入到 ZK)
<property name="sequnceHandlerType">3</property>
配置文件:sequence_distributed_conf.properties
# 代表使用 zk INSTANCEID=ZK # 与 myid.properties 中的 CLUSTERID 设置的值相同 CLUSTERID=010
Mycat 监控与日志查看
命令行监控
连接到管理端口 9066,注意必须要带 IP
mysql -uroot -h127.0.0.1 -p123456 -P9066 mysql>show @@help;
mycatweb 监控
https://github.com/MyCATApache/Mycat-download/tree/master/mycat-web-1.0
日志
log4j 的 level 配置要改成 debug
wrapper.log 日志
- mycat 启动,停止,添加为服务等都会记录到此日志文件,如果系 统环境配置错误或缺少配置时,导致 Mycat 无法启动,可以通过查看 wrapper.log 定位 具体错误原因。
mycat.log 日志
- mycat 主要日志文件,记录了启动时分配的相关 buffer 信息,数据源 连接信息,连接池,动态类加载信息等等
在 conf/log4j2.xml 文件中进行相关配置,如保留个数,大小,字符集,日志文件大 小等
MySQL主从配置
主节点配置
log-bin,指定文件的名称
配置server-id,默认为1
主创建备份账号REPLACTION SLAVE
grant replaction slave on *.* to 'your username'@'%';
主进行锁表FLUSH TABLES WITH READ LOCK
主找到bin-log的位置
show master status;
主备份数据
mysqldump --alldatabases --master-data >dbdump.db
从节点配置
配置server-id,默认为1
log-bin=mysql-bin
relay-log=mysql-relay-bin
read-only=1
log-slave-updates=1
change master to
master_host='123.123.123.123',
master_user='repl',
mastet_password='123',
master_logfile='file.000001',
master_log_pos=1460;
start slave;
主从复制原理
1、slave 服务器执行 start slave,开启主从复制开关, slave 服务器的 IO 线程请 求从 master 服务器读取 binlog(如果该线程追赶上了主库,会进入睡眠状态)。
2、master 服务器创建 Log Dump 线程,把 binlog 发送给 slave 服务器。slave 服 务器把读取到的 binlog 日志内容写入中继日志 relay log(会记录位置信息,以便下次继 续读取)。
3、slave 服务器的 SQL 线程会实时检测 relay log 中新增的日志内容,把 relay log 解析成 SQL 语句,并执行。
Mycat 注解
当关联的数据不在同一个节点的时候,Mycat 是无法实现跨库 join 的。
注解的用法
/*!mycat: sql=注解 SQL 语句*/ 真正执行的 SQL
原始 SQL 注解 SQL select 如果需要确定分片,则使用能确定分片的注解,比如/*!mycat: sql=select * from users where user_id=1*/ 如果要在所有分片上执行则可以不加能确定分片的条件 insert 使用 insert 的表作为注解 SQL,必须能确定到某个分片 原始 SQL 插入的字段必须包括分片字段 非分片表(只在某个节点上):必须能确定到某个分片 delete 使用 delete 的表作为注解 SQL update 使用 update 的表作为注解 SQL
分片策略详解
总体上分为连续分片和离散分片,还有一种是连续分片和离散分片的结合,例如先 范围后取模
连续分片优点: 1)范围条件查询消耗资源少(不需要汇总数据) 2)扩容无需迁移数据(分片固定)
连续分片缺点: 1)存在数据热点的可能性 2)并发访问能力受限于单一或少量 DataNode(访问集中)
离散分片优点: 1)并发访问能力增强(负载到不同的节点) 2)范围条件查询性能提升(并行计算)
离散分片缺点: 1)数据扩容比较困难,涉及到数据迁移问题 2)数据库连接消耗比较多
Mycat 离线扩缩容
当我们规划了数据分片,而数据已经超过了单个节点的存储上线,或者需要下线节 点的时候,就需要对数据重新分片
1、复制 schema.xml、rule.xml 并重命名为 newSchema.xml、newRule.xml 放 于 conf 目录下。
2、修改 newSchema.xml 和 newRule.xml 配置文件为扩容缩容后的 mycat 配置参数(表的节点数、数据源、路由规则)。
注意: 只有节点变化的表才会进行迁移。仅分片配置变化不会迁移
3、修改 conf 目录下的 migrateTables.properties 配置文件,告诉工具哪些表 需要进行扩容或缩容,没有出现在此配置文件的 schema 表不会进行数据迁移,
注意 1)不迁移的表,不要修改 dn 个数,否则会报错。 2)ER 表,因为只有主表有分片规则,子表不会迁移。
4、dataMigrate.sh 中这个必须要配置 通 过 命 令 "find / -name mysqldump" 查 找 mysqldump 路 径 为 "/usr/bin/mysqldump",指定#mysql bin 路径为"/usr/bin/"
5、停止 mycat 服务
6、执行执行 bin/ dataMigrate.sh 脚本 注意:必须要配置 Java 环境变量,不能用 openjdk
7 、 脚 本 执 行 完 成 , 如 果 最 后 的 数 据 迁 移 验 证 通 过 , 就 可 以 将 之 前 的 newSchema.xml 和 newRule.xml 替换之前的 schema.xml 和 rule.xml 文 件,并重启 mycat 即可