shardingsphere
目录- shardingsphere
- 1.面试题
- 2.分库分表带来的优点
- 3.分库分表带来的问题
- 4.分库分表核心概念
- 5.水平分库分表策略
- 6.分库分表技术选型
- 7.ShardingSphere的认知
- 8.分库分表及ShardingJDBC常见术语
- 9.SpringBoot整合MyBatis-plus及Sharding-JDBC
- 10.Sharding-Jdbc常见分片算法
- 11. id生成策略之雪花算法
- 12.Sharding-Jdbc广播表和绑定配置表实战
1.面试题
1.1 业务增长-数据库性能优化
-
数据库-单表1千万数据,未来1年还会增长500多万,性能较慢,怎么优化
-
根据实际情况优化
-
不分库分表
- 硬优化:提升系统硬件(更快的IO、更多的内存):宽带、CPU、硬盘
- 软优化:
- 数据库参数调优(运维做的)
- 分析慢查询SQL语句,分析执行计划,SQL优化
- 优化数据库索引
- 优化数据表结构优化
- 引入NOSQL和程序架构调整(数据库搭建主从,读写分离)
-
分库分表
-
根据业务情况而定,选择适合的分库分表策略(没有通用的策略)
- 外卖、物流、电商领域
-
先看只分表是否满足业务需求和未来增长
数据库分表能够解决单表数据量很大的时候,数据查询效率问题; 但无法给数据库的并发操作带来效率提高,分表的实质还是在操作同一个数据库,会受IO性能的限制
-
如果分表不能满足需求,再进行分库分表
-
结论:在数据量访问压力不是特别大的情况,首先考虑缓存、主从分离、索引等技术方法 如果数据量极大,且业务增长快,再考虑分库分表
-
-
2.分库分表带来的优点
-
分库分表解决的问题
-
解决数据库本身的瓶颈
- 解决连接数不足问题:连接数超过数据库设置的最大连接数,会出现问题
- MySQL默认连接数是100,,⽽mysql服务允 许的最⼤连接数为16384
- 解决海量数据的查询问题
- 解决单台数据库访问并发压力问题
- 解决连接数不足问题:连接数超过数据库设置的最大连接数,会出现问题
-
解决系统本身IO、CPU瓶颈
-
磁盘读写IO瓶颈:热点数据太多,大量IO操作,导致sql执行慢
-
网络IO瓶颈,请求数据太多,数据传输大,宽带不够,导致链路响应时间边长
-
CPU瓶颈,尤其在基础数据量⼤单机复杂SQL计算,SQL语 句执⾏占⽤CPU使⽤率⾼,也有扫描⾏数⼤、锁冲突、锁 等待等原因;
使用explain执行计划分析
-
-
3.分库分表带来的问题
-
分库后,跨节点数据库Join关联查询和多维度查询
- 数据库切分前,多表关联查询,可以通过sql join进行实现
- 分库分表后,数据可能分布在不同的节点上,sql join带来的问题就比较麻烦
-
不同维度查看数据,利用的partitionKey是不一样的
- 例如
- 订单表 的partionKey是user_id,用户查看自己的订单列表方便
- 但商家查看自己店铺的订单列表就麻烦,分布在不同数据节点
- 例如
-
分库后,会带来分布式事务问题
-
分库后,执行的SQL排序、翻页、函数计算等问题
- 分库后,数据在不同节点上,跨节点多库查询,会出现limit分页、order by排序等问题
-
分库后,会带来数据库全局主键重复问题
- 常规表的id是使⽤⾃增id进⾏实现,分库分表后,由于表 中数据同时存在不同数据库中,如果⽤⾃增id,则会出现 冲突问题
-
容量规划,分库分表后⼆次扩容问题
- 业务发展快,初次分库分表后,满⾜不了数据存储,导致 需要多次扩容
-
分库分表技术选型问题
- 市场上分库分表中间件较多,怎么选择
4.分库分表核心概念
4.1 垂直分表
- 需求:商品表字段太多,每个字段的访问频次不一样,浪费IO资源,需要进行优化
- 垂直分表:
- 基于表的字段进行拆分,“大表拆分为小表”;把不常用或数据大的字段,拆分到“扩展表”,如text类型字段
- 访问频次低、字段大的商品描述信息单独存放一张表中;将访问频次高的商品基本信息单独放在一张表中
- 垂直拆分原则
- 把不常⽤的字段单独放在⼀张表;
- 把text,blob等⼤字段拆分出来放在附表中;
- 业务经常组合查询的列放在⼀张表中;
例子:商品详情一般是拆分主表和附表
-
拆分前
id title 视频标题 cover_img 封面图 price 价格,分 total 总库存 left_num 剩余 learn_base 课前须知,学习基础 learn_result 达到水平 summary 概述 detail 视频商品详情
-
拆分后
商品表(主表):经常访问,比如商品界面 id title 视频标题 cover_img 封面图 price 价格,分 total 总库存 left_num 剩余 ------------------ 附表 点击进入商品之后的信息 id productId 商品id learn_base 课前须知,学习基础 learn_result 达到水平 summary 概述 detail 视频商品详情
4.2 垂直分库
-
需求:C端项目里,单个数据库的CPU、内存长期处于90+%的情况,数据库连接经常不够,需要优化。
-
垂直分库
垂直分库是针对一个系统中的不同业务进行拆分,数据库连接资源不够且单机处理能力有限 没拆分前,全部数据在单库上,单库处理能力成为瓶颈,磁盘空间不足,内存,tps等限制 拆分之后,避免不同库竞争同⼀个物理机的CPU、内存、⽹络IO、磁盘,所以在⾼并发场景下,垂直分库⼀定程度上能够突破IO、连接数及单机硬件资源的瓶颈
- 垂直分库垂直分库可以更好解决业务层⾯的耦合,业务清晰,且⽅便管 理和维护
- 使用场景:单体项⽬升级改造为微服务项⽬,就是垂直分库
-
问题:
- 垂直分库,可以提高并发,但并没有解决单表数据量大的情况
4.3 水平分表
- 需求:当一张表数据达到几千万时,就算使用索引也查询时间过长,需要进行优化,缩短查询时间
- 水平分表:对数据进行拆分
- 把⼀个表的数据分到⼀个数据库的多张表中,每个表只有这个 表的部分数据
- 核心:把数据量大的表,分割成N个小表;N个小表数据合起来就是原来大表数据
- 针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去
- 但是这些表还是在同⼀个库中,所以单数据库操作还是有IO 瓶颈,主要是解决单表数据量过⼤的问题
- 优点:
- 减少锁表时间,没分表前,如果是DDL(create/alter/add等) 语句,当需要添加⼀列的时候mysql会锁表,期间所有的读写 操作只能等待;分表后,只会锁住部分表数据
4.4 水平分库
-
需求:高并发项目中,⽔平分表后依旧在单个库上⾯,1个数 据库资源瓶颈 CPU/内存/带宽等限制导致响应慢,需要进⾏优化
-
水平分库
- 把表的数据按照⼀定规则分到不同的数据库中,数据库在 不同的服务器上
- 水平分库是把不同表拆到不同数据库中,它是对数据行的拆分,不影响表结构
- 每个库的结构都⼀样,但每个库的数据都不⼀样,没有交集, 所有库的并集就是全量数据
- 水平分库的粒度,比水平分表更大
4.5 分库分表总结
-
技术负责人前瞻性思维
-
我们需要提前考虑系统一年到两年左右的业务情况
-
对数据库服务器的QPS、连接数、容量等做合理评估和规划
-
常规开发⾥⾯单表建议1千万内,推荐是百万级别单表存储,常 规sql和索引优化先⾏,然后结合缓存+异步+nosql+mq
-
-
垂直角度
- 垂直分表::将⼀个表字段拆分多个表,每个表存储部分字段
- 好处:避免了IO时,锁表次数,分离热点字段和⾮热点字段, 避免⼤字段IO导致性能下降
- 原则:业务经常组合查询的字段⼀个表;不常⽤字段⼀个 表;text、blob类型字段作为附属表
- 垂直分库:根据业务将表分类,放到不同的数据库服务器上
- 好处:避免表之间竞争同个物理机的资源,⽐如CPU/内 存/硬盘/⽹络IO
- 原则:根据业务相关性进⾏划分,领域模型,微服务划分 ⼀般就是垂直分库
- 垂直分表::将⼀个表字段拆分多个表,每个表存储部分字段
-
⽔平⻆度(表结构⼀样)
- ⽔平分库:把同个表的数据按照⼀定规则分到不同的数据库 中,数据库在不同的服务器上
- 好处: 多个数据库,降低了系统的IO和CPU压⼒
- 原则
- 选择合适的分⽚键和分⽚策略,和业务场景配合
- 避免数据热点和访问不均衡、避免⼆次扩容难度⼤
- ⽔平分表:同个数据库内,把⼀个表的数据按照⼀定规则拆分 到多个表中,对数据进⾏拆分,不影响表结构
- 单个表的数据量少了,业务SQL执⾏效率⾼,降低了系统 的IO和CPU压⼒
- 原则
- 选择合适的分⽚键和分⽚策略,和业务场景配合
- 避免数据热点和访问不均衡、避免⼆次扩容难度⼤
- ⽔平分库:把同个表的数据按照⼀定规则分到不同的数据库 中,数据库在不同的服务器上
5.水平分库分表策略
5.1 range(范围)
-
方案1:自增id,根据ID范围进行分表(左闭右开)
-
规则案例
- 1~1,000,000 是 table_1
- 1,000,000 ~2,000,000 是 table_2
- 2,000,000~3,000,000 是 table_3
- ...
-
优点
- id是⾃增⻓,可以⽆限增⻓
- 扩容不⽤迁移数据,容易理解和维护
-
缺点:
- ⼤部分读和写都访会问新的数据,有IO瓶颈,整体资源利 ⽤率低
- 数据倾斜严重,热点数据过于集中,部分节点有瓶颈
-
-
其他根据范围进行水平分库分表(范围一般是分表)
- 数字
- 自增id范围
- 时间
- 年、月、日范围:⽐如按照⽉份⽣成 库或表 pay_log_2022_01、 pay_log_2022_02
- 空间
- 地理位置:省份、区域(华东、华北、华南):地理位置:省份、区域(华东、华北、华南)
- 数字
- 基于Range范围分库分表业务场景
- 微博发送记录、微信消息记录、⽇志记录,适用于id增⻓/时间分区 都⾏
- ⽔平分表为主,⽔平分库则容易造成资源的浪费
- ⽹站签到等活动流⽔数据时间分区最好
- ⽔平分表为主,⽔平分库则容易造成资源的浪费
- ⼤区划分(⼀⼆线城市和五六线城市活跃度不⼀样,如果能避 免热点问题,即可选择)
- saas业务⽔平分库(华东、华南、华北等)
- 微博发送记录、微信消息记录、⽇志记录,适用于id增⻓/时间分区 都⾏
5.2 Hash取模
-
⽅案⼆:hash取模(Hash分库分表是最普遍的⽅案)
-
取模计算规则
-
如:⽤户ID是整数型的,要分2库,每个库表数量4表,⼀共8张表
-
用户ID取模后,值是0到7的要平均分配到每张表
库ID = userId % 库数量 2 表ID = userId / 库数量 2 % 表数量4
-
例子
userId id % 库数量 (库id) (id/库数量)%表数量(表id) 1 1 0 2 0 1 3 1 1 4 0 2 5 1 2 -
优点:
- 保证数据较均匀的分散落在不同的库、表中,可以有效的避免 热点数据集中问题,
-
缺点:
- 扩容不是很⽅便,需要数据迁移
-
6.分库分表技术选型
常见分库分表中间件
-
TDDL
- 淘宝根据自己的业务特点开发了 TDDL (Taobao Distributed Data Layer)
- 基于JDBC规范,没有server,以client-jar的形式存在,引入项目即可使用
- 开源功能比较少,阿里内部使用为主
-
MyCat
- 地址 http://www.mycat.org.cn/
- Java语言编写的MySQL数据库网络协议的开源中间件,前身 Cobar
- 遵守Mysql原生协议,跨语言,跨平台,跨数据库的通用中间件代理
- 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库
- 和ShardingShere下的Sharding-Proxy作用类似,需要单独部署
-
ShardingSphere 下的Sharding-JDBC
-
Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈
- 它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 3个独立产品组合
-
Sharding-JDBC
- 基于jdbc驱动,不用额外的proxy,支持任意实现 JDBC 规范的数据库
- 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖
- 可理解为加强版的 JDBC 驱动,兼容 JDBC 和各类 ORM 框架
-
Mycat和ShardingJdbc区别
- 两者设计理念相同,主流程都是SQL解析-->SQL路由-->SQL改写-->结果归并
- sharding-jdbc
- 基于jdbc驱动,不用额外的proxy,在本地应用层重写Jdbc原生的方法,实现数据库分片形式
- 是基于 JDBC 接口的扩展,是以 jar 包的形式提供轻量级服务的,性能高
- 代码有侵入性
- Mycat
- 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库
- 客户端所有的jdbc请求都必须要先交给MyCat,再有MyCat转发到具体的真实服务器
- 缺点是效率偏低,中间包装了一层
- 代码无侵入性
7.ShardingSphere的认知
-
什么是ShardingSphere
- 是⼀套开源的分布式数据库解决⽅案组成的⽣态圈,定位为 Database Plus
- 它由 JDBC、Proxy 和 Sidecar这 3 款既能够独⽴部署,⼜⽀ 持混合部署配合使⽤的产品组成
-
三大构成
-
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
- 数据库代理端,提供封装了数据库⼆进制协议的服务端版 本,⽤于完成对异构语⾔的⽀持
- 向应⽤程序完全透明,可直接当做 MySQL/PostgreSQL
- 它可以使⽤任何兼容 MySQL/PostgreSQL 协议的访问客户 端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据
-
ShardingSphere-Sidecar(规划中,简单知道就⾏)
- 定位为 Kubernetes 的云原⽣数据库代理,以 Sidecar 的 形式代理所有对数据库的访问
- 通过⽆中⼼、零侵⼊的⽅案提供与数据库交互的啮合层, 即 Database Mesh,⼜可称数据库⽹格
-
8.分库分表及ShardingJDBC常见术语
-
数据节点Node
- 数据分片的最小单元
- 比如:分库分表后,某数据库中的一张表;ds_0.product_order_0
-
真实表
- 在分⽚的数据库中真实存在的物理表
- ⽐如订单表 product_order_0、product_order_1、 product_order_2
-
逻辑表
- ⽔平拆分的数据库(表)的相同逻辑和数据结构表的总称
- ⽐如订单表 product_order_0、product_order_1、 product_order_2,逻辑表就是拆分前的表product_order
-
绑定表
- 指分⽚规则⼀致的主表和⼦表
- ⽐如product_order表和product_order_item表,均按照 order_id分⽚,则此两张表互为绑定表关系
- 绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询 效率将⼤⼤提升
-
⼴播表
- 指所有的分⽚数据源中都存在的表,表结构和表中的数据在每 个数据库中均完全⼀致
- 适⽤于数据量不⼤且需要与海量数据的表进⾏关联查询的场景
- 例如:字典表、配置表,类似冗余表
9.SpringBoot整合MyBatis-plus及Sharding-JDBC
-
新建maven项目
-
导入依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xdclass</groupId> <artifactId>sharding-jdbc</artifactId> <version>1.0-SNAPSHOT</version> <properties> <!--JDK版本,如果是jdk8则这⾥是 1.8--> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <spring.boot.version>2.5.5</spring.boot.version> <mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version> <lombok.version>1.18.16</lombok.version> <sharding-jdbc.version>4.1.1</sharding-jdbc.version> <junit.version>4.12</junit.version> <druid.version>1.1.16</druid.version> <!--跳过单元测试--> <skipTests>true</skipTests> </properties> <dependencies> <!-- 导入springBoot 依赖 begin--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring.boot.version}</version> </dependency> <!--<dependency>--> <!-- <groupId>org.springframework.boot</groupId>--> <!--<artifactId>spring-boot-test</artifactId>--> <!--</dependency>--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring.boot.version}</version> <scope>test</scope> </dependency> <!-- 导入springBoot 依赖 end--> <!--mybatis plus和springboot整合 begin--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatisplus.boot.starter.version}</version> </dependency> <!-- end --> <!-- mysql与SpringBoot整合 begin--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <!-- end --> <!-- lombok 插件依赖 begin--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <!--<scope>provided</scope>--> </dependency> <!-- end--> <!-- 导入shardingsphere依赖 begin --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>${sharding-jdbc.version}</version> </dependency> <!-- end --> <dependency> <!-- 配置⽂件修改--> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring.boot.version}</version> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build> </project>
-
数据库
-
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 { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; /** * 订 单唯⼀标识 */ private String outTradeNo; /** * NEW 未⽀ 付订单,PAY已经⽀付订单,CANCEL超时取消订单 */ private String state; /** * 订单⽣ 成时间 */ private Date createTime; /** * 订 单实际⽀付价格 */ private BigDecimal payAmount; /** * 昵称 */ private String nickname; /** * ⽤户id */ private Long userId; }
-
配置
server.port=8080 spring.application.name=xdclass-sharding-jdbc logging.level.root=INFO # 打印执⾏的数据库以及语句 spring.shardingsphere.props.sql.show=true # 设置数据源名称 ds0,ds1 spring.shardingsphere.datasource.names=ds0,ds1 # 第⼀个数据库 基本配置 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://127.0.0.1:3306/xdclass_shop_order_0?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8&useSSL=false spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=root #第二个数据库 配置 spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://127.0.0.1:3306/xdclass_shop_order_1?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8&useSSL=false spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=root
10.Sharding-Jdbc常见分片算法
-
数据库表分片
- 分片键 和 分片策略
-
分片键
-
⽤于分⽚的数据库字段,是将数据库(表)⽔平拆分的关键字段
-
⽐如prouduct_order订单表,根据用户ID 做哈 希取模,则userId是分⽚键
-
除了对单分⽚字段的⽀持,ShardingSphere也⽀持根据多个 字段进⾏分⽚
-
-
分片策略
-
行表达式分片策略 InlineShardingStrategy
-
只支持【单分片键】使用Groovy的表达式,提供对SQL语句中的 =和IN 的分片操作支持
-
可以通过简单的配置使用,无需自定义分片算法,从而避免繁琐的Java代码开发
prouduct_order_$->{user_id % 8}` 表示订单表根据user_id模8,而分成8张表,表名称为`prouduct_order_0`到`prouduct_order_7
-
-
标准分片策略StandardShardingStrategy
- 只支持【单分片键】,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法
- PreciseShardingAlgorithm 精准分片 是必选的,用于处理=和IN的分片
- RangeShardingAlgorithm 范围分配 是可选的,用于处理BETWEEN AND分片
- 如果不配置RangeShardingAlgorithm,如果SQL中用了BETWEEN AND语法,则将按照全库路由处理,性能下降
-
复合分片策略ComplexShardingStrategy
- 支持【多分片键】,多分片键之间的关系复杂,由开发者自己实现,提供最大的灵活度
- 提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持
- prouduct_order_0_0、prouduct_order_0_1、prouduct_order_1_0、prouduct_order_1_1
-
Hint分片策略HintShardingStrategy
- 这种分片策略无需配置分片健,分片健值也不再从 SQL中解析,外部手动指定分片健或分片库,让 SQL在指定的分库、分表中执行
- 用于处理使用Hint行分片的场景,通过Hint而非SQL解析的方式分片的策略
- Hint策略会绕过SQL解析的,对于这些比较复杂的需要分片的查询,Hint分片策略性能可能会更好
-
不分片策略 NoneShardingStrategy
- 不分片的策略。
-
-
自己实现分片策略的优缺点
-
优点:可以根据分片策略代码里面自己拼装 真实的数据库、真实的表,灵活控制分片规则
-
缺点:增加了编码,不规范的sql容易造成全库表扫描,部分sql语法支持不友好
-
10.1 行表达式分片策略
-
只⽀持【单分⽚键】使⽤Groovy的表达式,提供对SQL语 句中的 =和IN 的分⽚操作⽀持
-
可以通过简单的配置使⽤,⽆需⾃定义分⽚算法,从⽽避 免繁琐的Java代码开发
-
配置
# 指定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) @SpringBootTest(classes = DemoApplication.class) @Slf4j public class DbTest { @Autowired private ProductOrderMapper productOrderMapper; @Test public void testSaveProductOrder() { for(int i=0;i<10;i++){ ProductOrderDO productOrderDO = new ProductOrderDO(); productOrderDO.setNickname("小滴"+i); productOrderDO.setOutTradeNo(UUID.randomUUID().toString().substring(0,32)); productOrderDO.setPayAmount(BigDecimal.valueOf(100.00)); productOrderDO.setState("好"); productOrderDO.setUserId(Long.valueOf(i)); productOrderMapper.insert(productOrderDO); } } }
-
效果:进行插入操作,会根据 分片策略算法,分别插入两个表中
- 问题:常规表的id是使⽤⾃增id进⾏实现,分表后,由于表 中数据同时存在不同数据库中,如果⽤⾃增id,则会出现 冲突问题
- 解决方案
- id生成策略雪花算法
10.2 标准分⽚策略StandardShardingStrategy
- 只⽀持【单分⽚键】,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分⽚算法
- PreciseShardingAlgorithm 精准分⽚ 是必选的,⽤于处理=和IN的分⽚
- RangeShardingAlgorithm 范围分配 是可选的,⽤于处理BETWEEN AND分⽚如果不配置RangeShardingAlgorithm,如果SQL中⽤了BETWEEN AND语法,则将按照全库路由处理,性能下降
10.3 Hint分⽚策略HintShardingStrategy
- 这种分⽚策略⽆需配置分⽚健,分⽚健值也不再从 SQL中解析,外部⼿动指定分⽚健或分⽚库,让 SQL在指定的分库、分表中执⾏
- ⽤于处理使⽤Hint⾏分⽚的场景,通过Hint⽽⾮SQL解析的⽅式分⽚的策略
- Hint策略会绕过SQL解析的,对于这些⽐较复杂的需要分⽚的查询,Hint分⽚策略性能可能会更好
11. id生成策略之雪花算法
11.1雪花算法介绍
-
单库下⼀般使⽤Mysql⾃增ID, 但是分库分表后,会造成不同分⽚ 上的数据表主键会重复。
-
需求
- 性能强劲
- 全局唯⼀
- 防⽌恶意⽤户规矩id的规则来获取数据
-
业界常用id解决方案
-
数据库⾃增ID
-
利⽤⾃增id, 设置不同的⾃增步⻓,
-
auto_increment_offset:表示自增长字段从那个数开始
-
auto-increment-increment:表示自增长字段每次递增的量
-
show variables like '%auto_inc%'; show session variables like '%auto_inc%'; -- //session会话变量 show global variables like '%auto_inc%'; -- //全局变量 SET @auto_increment_increment = 3 ; SET session auto_invrement_increment=2; SET global auto_increment_increment=1;
-
DB1: 单数 //从1开始、每次加2 DB2: 偶数 //从2开始,每次加2
- 依靠数据库系统的功能实现,但是未来扩容麻烦
- 主从切换时的不⼀致可能会导致重复发号
- 性能瓶颈存在单台sql上
-
-
UUID
- 性能⾮常⾼,没有⽹络消耗
- 缺点
- ⽆序的字符串,不具备趋势⾃增特性
- UUID太⻓,不易于存储,浪费存储空间,很多场景不 适⽤
-
Redis发号器
- 利⽤Redis的INCR和INCRBY来实现,原⼦操作,线程安 全,性能⽐Mysql强劲
- 缺点
- 需要占⽤⽹络资源,增加系统复杂度
-
Snowflake雪花算法
- twitter 开源的分布式 ID ⽣成算法,代码实现简单、不占 ⽤宽带、数据迁移不受影响
- ⽣成的 id 中包含有时间戳,所以⽣成的 id 按照时间递增
- 注意事项:多台服务器,需要保证系统时间⼀样,机器编号不 ⼀样
- 缺点
- 依赖系统时钟(多台服务器时间⼀定要⼀样)
-
雪花算法
- 什么是雪花算法Snowflake
- twitter⽤scala语⾔编写的⾼效⽣成唯⼀ID的算法
- 优点
- ⽣成的ID不重复
- 算法性能⾼
- 基于时间戳,基本保证有序递增
bit 与 byte
bit(位):电脑中存储的最⼩单位,可以存储⼆进制中的0或1
byte(字节):⼀个byte由8个bit组成
int:4 个字节
short:2 个字节
long:8 个字节
byte:1 个字节
float:4 个字节
double:8 个字节
char:2 个字节
注意:
数据类型在不同位数机器的平台下⻓度不同(怼⾯试官的严谨性)
16位平台 int 2个字节16位
32位平台 int 4个字节32位
64位平台 int 4个字节32位
-
雪花算法生成的数字,long类型,即 8 byte ,64 bit
- 64 位表示的范围:表示的值 -9223372036854775808(-2的63次⽅) ~ 9223372036854775807(2的63次⽅-1)
- 但生成唯一id是数据库主键,所以不能是负数;
- 即生成id范围:0~9223372036854775807(2的63次⽅-1)
11.2 雪花算法生成分布式ID的坑
-
全局唯⼀不能重复-坑
- 坑一
- 分布式部署就需要分配不同的workId, 如果workId相同, 可能会导致⽣成的id相同
- 坑二
- 分布式情况下,需要保证各个系统时间⼀致,如果服务器 的时钟回拨,就会导致⽣成的 id 重复
- 啥时候会有系统回拨????
- ⼩滴课堂-⽼王闲着,⼈⼯去⽣产环境做了系统时间 调整,应该不会这么傻吧
- 业务需求,代码⾥⾯做了系统时间同步
- 坑一
-
解决时间回拨的方法
-
服务器时钟回拨会导致产生重复的 ID,
SNOWFLAKE
方案中对原有雪花算法做了改进,增加了一个最大容忍的时钟回拨毫秒数。如果时钟回拨的时间超过最大容忍的毫秒数阈值,则程序直接报错;如果在可容忍的范围内,默认分布式主键生成器,会等待时钟同步到最后一次主键生成的时间后再继续工作。
最大容忍的时钟回拨毫秒数,默认值为 0,可通过属性
max.tolerate.time.difference.milliseconds
设置。
# 最大容忍的时钟回拨毫秒数 spring.shardingsphere.sharding.tables.product_order.key-generator.max.tolerate.time.difference.milliseconds=5
-
11.3 雪花算法使用方法
-
配置实操
#配置workID spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1 # 最大容忍的时钟回拨毫秒数 spring.shardingsphere.sharding.tables.product_order.key-generator.max.tolerate.time.difference.milliseconds=5
-
方式一:使⽤MybatisPlus的配置
- 在DO类配置
Data @EqualsAndHashCode(callSuper = false) @TableName("product_order") public class ProductOrderDO { private static final long serialVersionUID = 1L; //id默认是自增,设置为 雪花算法 生成id @TableId(value = "id", type = IdType.ASSIGN_ID) private Long id;
-
方式二:添加配置
#id⽣成策略 spring.shardingsphere.sharding.tables.product_order.key-generator.column=id spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
-
12.Sharding-Jdbc广播表和绑定配置表实战
12.1广播表介绍配置和实战
-
什么是广播表
- 指所有的分⽚数据源中都存在的表,表结构和表中的数据在每 个数据库中均完全⼀致
- 适⽤于数据量不⼤且需要与海量数据的表进⾏关联查询的场景 (解决跨库关联查询问题)
- 例如:字典表、配置表;类似冗余表
-
添加配置
#配置⼴播表 spring.shardingsphere.sharding.broadcast-tables=ad_config spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
12.2水平分库+水平分表实战
-
库表结构
- 2个数据库、每个库2张表
- 需求 插⼊订单数据,分布在不同的库和表上
-
分库分表配置
- 分库规则 根据 user_id 进⾏分库
- 分表规则 根据 product_order_id 订单号进⾏分表
-
配置
# 数据源 db0 spring.shardingsphere.datasource.names=ds0,ds1 # 第一个数据库 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://120.25.217.15:3306/xdclass_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=xdclass.net168 # 第二个数据库 spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://120.25.217.15:3306/xdclass_shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=xdclass.net168 #配置workId spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1 #配置广播表 spring.shardingsphere.sharding.broadcast-tables=ad_config spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE #配置【默认分库策略】 spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2 } #配置分库规则 spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.sharding-column=user_id spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 2 } #id生成策略 spring.shardingsphere.sharding.tables.product_order.key-generator.column=id spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE # 指定product_order表的数据分布情况,配置数据节点,行表达式标识符使用 ${...} 或 $->{...}, # 但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...} #spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1} spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1} # 指定product_order表的分片策略,分片策略包括【分片键和分片算法】 spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=id spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{id % 2} # 指定product_order_item表的分片策略,分片策略包括【分片键和分片算法】 spring.shardingsphere.sharding.tables.product_order_item.actual-data-nodes=ds$->{0..1}.product_order_item_$->{0..1} spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.sharding-column=product_order_id spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.algorithm-expression=product_order_item_$->{product_order_id % 2}
注意:数据库和表的下标如果不想从0开始,则hash取模后+1 {user_id % 2+1
12.3 绑定表配置实战
- 什么是绑定表
- 指分⽚规则⼀致的主表和⼦表
- ⽐如product_order表和product_order_item表,均按照 order_id分⽚,则此两张表互为绑定表关系
- 绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询 效率将⼤⼤提升
-
库表数量
-
配置实战
#配置workId spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1 # 指定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} # 配置product_order表的id⽣成策略 spring.shardingsphere.sharding.tables.product_order.key-generator.column=id spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE #配置⼴播表 spring.shardingsphere.sharding.broadcast-tables=ad_config #ad_config表id⽣成策略 spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE #绑定表 product_order,product_order_item spring.shardingsphere.sharding.binding‐tables[0] =product_order,product_order_item