数据切分-解决数据库性能瓶颈
海量数据的存储与访问瓶颈解决方案-数据切分
存储在一台数据库上的数据,分散到多台数据库中,从而达到降低单台数据库负载的效果
垂直切分
按照业务去切分
每种业务一个数据库
不同业务之间,禁止跨库join联查
按照不同的表或Schema切分到不同的数据库中
优点
- 拆分后业务清晰,拆分规则明确
- 系统之间容易扩展和整合
- 数据维护简单
缺点
- 部分业务表无法join,只能通过接口调用,提升了系统的复杂度
- 跨库事务难以处理
- 垂直切分后,某些业务数据过于庞大,仍然存在单体性能瓶颈
水平切分
将一个表中的数据,根据某种规则拆分到不同的数据库中
水平拆分的典型的几种分片规则
- 用户id求模
- 按照日期去拆分数据
- 按照其他字段求模,去拆分数据
优点
- 解决了单库大数据、高并发的性能瓶颈
- 拆分规则封装好,对应用端几乎透明,开发人员无需关心拆分细节
- 提高了系统的稳定性和负载能力
缺点
- 拆分规则很难抽象
- 分片事务一致性难以解决
- 二次扩展时,数据迁移、维护难度大。比如:开始我们按照用户id对2求模,但是随着业务的增长,2台数据库难以支撑,还是继续拆分成4个数据库,那么这时就需要做数据迁移了
世界上的万物没有完美的,有利就有弊,就像数据切分一样。无论是垂直切分,还是水平切分,它们解决了海量数据的存储和访问性能问题,但也随之而来的带来了很多新问题,它们的共同缺点有:
- 分布式的事务问题
- 跨库join问题
- 多数据源的管理问题
读写分离
数据库承载压力大,主要是由读请求造成的,那么我们是不是可以把读操作和写操作分开,让所有读的请求落到专门负责读的数据库上,所有写的操作落到专门负责写的数据库上,写库的数据同步到读库上,这样保证所有的数据修改都可以在读取时,从读库获得,系统的架构如图所示:
读写分离弊端
- 同步挂掉,或者同步延迟比较大时,写库和读库的数据不一致,这个数据的不一致
如何正确使用读写分离
- 一些对数据实时性要求不高的业务场景,可以考虑使用读写分离
- 对数据实时性要求比较高的场景,比如订单支付状态,还是不建议采用读写分离的,或者你在写程序时,老老实实的从写库去读取数据
MyCat-中间层代理
什么是MyCat
- MyCat是一个数据库的中间件,介于应用与数据库之间,是进行数据处理和交互的中间服务。
MyCat应用场景
- 单纯的读写分离,此时配置最为简单,支持读写分离,主从切换;
- 分库分表,对于超过1000w的表进行分片,最大支持1000亿的数据;
- 多租户应用,每个应用一个数据库,应用只连接MyCat,程序本身不需要改造;
- 代替HBase,分析大数据
MyCat中基本概念
逻辑库(Schema)
- 可以理解为系统先做了垂直切分,被分为了3个库,用户库,订单库,商品库,而这3个库就被称为逻辑库
- 逻辑表(table)
* 分片节点(dataNode)
- 数据被切分后,一张大表被分到不同的分片数据库上面,每个分片表所在的数据库就叫做分片节点
- 节点主机(dataHost)
- 分片规则(rule)
全局序列号(sequence)
MyCat中分库分表、读写分离的配置
server.xml配置
配置MyCat的用户名、密码、权限、Schema等
如同给Mysql新建用户一样
客户端连接MyCat与连接MySql无异
schema.xml配置
配置dataHost(节点主机),包括读host、写host
- 一个dataHost可以配置多个writeHost
- 一个writeHost可以配置多个readHost
balance:读请求负载均衡类型
- 0:不开启读写分离
- 1:第一个writeHost不参与
- 2:读写均匀分配
- 3:读落在readHost上
writeType写请求类型
- 0:落在第一个writeHost写库上,官方推荐
- 1:随机
配置dataNode(数据节点),指定到具体的数据库
配置schema,表名、数据节点、分片规则等
- name要和server.xml里面name对应
- checkSQLschema:是否去掉sql中的Schema
- sqlMaxLimit:select默认的limit值,仅对分片表有效
- table:定义表
- name:定义逻辑表的声明
- dataNode:定义逻辑表的数据节点
- rule:定义分片表的分片规则,必须与rule.xml中的tableRule对应
- ruleRequired:是否绑定分片规则,如果为true,没有绑定分片规则,程序报错
MyCat分片规则
枚举分片
取模
MyCat-全局表
- type:global为全局表(所有节点主机都保存数据),不指定则为分片表
<table name="province" dataNode="dn131,dn132" type="global"/>
- type:global为全局表(所有节点主机都保存数据),不指定则为分片表
MyCat-子表
childTable标签,定义分片子表
- name属性:子表名称
- joinKey属性:标志子表中的列,用于与父表做关联
- parentKey属性,标志父表中的列,与joinKey对应
- primaryKey属性:子表主键,同table标签
- needAddLimit属性:同table标签
MyCat的HA-原理
MyCat的HA-Haproxy
MyCat的HA-keepalived
MyCat技术落地
对订单表进行分片,以及订单表的子表订单商品表、订单状态表
server.xml
- 配置用户的schemas
<user name="root" defaultAccount="true">
<property name="password">root</property>
<property name="schemas">foodie-shop-dev</property>
</user>
<user name="user">
<property name="password">user</property>
<property name="schemas">foodie-shop-dev</property>
<property name="readOnly">true</property>
</user>
- 配置用户的schemas
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>
订单表分片规则rule.xml
根据用户id(userId)进行分片,同一个用户的所有订单落在同一个分片上
分片规则采用一致性hash
代码修改
数据库连接改为MyCat客户端连接地址
先创建订单,金额设置为0
// 先创建订单,金额先设置为0
newOrder.setTotalAmount(0);
newOrder.setRealPayAmount(0);
// 根据userId进行分片,不允许更新
newOrder.setUserId(null);
ordersMapper.insert(newOrder);
-
在创建OrderItem
// OrderItem会和订单表落在同一个分片
orderItemsMapper.insert(subOrderItem);
-
在更新订单,userId设置为null
// OrderItem创建完成后,在更新Order的金额
newOrder.setTotalAmount(totalAmount);
newOrder.setRealPayAmount(realPayAmount);
// 根据userId进行分片,更新时不允许修改userId
newOrder.setUserId(null);
ordersMapper.updateByPrimaryKeySelective(newOrder);
Sharding-Jdbc客户端模式
官网地址:https://shardingsphere.apache.org/document/current/cn/quick-start/sharding-jdbc-quick-start/
Sharding-Jdbc简介
开源的分布式的关系型数据库的中间件
目前已经进入了Apache孵化器
客户端代理模式
定位为轻量级的java框架,以jar包提供服务
可以理解为增强版的Jdbc驱动
提供4种配置方式
- Java API
- Yaml
- Spring Boot
- Spring命名空间
与MyCat区别
- MyCat是服务端代理,Sharding-Jdbc是客户端代理
- MyCat不支持同一个库内的水平切分,Sharding-Jdbc支持
- MyCat读写分离时可以配置从主库读,Sharding-jdbc只能从从库读
- MyCat子表可以指定分片字段,Sharding-Jdc子表不可以指定分片字段
Sharding-Jdbc分库分表、读写分离配置
spring方式
- pom
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>4.0.0-RC2</version>
</dependency>
- pom
-
sharding-jdbc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:sharding="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
xmlns:master-slave="http://shardingsphere.apache.org/schema/shardingsphere/masterslave"
xmlns:bean="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://shardingsphere.apache.org/schema/shardingsphere/sharding
http://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd
http://shardingsphere.apache.org/schema/shardingsphere/masterslave
http://shardingsphere.apache.org/schema/shardingsphere/masterslave/master-slave.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!-- 数据源配置 这个配置了3个 ds0、slave0、ms1 -->
<!-- 主数据源 -->
<bean id="ds0" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="jdbcUrl"
value="jdbc:mysql://192.168.73.131/sharding_order?serverTimezone=Asia/Shanghai&useSSL=false"/>
</bean>
<!-- 从数据源 -->
<bean id="slave0" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="jdbcUrl"
value="jdbc:mysql://192.168.73.130/sharding_order?serverTimezone=Asia/Shanghai&useSSL=false"/>
</bean>
<bean id="ms1" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="jdbcUrl"
value="jdbc:mysql://192.168.73.132/shard_order?serverTimezone=Asia/Shanghai&useSSL=false"/>
</bean>
<!-- 主从策略 -->
<master-slave:load-balance-algorithm id="msStrategy" type="RANDOM"/>
<sharding:data-source id="sharding-data-source">
<sharding:sharding-rule data-source-names="ds0,slave0,ms1">
<!-- 读写分离配置 -->
<sharding:master-slave-rules>
<sharding:master-slave-rule id="ms0" master-data-source-name="ds0" slave-data-source-names="slave0"
strategy-ref="msStrategy"
/>
</sharding:master-slave-rules>
<!-- 表分片规则 -->
<sharding:table-rules>
<sharding:table-rule logic-table="t_order" actual-data-nodes="ms$->{0..1}.t_order_$->{1..2}"
database-strategy-ref="databaseStrategy" table-strategy-ref="standard"
key-generator-ref="snowflake"
/>
</sharding:table-rules>
<!-- 广播表 -->
<sharding:broadcast-table-rules>
<sharding:broadcast-table-rule table="area"/>
</sharding:broadcast-table-rules>
<!-- 绑定子表 不能指定分片字段 -->
<sharding:binding-table-rules>
<sharding:binding-table-rule logic-tables="t_order,t_order_item"/>
</sharding:binding-table-rules>
</sharding:sharding-rule>
</sharding:data-source>
<sharding:key-generator id="snowflake" column="order_id" type="SNOWFLAKE" props-ref="snow"/>
<bean:properties id="snow">
<prop key="worker.id">678</prop>
<prop key="max.tolerate.time.difference.milliseconds">10</prop>
</bean:properties>
<!-- 数据库分片策略 根据用户id 取模-->
<sharding:inline-strategy id="databaseStrategy" sharding-column="user_id"
algorithm-expression="ms$->{user_id % 2}"/>
<bean id="myShard" class="com.yy.shardingjdbcdemo.sharding.MySharding"/>
<sharding:standard-strategy id="standard" sharding-column="order_id" precise-algorithm-ref="myShard"/>
<!-- 表分片策略 根据order-id取模 -->
<sharding:inline-strategy id="tableStrategy" sharding-column="order_id"
algorithm-expression="t_order_$->{order_id % 2 +1}"/>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="sharding-data-source"/>
<property name="mapperLocations" value="classpath*:/mybatis/*.xml"/>
</bean>
</beans>
-
启动类
@SpringBootApplication
@ImportResource("classpath*:sharding-jdbc.xml")
@MapperScan("com.yy.shardingjdbcdemo.dao")
public class ShardingJdbcDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingJdbcDemoApplication.class, args);
}
}
sping boot方式
- pom
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC2</version>
</dependency>
- pom
-
application.properties
spring.shardingsphere.datasource.names=ds0,ms1,slave0
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbcUrl=jdbc:mysql://192.168.73.131:3306/sharding_order
spring.shardingsphere.datasource.ds0.username=imooc
spring.shardingsphere.datasource.ds0.password=Imooc@123456
spring.shardingsphere.datasource.slave0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.slave0.jdbcUrl=jdbc:mysql://192.168.73.130:3306/sharding_order
spring.shardingsphere.datasource.slave0.username=imooc
spring.shardingsphere.datasource.slave0.password=Imooc@123456
spring.shardingsphere.datasource.ms1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ms1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ms1.jdbcUrl=jdbc:mysql://192.168.73.132:3306/shard_order
spring.shardingsphere.datasource.ms1.username=imooc
spring.shardingsphere.datasource.ms1.password=Imooc@123456
spring.shardingsphere.sharding.master-slave-rules.ms0.master-data-source-name=ds0
spring.shardingsphere.sharding.master-slave-rules.ms0.slave-data-source-names=slave0
spring.shardingsphere.sharding.master-slave-rules.ms0.load-balance-algorithm-type=RANDOM
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ms$->{0..1}.t_order_$->{1..2}
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ms$->{user_id % 2}
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.sharding-column=order_id
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.precise-algorithm-class-name=com.example.shardingjdbcdemo.sharding.MySharding
spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key-generator.props.worker.id=345
spring.shardingsphere.sharding.tables.t_order.key-generator.props.max.tolerate.time.difference.milliseconds=10
#广播表
spring.shardingsphere.sharding.broadcast-tables=area
mybatis.mapper-locations=/mybatis/*.xml
logging.pattern.dateformat=HH:mm:ss
-
启动类
@SpringBootApplication
//@ImportResource("classpath*:sharding-jdbc.xml")
@MapperScan("com.yy.shardingjdbcdemo.dao")
public class ShardingJdbcDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingJdbcDemoApplication.class, args);
}
}
log-bin=imooc_mysql
server-id=1
主配置log-bin,指定文件的名字
主配置server-id,MySQL实例中全局唯一,并且大于0,默认为1
编辑MySQL从上的/etc/my.cnf
server-id=2
- 从配置server-id,与主不能重复
- 在MySQL主上创建用于备份账号
mysql> CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
- 在MySQL主上创建用于备份账号
- MySQL主上加锁,阻止所有的写入操作
mysql> FLUSH TABLES WITH READ LOCK;
- MySQL主上,查看bin-log的文件名和位置
mysql > SHOW MASTER STATUS;
- MySQL主上dump所有数据
mysqldump --all-databases --master-data > dbdump.db -uroot -p
- MySQL主进行解锁,解锁后,主上可以写入数据
mysql> UNLOCK TABLES;
- MySQL从上导入之前dump的数据
mysql < aa.db -uroot -p
- MySQL从上配置主从连接信息
mysql> CHANGE MASTER TO
-> MASTER_HOST='master_host_name',
-> MASTER_PORT=port_num
-> MASTER_USER='replication_user_name',
-> MASTER_PASSWORD='replication_password',
-> MASTER_LOG_FILE='recorded_log_file_name',
-> MASTER_LOG_POS=recorded_log_position;
- master_host_name : MySQL主的地址
- port_num : MySQL主的端口(数字型)
- replication_user_name : 备份账户的用户名
- replication_password : 备份账户的密码
- recorded_log_file_name :bin-log的文件名
- recorded_log_position : bin-log的位置(数字型)
- bin-log的文件名和位置 是 步骤5中的show master status 得到的。
- MySQL从上开启同步
mysql> START SLAVE;
- MySQL从上开启同步
- 查看MySQL从的状态
show slave status;