# ShardingSphere分库分表
# ShardingSphere简介
Sharding就是数据分片。他的核心功能就是可以将任意数据库组合,转换成为一个分布式数据库,提供整体的数据库集群服务,本身不做数据存储,而是对其他数据库产品进行整合。
Sphere是生态的意思,这意味着ShardingSphere不是一个单独的产品或框架,而是由多个框架以及产品组成的一个完整的技术生态。目前主要的成型产品是ShardingJDBC
和SphereProxy
两个产品,以及一个用于数据迁移的子项目ElasticJob、基于公有云的云上服务OnCloud。
目前ShardingSphere已经演进到了5.X大版本,它的核心设计哲学是连接、增强、可插拔
。
# ShardingJDBC客户端分库分表
定位为轻量级Java框架,在Java的JDBC层提供额外服务,使用客户端直连数据库,以jar包形式提供服务,完全兼容JDBC和各种ORM框架。
- 适用于任何基于JDBC的ORM框架:JPA、Hibernate、Mybatis、Spring JDBC Template
- 支持任何第三方的数据库连接池:DBCP、C3P0、HikariCP等
- 支持任意实现JDBC规范的数据库:目前支持MySQL、PostgreSQL、Oracle、SQLserver等
# ShardingProxy服务端分库分表
定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。
- 对应用程序完全透明,可以直接当做MySQL/PostgreSQL使用
- 兼容MariaDB等基于MySQL协议的数据库,以及openGauss等基于Postgresql协议的数据库
- 适用于任何兼容MySQL/Postgresql协议的客户端,如:Mysql Command Client,MySQL Workbench、Navicat等。
# JDBC和Proxy对比
ShardingSphere-JDBC | ShardingSphere-Proxy | |
---|---|---|
数据库 | 任意 | MySQL/PostgreSQL |
连接消耗数 | 高 | 低 |
异构语言 | 仅Java | 任意 |
性能 | 损耗低 | 损耗略高 |
无中心化 | 是 | 否 |
静态入口 | 无 | 有 |
# 分库分表的优势
提高系统性能:将大型数据库分成多个小数据库,每个小数据库只需要处理部分数据
提高系统可用性:可以将数据复制到多个数据库
提高系统课扩展性:当数据量增加,只需要增加更多的数据库和表,而不是替换整个数据库
提高系统灵活性:可以根据数据的使用情况,对不同的数据库和表进行不同的优化
降低系统成本:分库分表可以使用更便宜的硬件和存储设备
# 分库分表的挑战
数据是应用的基础,随着数据被拆分到多个分片,应用层会面临很多新的问题。
- 主键避重问题:自增ID无法保证全局唯一,需要单独设计全局主键
- 数据备份问题:如何保持集群服务整体的稳定性
- 数据迁移问题:业务增长必然遇到的问题,当数据库集群需要扩缩容的时候,数据也要跟着迁移
- 分布式事务问题:数据分布在不同的库和服务器,不可避免会带来分布式事务问题
- SQL路由问题:执行SQL语句检索的时候,如何快速定位到目标数据所在的数据库
- 跨节点查询、归并问题:比如常见的Limit、OrderBy等等
业界目前唯一比较值得参考的详细标准,是阿里公开的开发手册中提到的诶,建议预估三年内,单表数据超过500W,或者单表数据大小超过2G,就需要考虑分库分表。
# 版本变更5.3.0
# Maven坐标
ShardingSphere 5.3.0 以前,可以直接在yml文件中配置
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-namespace</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
ShardingSphere 5.3.0 以后
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
在 5.3.0 版本以前,ShardingSphere-JDBC 同时支持 Java API、YAML、Spring Boot Starter 和 Spring Namespace 等配置方式,维护更新较为困难。
pom.xml示例
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.18</version>
<!--解决版本冲突问题-->
<exclusions>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--需要手动引入 解析yaml文件-->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!--sharding 核心库-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.4.1</version>
</dependency>
# 自定义算法
移除 Spring 模块会同时移除 AlgorithmProvided 相关类。若此前用户在自定义算法中有使用到 Bean 注入相关的逻辑,更新后将失效。对需要在算法中使用 Spring Bean 的场景,需开发者主动管理。
# 事务
移除用于支持方法级别事务声明的 @ShardingSphereTransactionType 注解。
若用户有在方法级别更改事务类型的需求,请使用Java API方式。
# 配置文件
ShardingSphere 社区决定在 ShardingSphere 5.3.0 Release 中移除 Spring 全部依赖和配置支持!!!
使用方式为在application.yml文件引入官方的sharding.yaml配置文件(resources目录下)
spring:
datasource:
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:sharding.yaml
所以建议直接学习一下官方的yaml配置文件书写格式
Sharding5.5.1文档 (opens new window)
sharding.yaml示例
# 数据源配置
dataSources:
m0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/coursedb1?serverTimezone=UTC
username: root
password: 123456
m1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/coursedb2?serverTimezone=UTC
username: root
password: 123456
# 分片规则配置
rules:
- !SHARDING
tables:
course: # 要分片的表
actualDataNodes: m$->{0..1}.course_$->{1..2} # 实际数据节点
tableStrategy: # 表分片策略
standard: # 标准分片策略,
shardingColumn: cid
shardingAlgorithmName: course_tbl_alg
databaseStrategy: # 数据库分片策略
standard: # 用于单分片键的标准分片场景
shardingColumn: cid # 分片列名称
shardingAlgorithmName: course_db_alg # 分片算法名称
keyGenerateStrategy: # 主键生成策略
column: cid
keyGeneratorName: alg_snowflake
# 表默认不分片
defaultTableStrategy:
none:
# 默认数据库不分片
defaultDatabaseStrategy:
none:
# 分片算法
shardingAlgorithms:
course_db_alg:
type: INLINE
props:
algorithm-expression: m$->{cid%2} # 分片数量
course_db_alg1:
type: MOD
props:
sharding-count: 2 # 分片数量
course_tbl_alg:
type: INLINE
props:
algorithm-expression: course_$->{cid%2+1} # 算法的行表达式
# 分布式序列算法配置
keyGenerators:
alg_snowflake:
type: SNOWFLAKE
props:
worker-id: 1
props:
sql-show: true
# 分片算法详解(补充)
# 时间范围分片
# 开发环境分库分表
# 数据源配置
dataSources:
m0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/hzw_bridge?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password:
m1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/hzw_bridge_ais?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password:
# 分片规则配置
rules:
# 加载单表
- !SINGLE
tables:
# MySQL 风格
- "*.*" # 所有表都不需要分片
# 要分片的表
- !SHARDING
tables:
ais_move: # 要分片的表
actualDataNodes: m1.ais_move_$->{202501..203012} # 实际数据节点
tableStrategy: # 表分片策略
standard: # 标准分片策略,
shardingColumn: update_time
shardingAlgorithmName: ais_sharding_alg
keyGenerateStrategy: # 主键生成策略
column: id
keyGeneratorName: alg_snowflake
# 默认数据库不分片
defaultDatabaseStrategy:
none:
# 表默认不分片
defaultTableStrategy:
none:
# 分片算法
shardingAlgorithms:
ais_sharding_alg:
type: INTERVAL
props:
datetime-pattern: "yyyy-MM-dd HH:mm:ss" # 时间格式
datetime-lower: "2025-01-01 00:00:00" # 时间分片下限
datetime-upper: "2030-12-31 23:59:59" # 时间分片上限
sharding-suffix-pattern: yyyyMM # 表后缀名
datetime-interval-unit: MONTHS # 时间片段单位
datetime-interval-amount: 1 # 时间片段数量
# 分布式序列算法配置
keyGenerators:
alg_snowflake:
type: SNOWFLAKE
props:
worker-id: 1
props:
sql-show: true
# 使用案例
# ShardingJDBC基于Mybatis单库分表
# 引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.10</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
# yml配置
spring:
shardingsphere:
datasource:
names: ds # 数据源名称,唯一标识
ds:
type: com.zaxxer.hikari.HikariDataSource # 数据源类型
driver-class-name: com.mysql.cj.jdbc.Driver # 驱动名称
jdbc-url: jdbc:mysql://localhost:3306/test_db_sharding?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
username: root
password: 123456
sharding:
tables:
tb_user: # 要分库分表的名称
actual-data-nodes: ds.tb_user_$->{0..1} # 数据节点 tb_user_0 tb_user_1
table-strategy: # 分表策略
inline: # 内联方式
sharding-column: id # 切片的列
algorithm-expression: tb_user_$->{id % 2} # 奇数在tb_user_1 偶数在tb_user_0
key-generator: # 主键生成策略
column: id # 主键列名
type: SNOWFLAKE
props:
worker: # 工作节点为123
id: 123
binding-tables: tb_user
mybatis:
type-aliases-package: #实体对象包全限定名
configuration:
cache-enabled: true
use-generated-keys: true
default-executor-type: REUSE
use-actual-param-name: true
# 注意事项
- 数据分片后,原有的数据库自动增长ID以及ORM层框架的配置不能再使用
- 使用SNOWFLAKE 雪花算法,64为LONG类型,所以需要设置相关字段为BIGINT
- 注意项目中springboot、jdk的版本号
# ShardingJDBC基于JPA单库分表
# 引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.github.wenhao</groupId>
<artifactId>jpa-spec</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
# yml配置
# sharding部分配置和前文mybatis方式相同
jpa:
open-in-view: false # 每个请求处理完毕关闭数据库连接
generate-ddl: false # 一般在开发阶段使用,根据entity生成数据库的ddl
show-sql: false # 控制台不显示sql语句
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect # 数据库方言
format_sql: true
use-new-id-generator-mappings: false # 新的主键映射策略
# 注意事项
- jpa方式要使用@Table标注表名 @Repository标注dao
# ShardingJSBD基于jpa读写分离
# 读写分离介绍
设计目标:让使用方像使用一个数据库一样使用主从数据库集群
读写分离则是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库。
读写分离可以提升系统的吞吐量和可用性,但也带来了数据不一致的问题
# 引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.github.wenhao</groupId>
<artifactId>jpa-spec</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
# yml配置
spring:
shardingsphere:
datasource:
names: master,slave0 # 定义多个数据源
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test_db_sharding_master?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
username: test
password: test
slave0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test_db_sharding_slave0?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
username: test
password: test
sharding: # 分片
tables:
tb_user:
database-strategy:
inline:
sharding-column: id
algorithm-expression: master
key-generator:
column: id
type: SNOWFLAKE
props:
worker:
id: 123
# 其他表
master-slave: #主从配置
name: ms
load-balance-algorithm-type: round_robin # 负载均衡算法为轮询
master-data-source-name: master # 主数据库名称
slave-data-source-names: slave0 # 从数据库名称
props:
sql:
show: true
jpa:
open-in-view: false
generate-ddl: false
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
format_sql: true
use-new-id-generator-mappings: false
# 注意事项
- 主库和从库的数据同步不是shardingJDBC做的,需要自行同步
- 查询操作会发生在从库,修改、添加,操作会发生在主库
原文链接 https://www.pdai.tech/md/spring/springboot/springboot-x-mysql-shardingjdbc-jpa-masterslave.html
# ShardingJDBC基于JPA的DB隔离
# 知识准备
逻辑表:水平拆分的数据库的相同逻辑和数据结构表的总称。例如订单数据有10张表,分别是t_order_0到t_order_9,逻辑表名为t_order
真实表:分片数据库中真实存在的物理表,t_order_0到t_order_9
数据节点: 数据源名称和数据表,例如ds_0.t_order_0
绑定表:分片规则一致的主表和子表,如t_order和t_order_item,均按照order_id分片
- 绑定表之间的多表关联查询不会出现笛卡尔积
广播表: 分布式计算中,将小表的副本发送到集群的每个节点,以便提高访问效率。
分片键:用于分片的数据库字段
分片算法:精确分片算法(单字段)、范围分片算法、复合分片算法(多字段)、Hint分片算法
分片策略:标准分片策略、复合分片策略、行表达式分片策略(groovy表达式)、Hint分片策略
# yml配置
shardingsphere:
datasource:
names: tenant-a,tenant-b
tenant-a:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test_db_tenant_a?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
username: root
password: 123456
tenant-b:
...
sharding:
default-database-strategy:
hint:
algorithm-class-name:
# hint算法全限定类名
tables:
tb_user:
actual-data-nodes: tenant-${['a','b']}.tb_user
key-generator:
...
tb_role:
actual-data-nodes: tenant-${['a','b']}.tb_role
key-generator:
...
tb_user_role:
actual-data-nodes: tenant-${['a','b']}.tb_user_role
key-generator:
...
binding-tables: tb_user,tb_role,tb_user_role
props:
sql:
show: true
# 使用
@Aspect
@Order(1)
@Component
public class TenantDatasourceAspect {
/**
* 定义切点.
*/
@Pointcut("execution(* tech.pdai.springboot.shardingjdbc.jpa.tenant.dbhint.dao.*.*(..))")
public void useTenantDSPointCut() {
// no impl
}
// 前置逻辑
@Before("useTenantDSPointCut()")
public void doDs0Before() {
HintManager.clear();
HintManager hintManager = HintManager.getInstance();
// 实际环境将client信息放在xxxContext中(由ThreadLocal承接),并通过client-id来获取tenant.
// 这里为了方便演示,只是使用了tenant-a
hintManager.setDatabaseShardingValue("tenant-a");
}
// 后置逻辑
@After("useTenantDSPointCut()")
public void doDs0after() {
HintManager.clear();
}
}