如今随着互联网的发展,数据的量级也是成指数的增长,从GB到TB到PB。对数据的各种操作也是愈加 的困难,传统的关系性数据库已经无法满足快速查询与插入数据的需求。
简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库 (主机)上面,以达到分散单台设备负载的效果
分库分表解决的问题
分库分表的目的是为了解决由于数据量过大而导致数据库性能降低的问题,将原来单体服务的数据 库进行拆分。将数据大表拆分成若干数据表组成,使得单一数据库、单一数据表的数据量变小,从 而达到提升数据库性能的目的。
什么情况下需要分库分表:
注意:
- 分库分表之前,要根据项目的实际情况 确定我们的数据量是不是够大,并发量是不是够大,来决定是 否分库分表.
- 数据量不够就不要分表。单表数据量超过1000万或100G的时候, 速度就会变慢(官方测试)
分库分表包括: 垂直分库、垂直分表、水平分库、水平分表 四种方式。
数据库中不同的表对应着不同的业务,垂直切分是指按照业务的不同将表进行分类,分布到不同的 数据库上面
将数据库部署在不同服务器上,从而达到多个服务器共同分摊压力的效果
将一个表按照字段分成多表,每个表存储其中一部分字段
对职位表进行垂直拆分, 将职位基本信息放在一张表, 将职位描述信息存放在另一张表
垂直拆分带来的一些提升:
垂直拆分没有彻底解决单表数据量过大的问题
分库分表的目的:将我们的单库的数据控制在合理范围内,从而提高数据库的性能.
垂直拆分( 按照结构分 ):
水平拆分( 按照数据行分 ):
什么时候用分库分表:
当数据库进行分库分表后,数据由一个数据库分散到多个数据库中。此时系统要查询时需要切换不同的数据库进行查询,那么系统如何知道要查询的数据在哪个数据库中?当添加一条记录时要向哪个数据库 中插入呢?这些问题处理起来都是非常的麻烦。
这种情况下可以使用一个数据库中间件mycat来解决相关的问题。
关系型数据库在单机单库的情况下,比较容易出现性能瓶颈问题,分库分表可以有效的解决这方面的问题,但是同时也会产生一些比较棘手的问题
当我们需要更新的内容同时分布在不同的库时, 不可避免的会产生跨库的事务问题.
原来在一个数据库操作, 本地事务就可以进行控制, 分库之后 一个请求可能要访问多个数据库,如何保证事务的一致性,目前还没有简单的解决方案.
在分库之后, 原来在一个库中的一些表,被分散到多个库,并且这些数据库可能还不在一台服务器,无法关联查询.
解决这种关联查询,需要我们在代码层面进行控制,将关联查询拆开执行,然后再将获取到的结果进行拼装.
分库并行查询时,如果用到了分页,每个库返回的结果集本身是无序的,
只有将多个库中的数据先查出来,然后再根据排序字段在内存中进行排序,如果查询结果过大也是十分消耗资源的
在分库分表的环境中,表中的数据存储在不同的数据库, 主键自增无法保证ID不重复,
需要单独设计全局主键
不同的数据库,都需要从公共表中获取数据.
可以在每一个库都创建这个公共表, 所有对公共表的更新操作,都同时发送到所有分库执行.
ShardingJDBC可以帮助我们解决这个问题
MyCat 是目前最流行的基于 java 语言编写的数据库中间件
是一个实现了 MySQL 协议的服务器,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生协议与多个 MySQL 服务器通信,也可以用 JDBC 协议与大多数主流数据库服务器通信
其核心功能是分库分表和读写分离
即将一个大表水平分割为 N 个小表,存储在后端 MySQL 服务器里或者其他 数据库里。
MyCat对于我们Java程序员来说,就是一个近似等于 MySQL 的数据库服务器,你可以用连接 MySQL 的 方式去连接 Mycat
除了端 口不同,默认的 Mycat 端口是 8066 而非 MySQL 的 3306,因此需要在连 接字符串上增加端口信息
我们可以像使用MySQL一样使用MyCat,Mycat 可以管理若干 MySQL 数据库,同时实现数据的存储和操作
分片:通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机) 上面,以达到分散单台设备负载的效果。
MyCat支持两种切分模式:
对数据进行分片处理之后,从原有的一个库,被切分为多个分片数据库,所有的分片数据库集群构 成了整个完整的数据库存储。
Mycat在操作时,使用逻辑库来代表这个完整的数据库集群,便于对 整个集群操作。
既然有逻辑库,那么就会有逻辑表,分布式数据库中,对应用来说,读写数据的表就是逻辑表。
逻辑表,可以是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一 个表构成。
数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点 (dataNode)。
数据切分后,每个分片节点不一定都会独占一台机器,同一机器上面可以有多个分片数据库, 这 样一个或多个分片节点所在的机器就是节点主机.
为了规避单节点主机并发数限制, 尽量将读写 压力高的分片节点均衡的放在不同的节点主机dataHost。
前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则rule,这样按照某种业务规则把数据分到 某个分片的规则就是分片规则.
数据切分选择合适的分片规则非常重要,将极大的 避免后续数据处理的难度。
- jdk: 要求jdk必须是1.7 及以上版本
- MySQL: 推荐mysql5.5 版本以上
- MyCat官网:http://www.mycat.org.cn/
第一步: 搭建3台虚拟机:server01、server02、server03
第二步: server01与server02 安装MySQL数据库服务器,保证版本一致
第三步: 创建数据库
注意: 提前安装好JDK
下载MyCat(使用资料的压缩包),并上传MyCat 到 server03 服务器 ,并解压
使用命令如下:
./mycat start # 启动
./mycat stop # 停止
./mycat restart # 重启
./mycat status # 查看
./mycat console # 带控制台启动
Schema.xml作为MyCat中重要的配置文件之一,管理着MyCat的逻辑库、表、分片规则、DataNode以 及DataSource。
以下一层层对该文件进行解析
<schema name="lagou" checkSQLschema="true" sqlMaxLimit="100" >
schema>
属性名 | 值 | 数量限制 | 说明 |
---|---|---|---|
dataNode | 任意String | (0…1) | 分片节点 |
sqlMaxLimit | Integer | (1) | 查询返回的记录数限制limit |
checkSQLschema | Boolean | (1) | 执行SQL时,是否去掉表所属的库名 |
table标签定义了 Mycat 中的逻辑表,所有需要拆分的表都需要在这个标签中定义
<schema name="lagou" checkSQLschema="true" sqlMaxLimit="100" >
<table name="pay_order" dataNode="dn1,dn2,dn3" rule="auto-sharding-long"
primaryKey="id" autoIncrement="true" >table>
schema>
属性 | 值 | 数量限制 | 说明 |
---|---|---|---|
name | String | (1) | 逻辑表名 |
dataNode | String | (1…*) | 分片节点 |
rule | String | (0…1) | 分片规则 |
ruleRequired | Boolean | (0…1) | 是否强制绑定分片规则 |
primaryKey | String | (1) | 主键 |
type | String | (0…1) | 逻辑表类型,全局表、普通表 |
autoIncrement | Boolean | (0…1) | 自增长主键 |
subTables | String | (1) | 分表 |
needAddLimit | Boolean | (0…1) | 是否为查询SQL自动加limit限制 |
dataNode标签定义了 MyCat 中的分片节点,也就是我们通常说所的数据分片。
<dataNode name="dn1" dataHost="localhost1" database="lagou1" />
<dataNode name="dn2" dataHost="localhost2" database="lagou2" />
<dataNode name="dn3" dataHost="localhost2" database="lagou3" />
dataHost标签在 Mycat 逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写分 离配置和心跳语句
<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.52.10:3306" user="root" password="QiDian@666">
writeHost>
dataHost>
<dataHost name="localhost2" 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.52.11:3306" user="root" password="QiDian@666">
writeHost>
dataHost>
属性 | 值 | 数量限制 | 说明 |
---|---|---|---|
name | String | (1) | 节点主机名 |
maxCon | Integer | (1) | 最大连接数 |
minCon | Integer | (1) | 最小连接数 |
balance | Integer | (1) | 读操作负载均衡类型 |
writeType | Integer | (1) | 写操作负载均衡类型 |
dbType | String | (1) | 数据库类型 |
dbDriver | String | (1) | 数据库驱动 |
switchType | String | (1) | 主从切换类型 |
注意: 解决MyCat乱码问题
heartbeat标签内指明用于和后端数据库进行心跳检查的语句。
例如:MySQL 可以使用 select user()、 Oracle 可以 使用 select 1 from dual 等
属性 | 值 | 数量限制 | 说明 |
---|---|---|---|
host | String | (1) | 主机名 |
url | String | (1) | 连接字符串 |
password | String | (1) | 密码 |
user | String | (1) | 用户名 |
weight | String | (1) | 权重 |
usingDecrypt | String | (1) | 是否对密码加密,默认0 |
server.xml几乎保存了所有 mycat 需要的系统配置信息。
这个标签主要用于定义登录 mycat 的用户和权限。
<user name="root" defaultAccount="true">
<property name="password">123456property>
<property name="schemas">lagouproperty>
<property name="defaultSchema">lagouproperty>
user>
./mycat start # 重启myCat
./mycat status # 查看状态
# 连接mycat
mysql -uroot -p123456 -h127.0.0.1 -P8066
rule.xml里面就定义了我们对表进行拆分所涉及到的规则定义。
我们可以灵活的对表使用不同的分片算 法,或者对表使用相同的算法但具体的参数不同。
这个文件里面主要有tableRule和function这两个标签。在具体使用过程中可以按照需求添加 tableRule和function。
此配置文件可以不用修改,使用默认即可。
<mycat:rule xmlns:mycat="http://io.mycat/">
<tableRule name="sharding-by-intfile">
<rule>
<columns>sharding_idcolumns>
<algorithm>hash-intalgorithm>
rule>
tableRule>
mycat:rule>
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txtproperty>
function>
Mycat常用分片配置示例:
根据指定的列的范围进行分片.默认从0节点开始
<tableRule name="auto-sharding-long">
<rule>
<columns>idcolumns>
<algorithm>rang-longalgorithm>
rule>
tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txtproperty>
function>
autopartition-long.txt文件:
0-200000范围分配给节点0
200000-400000范围分配给节点1
0-200000=0
200000-400000=1
把数据分类存储, 这种方法适用于取值固定的场合,例如性别和省份
<tableRule name="sharding-by-intfile">
<rule>
<columns>sharding_idcolumns>
<algorithm>hash-intalgorithm>
rule>
tableRule>
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txtproperty>
function>
partition-hash-int.txt文件内容如下:
beijing=0
wuhan=1
shanghai=2
根据配置中的count值进行分片,将数据分成配置的count份,然后将数据均匀的分布在各个节点上
<tableRule name="mod-long">
<rule>
<columns>idcolumns>
<algorithm>mod-longalgorithm>
rule>
tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">3property>
function>
水平分库: 把一张表的数据按照一定规则,分配到不同的数据库,每一个库只有这张表的部分数据.
在rule.xml配置, 自动分片
每个datanode中保存一定数量的数据。根据id进行分片
<schema name="lagou" checkSQLschema="true" sqlMaxLimit="100" >
<table name="pay_order" dataNode="dn1,dn2,dn3" rule="auto-sharding-long"
primaryKey="id" autoIncrement="true" >
table>
schema>
<tableRule name="auto-sharding-long">
<rule>
<columns>idcolumns>
<algorithm>rang-longalgorithm>
rule>
tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txtproperty>
function>
autopartition-long.txt:
# range start-end ,data node index
# K=1000,M=10000.
0-1k=0
1k-2k=1
2k-3k=2
对分片规则进行测试id 范围为:
重启MyCat
./mycat stop # 停止命令
./mycat restart # 重启命令
在MyCat中创建逻辑表
DROP TABLE IF EXISTS pay_order;
CREATE TABLE pay_order (
id BIGINT(20) PRIMARY KEY,
user_id INT(11) NOT NULL ,
product_name VARCHAR(128) ,
amount DECIMAL(12,2)
);
MyCat中创建好表之后,我们的MySQL节点中也会对应的创建表
插入数据,观察数据被插入到哪张表中.
INSERT INTO pay_order(id,user_id,product_name,amount) VALUES(2001,1,"面试宝典",15.8);
如果未进行设置大小写,那么数据库创建表名会为大写
INSERT INTO PAY_ORDER(id,user_id,product_name,amount) VALUES(2001,1,"面试宝典",15.8);
在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。为此,Mycat 提供了全局 sequence,并且提供了包含本地配置和数据库配置等多种实现方式。
server.xml文件中
<system>
<property name="sequnceHandlerType">0property>
system>
此方式 Mycat 将 sequence 配置到文件中,当使用到 sequence 中的配置后,Mycat 会更新 classpath 中的 sequence_conf.properties 文件中 sequence 当前的值。
PAY_ORDER.HISIDS=
PAY_ORDER.MINID=101
PAY_ORDER.MAXID=10000000
PAY_ORDER.CURID=100
重启MyCat, 插入一条数据,不用指定id.
INSERT INTO pay_order(user_id,product_name,amount) VALUES(1,"xiao",12.8);
在实际的生产环境中, 数据的读写操作如果都在同一个数据库服务器中进行, 当遇到大量的并发读或者写 操作的时候,是没有办法满足实际需求的,数据库的吞吐量将面临巨大的瓶颈压力
MyCat的读写分离是建立在MySQL主从复制基础之上实现的,所以必须先搭建MySQL的主从复制架构。
主从复制的用途:
主从部署必要条件:
主从复制的原理:
Mysql的主从复制至少是需要两个Mysql的服务,当然Mysql的服务是可以分布在不同的服务器上,也可以在一台服务器上启动多个服务。
-- 创建数据库
CREATE DATABASE test CHARACTER SET utf8;
-- 创建表
CREATE TABLE users (
id INT(11) PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) DEFAULT NULL,
age INT(11) DEFAULT NULL
);
-- 插入数据
INSERT INTO users VALUES(NULL,'user1',20);
INSERT INTO users VALUES(NULL,'user2',21);
INSERT INTO users VALUES(NULL,'user3',22);
vim /etc/my.cnf
# 插入下面的内容:
lower_case_table_names=1
log-bin=mysql-bin
server-id=1
binlog-do-db=test
binlog_ignore_db=mysql
service mysqld restart
-- 创建账号
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'192.168.52.11' IDENTIFIED BY 'Qwer@1234';
-- 执行以下命令锁定数据库以防止写入数据。
FLUSH TABLES WITH READ LOCK;
使用SQLYog导出,主数据库备份完毕,恢复写操作
unlock tables;
导入后, 主库和从库数据会追加相平,保持同步!
此过程中,若主库存在业务,并发较高,在同步的时候要先锁表,让其不要有修改!等待主从数据追平,主从同步后在打开锁!
server-id=2
-- 重启
service mysqld restart
STOP SLAVE;
CHANGE MASTER TO MASTER_HOST='192.168.52.10',
MASTER_USER='slave',
MASTER_PASSWORD='Qwer@1234',
MASTER_PORT=3306,
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0,
MASTER_CONNECT_RETRY=10;
# 编辑auto.cnf
vim /var/lib/mysql/auto.cnf
# 修改UUID的值
server-uuid=a402ac7f-c392-11ea-ad18-000c2980a208
# 重启
service mysqld restart
start slave;
-- 查看状态
SHOW SLAVE STATUS;
-- 命令行下查看状态 执行
SHOW SLAVE STATUS \G;
-- 在主库插入一条数据,观察从库是否同步
INSERT INTO users VALUES(NULL,'user4',23);
数据库读写分离对于大型系统或者访问量很高的互联网应用来说,是必不可少的一个重要功能。
对于 MySQL来说,标准的读写分离是主从模式,一个写节点Master后面跟着多个读节点,读节点的数量取决于系统的压力,通常是1-3个读节点的配置
在schema.xml文件中配置Mycat读写分离。
使用前需要搭建MySQL主从架构,并实现主从复制, Mycat不负责数据同步问题。
修改用户可以访问的逻辑表为 test
<user name="root" defaultAccount="true">
<property name="password">123456property>
<property name="schemas">testproperty>
<property name="defaultSchema">testproperty>
user>
- 逻辑库 name=“test”
- 逻辑表 name=“users”
- 读写分离 不设置分片规则 ruleRequired=false
- 分片节点 dataNode=“dn4”
<schema name="test" checkSQLschema="true" sqlMaxLimit="100">
<table name="users" dataNode="dn4" ruleRequired="false" primaryKey="id" autoIncrement="true" >
table>
schema>
<dataNode name="dn4" dataHost="localhost3" database="test" />
<dataHost name="localhost3" maxCon="1000" minCon="10" balance="1" writeType="0"
dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()heartbeat>
<writeHost host="M1" url="192.168.52.10:3306" user="root" password="QiDian@666">
<readHost host="S1" url="192.168.52.11:3306" user="root" password="QiDian@666"
weight="1" />
writeHost>
dataHost>
balance参数:
writeType参数:
重启MyCat:
./mycat restart
执行查询和插入操作:
INSERT INTO users(NAME,age) VALUES('测试abc',26);
SELECT * FROM users;