单机
主从
双主
用户量级上来后,写请求越来越多
一个Master是不能解决问题的,添加多了个主节点进行写入,
多个主节点数据要保存一致性,写操作需要2个master之间同步更加复杂
思路
千万不要一上来就说分库分表,这个是最忌讳的事项
一定要根据实际情况分析,两个角度思考
不分库分表
软优化
硬优化
分库分表
根据业务情况而定,选择合适的分库分表策略(没有通用的策略)
先看只分表是否满足业务的需求和未来增长
如果单分表满足不了需求,再分库分表一起
结论
解决数据库本身瓶颈
连接数: 连接数过多时,就会出现‘too many connections’的错误,访问量太大或者数据库设置的最大连接数太小的原因
Mysql默认的最大连接数为100.可以修改,而mysql服务允许的最大连接数为16384
数据库分表可以解决单表海量数据的查询性能问题
数据库分库可以解决单台数据库的并发访问压力问题
解决系统本身IO、CPU瓶颈
磁盘读写IO瓶颈,热点数据太多,尽管使用了数据库本身缓存,但是依旧有大量IO,导致sql执行速度慢
网络IO瓶颈,请求的数据太多,数据传输大,网络带宽不够,链路响应时间变长
CPU瓶颈,尤其在基础数据量大单机复杂SQL计算,SQL语句执行占用CPU使用率高,也有扫描行数大、锁冲突、锁等待等原因
问题一:跨节点数据库Join关联查询
问题二:分库操作带来的分布式事务问题
问题三:执行的SQL排序、翻页、函数计算问题
问题四:数据库全局主键重复问题
问题五:容量规划,分库分表后二次扩容问题
问题六:分库分表技术选型问题
市场分库分表中间件相对较多,框架各有各的优势与短板,应该如何选择
需求:商品表字段太多,每个字段访问频次不一样,浪费了IO资源,需要进行优化
垂直分表介绍
也就是“大表拆小表”,基于列字段进行的
拆分原则一般是表中的字段较多,将不常用的或者数据较大,长度较长的拆分到“扩展表 如text类型字段
访问频次低、字段大的商品描述信息单独存放在一张表中,访问频次较高的商品基本信息单独放在一张表中
垂直拆分原则
例子:商品详情一般是拆分主表和附表
//拆分前
CREATE TABLE `product` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(524) DEFAULT NULL COMMENT '视频标题',
`cover_img` varchar(524) DEFAULT NULL COMMENT '封面图',
`price` int(11) DEFAULT NULL COMMENT '价格,分',
`total` int(10) DEFAULT '0' COMMENT '总库存',
`left_num` int(10) DEFAULT '0' COMMENT '剩余',
`learn_base` text COMMENT '课前须知,学习基础',
`learn_result` text COMMENT '达到水平',
`summary` varchar(1026) DEFAULT NULL COMMENT '概述',
`detail` text COMMENT '视频商品详情',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
//拆分后
CREATE TABLE `product` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(524) DEFAULT NULL COMMENT '视频标题',
`cover_img` varchar(524) DEFAULT NULL COMMENT '封面图',
`price` int(11) DEFAULT NULL COMMENT '价格,分',
`total` int(10) DEFAULT '0' COMMENT '总库存',
`left_num` int(10) DEFAULT '0' COMMENT '剩余',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `product_detail` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL COMMENT '产品主键',
`learn_base` text COMMENT '课前须知,学习基础',
`learn_result` text COMMENT '达到水平',
`summary` varchar(1026) DEFAULT NULL COMMENT '概述',
`detail` text COMMENT '视频商品详情',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
需求:C端项目里面,单个数据库的CPU、内存长期处于90%+的利用率,数据库连接经常不够,需要进行优化
垂直分库讲解
垂直分库针对的是一个系统中的不同业务进行拆分, 数据库的连接资源比较宝贵且单机处理能力也有限
没拆分之前全部都是落到单一的库上的,单库处理能力成为瓶颈,还有磁盘空间,内存,tps等限制
拆分之后,避免不同库竞争同一个物理机的CPU、内存、网络IO、磁盘,所以在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈
垂直分库可以更好解决业务层面的耦合,业务清晰,且方便管理和维护
一般从单体项目升级改造为微服务项目,就是垂直分库
需求:当一张表的数据达到几千万时,查询一次所花的时间长,需要进行优化,缩短查询时间
都是大表拆小表
水平分表
把一个表的数据分到一个数据库的多张表中,每个表只有这个表的部分数据
核心是把一个大表,分割N个小表,每个表的结构是一样的,数据不一样,全部表的数据合起来就是全部数据
针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去
但是这些表还是在同一个库中,所以单数据库操作还是有IO瓶颈,主要是解决单表数据量过大的问题
减少锁表时间,没分表前,如果是DDL(create/alter/add等)语句,当需要添加一列的时候mysql会锁表,期间所有的读写操作只能等待
需求:高并发的项目中,水平分表后依旧在单个库上面,1个数据库资源瓶颈 CPU/内存/带宽等限制导致响应慢,需要进行优化
水平分库
方案一:自增id,根据ID范围进行分表(左闭右开)
规则案例
优点
缺点
大部分读和写都访会问新的数据,有IO瓶颈,整体资源利用率低
数据倾斜严重,热点数据过于集中,部分节点有瓶颈
范围角度思考问题 (范围的话更多是水平分表)
数字
时间
空间
基于Range范围分库分表业务场景
微博发送记录、微信消息记录、日志记录,id增长/时间分区都行
网站签到等活动流水数据时间分区最好
大区划分(一二线城市和五六线城市活跃度不一样,如果能避免热点问题,即可选择)
方案二:hash取模(Hash分库分表是最普遍的方案)
案例规则
A库ID = userId % 库数量 2
表ID = userId / 库数量 2 % 表数量4
userId | id % 2 (库-取余) | id /2 % 4 (表) |
---|---|---|
1 | 1 | 0 |
2 | 0 | 1 |
3 | 1 | 1 |
4 | 0 | 2 |
5 | 1 | 2 |
6 | 0 | 3 |
7 | 1 | 3 |
8 | 0 | 0 |
9 | 1 | 0 |
优点
缺点
Cobar(已经被淘汰没使用了)
TDDL
Mycat
ShardingSphere 下的Sharding-JDBC
地址:https://shardingsphere.apache.org/
Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈
它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 3个独立产品组合Sharding-JDBC
最感兴趣的是Mycat和ShardingJdbc区别,也是被面试官问比较多的
两者设计理念相同,主流程都是SQL解析-->SQL路由-->SQL改写-->结果归并
sharding-jdbc
Mycat
什么是ShardingSphere
Database Plus
ShardingSphere-Sidecar(规划中,简单知道就行)
Database Mesh
,又可称数据库网格ShardingSphere-JDBC
它使用客户端直连数据库,以 jar 包形式提供服务
无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架
适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis,或直接使用 JDBC
支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库
采用无中心化架构,与应用程序共享资源,适用于 Java 开发的高性能的轻量级 OLTP 应用
ShardingSphere-Proxy
数据节点Node
真实表
逻辑表
绑定表
广播表
数据库表分片(水平库、表)
分片键 (PartitionKey)
行表达式分片策略 InlineShardingStrategy(必备)
只支持【单分片键】使用Groovy的表达式,提供对SQL语句中的 =和IN 的分片操作支持
可以通过简单的配置使用,无需自定义分片算法,从而避免繁琐的Java代码开发
prouduct_order_$->{user_id % 8}` 表示订单表根据user_id模8,而分成8张表,表名称为`prouduct_order_0`到`prouduct_order_7
标准分片策略StandardShardingStrategy(需了解)
复合分片策略ComplexShardingStrategy(需了解)
Hint分片策略HintShardingStrategy(需了解)
这种分片策略无需配置分片健,分片健值也不再从 SQL中解析,外部手动指定分片健或分片库,让 SQL在指定的分库、分表中执行
用于处理使用Hint行分片的场景,通过Hint而非SQL解析的方式分片的策略
Hint策略会绕过SQL解析的,对于这些比较复杂的需要分片的查询,Hint分片策略性能可能会更好
不分片策略 NoneShardingStrategy(需了解)
11
11
11
2.5.5
3.4.0
1.18.16
4.1.1
4.12
1.1.16
true
org.springframework.boot
spring-boot-starter-web
${spring.boot.version}
org.springframework.boot
spring-boot-starter-test
${spring.boot.version}
test
com.baomidou
mybatis-plus-boot-starter
${mybatisplus.boot.starter.version}
mysql
mysql-connector-java
8.0.27
org.projectlombok
lombok
${lombok.version}
org.apache.shardingsphere
sharding-jdbc-spring-boot-starter
${sharding-jdbc.version}
junit
junit
${junit.version}
org.springframework.boot
spring-boot-maven-plugin
${spring.boot.version}
true
true
分库分表需求
数据库
xdclass_shop_order_0
product_order_0
product_order_1
xdclass_shop_order_1
product_order_0
product_order_1
脚本
CREATE TABLE `product_order_0` (
`id` bigint NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订单唯一标识',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未支付订单,PAY已经支付订单,CANCEL超时取消订单',
`create_time` datetime DEFAULT NULL COMMENT '订单生成时间',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '订单实际支付价格',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
`user_id` bigint DEFAULT NULL COMMENT '用户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("product_order")
public class ProductOrderDO {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String outTradeNo;
private String state;
private Date createTime;
private Double payAmount;
private String nickname;
private Long userId;
}
//数据库实体类
public interface ProductOrderMapper extends BaseMapper {
}
server.port=8080
spring.application.name=xdclass-jdbc
logging.level.root=INFO
# 打印执行的数据库以及语句
spring.shardingsphere.props.sql.show=true
# 数据源 db0
spring.shardingsphere.datasource.names=ds0
# 第一个数据库
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.jdbc-url=jdbc:mysql://165.25.317.15:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root
# 指定product_order表的数据分布情况,配置数据节点,行表达式标识符使用 ${...} 或 $->{...},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...}
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
# 指定product_order表的分片策略,分片策略包括【分片键和分片算法】
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{user_id % 2}
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes = DemoApplication.class)
@Slf4j
public class DbTest {
@Autowired
private ProductOrderMapper productOrderMapper;
@Test
public void testSaveProductOrder(){
for(int i=0;i<10;i++){
ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setNickname("i="+i);
productOrder.setOutTradeNo(UUID.randomUUID().toString().substring(0,32));
productOrder.setPayAmount(100.00);
productOrder.setState("PAY");
productOrder.setUserId(Long.valueOf(i+""));
productOrderMapper.insert(productOrder);
}
}
}
控制台SQL
单库下一般使用Mysql自增ID, 但是分库分表后,会造成不同分片上的数据表主键会重复。
需求
业界常用ID解决方案
数据库自增ID
DB1: 单数
//从1开始、每次加2
DB2: 偶数
//从2开始,每次加2
缺点
UUID
性能非常高,没有网络消耗
缺点
Redis发号器
利用Redis的INCR和INCRBY来实现,原子操作,线程安全,性能比Mysql强劲
缺点
Snowflake雪花算法
twitter 开源的分布式 ID 生成算法,代码实现简单、不占用宽带、数据迁移不受影响
生成的 id 中包含有时间戳,所以生成的 id 按照时间递增
部署了多台服务器,需要保证系统时间一样,机器编号不一样
缺点