概览
认识ShardingSphere
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由ShardingJDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。
他们均提
供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容
器、云原生等各种多样化的应用场景。
官网地址: https://shardingsphere.apache.org/index_zh.html
ShardingSphere构成:
认识Sharding-JDBC
定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包
形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种
ORM框架。
适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或
直接使用JDBC。
基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和
PostgreSQL。
功能列表
数据分片
分库 & 分表
读写分离
分布式主键
分布式事务(Doing)
XA强一致事务
柔性事务
数据库治理
配置动态化
熔断 & 禁用
调用链路追踪
弹性伸缩 (Planning)
数据分片工作原理
hardingSphere的3个产品的数据分片主要流程是完全一致的。 核心由 SQL解析 => 执行器优化
=> SQL路由 => SQL改写 => SQL执行 => 结果归并 的流程组成。
**SQL解析 **
分为词法解析和语法解析。 先通过词法解析器将SQL拆分为一个个不可再分的单词。再使用语
法解析器对SQL进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、
分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。
**执行器优化 **
合并和优化分片条件,如OR等。
**SQL路由 **
根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。
**SQL改写 **
将SQL改写为在真实数据库中可以正确执行的语句。SQL改写分为正确性改写和优化改写。
**SQL执行 **
通过多线程执行器异步执行。
**结果归并 **
将多个执行结果集归并以便于通过统一的JDBC接口输出。结果归并包括流式归并、内存归并和
使用装饰者模式的追加归并这几种方式。
入门DEMO
多数据源入门
- pom配置
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
</dependencies>
- 多数据源配置
sharding:
jdbc:
datasource:
names: ds0,ds1 配置多数据源 ds0,ds1 ds0 ds1是逻辑名称真正的数据库是url连接中的
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
maxPoolSize: 50
minPoolSize: 1
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
maxPoolSize: 50
minPoolSize: 1
config:
sharding:
default-data-source-name: ds0 默认走ds0数据库
props:
sql.show: true
mybatis:
configuration:
map-underscore-to-camel-case: true
- dao代码,执行下面的addOrder()和get()默认会命中ds0-db1,因为配置了 default-data-source-name: ds0 默认走ds0数据库
@Mapper
public interface OrderDao {
//@Insert("insert into t_order(order_time,customer_id) values(#{orderTime},#{customerId})")
@Insert("insert into t_order(order_id,order_time,customer_id) values(#{orderId},now(),#{customerId})")
void addOrder(Order o);
@Select("select order_id,order_time,customer_id from t_order where order_id=#{id}")
Order get(@Param("id")Long orderId);
}
读写分离
- pom文件和上面示例多数据源入门一致, 配置数据库的主从复制 可以参考MYSQL Master-Slaves主从复制.pdf或者其他帖子
- 主从复制数据源配置
sharding:
jdbc:
datasource:
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.120.218:3306/orders?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: mike
password: Mike666!
maxPoolSize: 50
minPoolSize: 1
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.120.219:3306/orders?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: mike
password: Mike666!
maxPoolSize: 50
minPoolSize: 1
config:
sharding:
default-data-source-name: ds0
masterslave: 主从复制配置
name: ms
master-data-source-name: ds0 主数据源
slave-data-source-names: ds1 从数据源可以多个
props:
sql.show: true
mybatis:
configuration:
map-underscore-to-camel-case: true
- dao代码 配置完读写分离,进行addOrder()会命中配置的master对应的dso,进行查询会命中配置的slave对应的ds1
@Mapper
public interface OrderDao {
//@Insert(“insert into t_order(order_time,customer_id) values(#{orderTime},#{customerId})”)
@Insert(“insert into t_order(order_id,order_time,customer_id) values(#{orderId},now(),#{customerId})”)
void addOrder(Order o);
@Select(“select order_id,order_time,customer_id from t_order where order_id=#{id}”)
Order get(@Param(“id”)Long orderId);
}
简单分库分表
- pom文件和上面示例多数据源入门一致
- 数据源分库分表配置
sharding:
jdbc:
datasource:
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
maxPoolSize: 50
minPoolSize: 1
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
maxPoolSize: 50
minPoolSize: 1
config:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order$->{0..1} 库(ds0、ds1对应各自物理库)、表节点(t_order0、t_order1)
database-strategy:
inline:
sharding-column: customer_id
algorithm-expression: ds$->{customer_id % 2} 数据库选择根据customer_id字段去余,然后得到逻辑名称,根据名称映射对应库,如ds1对应db2
table-strategy:
inline:
sharding-column: order_id 在上面确定库的基础上,表选择根据order_id取余确定表
algorithm-expression: t_order$->{order_id % 2}
default-data-source-name: ds0
props:
sql.show: true
mybatis:
configuration:
map-underscore-to-camel-case: true
- dao层代码配置分库分表 请求传入参数orderId=10000&customerId=1003&orderTime=2020-09-19 ;addOrder()传入orderId=10000和customerId=1003 访问OrderDao类addOrder和get方法
@Mapper
public interface OrderDao {
1.根据分库分表路由配置,首先根据customerId%2=1,确定ds1,ds1对应db2,数据库确定之后根据表路由算法order_id % 2=0 所以最终命中
ds1.t_order0(db2.t_order0),sql打印如下
Actual SQL: ds1 ::: insert into t_order0(order_id,order_time,customer_id) values(?,?,?) ::: [[10000, 2020-09-19 00:00:00.0, 1003]]
@Insert(“insert into t_order(order_id,order_time,customer_id) values(#{orderId},now(),#{customerId})”)
void addOrder(Order o);
2.查询因为传入是orderId,库选择是根据customer_id路由的,没传入customer_id会去ds0和ds1都去查询,然后根据传入的orderId确定是t_order0查询,所以查询两次从ds0.t_order0查询一次、ds1.t_order0查询一次,sql打印如下
Actual SQL: ds0 ::: select order_id,order_time,customer_id from t_order0 where order_id=? ::: [[10000]]
Actual SQL: ds1 ::: select order_id,order_time,customer_id from t_order0 where order_id=? ::: [[10000]]
@Select(“select order_id,order_time,customer_id from t_order where order_id=#{id}”)
Order get(@Param(“id”)Long orderId);
}
standard 时间字段确定分片
-
pom引入包和以上一致
-
数据源配置,根据order_time字段自定义,2019年前放入dbo,2019后放入db1
sharding: jdbc: datasource: names: ds0,ds1 ds0: type: com.alibaba.druid.pool.DruidDataSource driver-class: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC username: root password: root maxPoolSize: 50 minPoolSize: 1 ds1: type: com.alibaba.druid.pool.DruidDataSource driver-class: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC username: root password: root maxPoolSize: 50 minPoolSize: 1 config: sharding: tables: t_order: actual-data-nodes: ds$->{0..1}.t_order$->{0..1} database-strategy: standard: sharding-column: order_time 分片库路由字段 preciseAlgorithmClassName: com.study.mike.sharding.jdbc.sharding.OrderTimePreciseShardingAlgorithm 重写PreciseShardingAlgorithm自定义路由策略,2019年前放入dbo,2019后放入db1 table-strategy: inline: sharding-column: order_id algorithm-expression: t_order$->{order_id % 2} #keyGeneratorClassName: io.shardingsphere.core.keygen.DefaultKeyGenerator default-data-source-name: ds0 props: sql.show: true mybatis: configuration: map-underscore-to-camel-case: true
3.dao层代码 传入参数orderId=10001&customerId=1004&orderTime=2018-09-19
@Mapper
public interface OrderDao {
1.根据分库分表路由配置,首先根据orderTime大于2019年路由ds0,否则ds1,因为传入2018年所以命中ds0,数据库确定之后根据表路由算法order_id % 2=01所以最终命中
ds1.t_order1(db2.t_order1),sql打印如下
Actual SQL: ds0 ::: insert into t_order1(order_id,order_time,customer_id) values(?,?,?) ::: [[10001, 2018-09-19 00:00:00.0, 1004]]
@Insert(“insert into t_order(order_id,order_time,customer_id) values(#{orderId},now(),#{customerId})”)
void addOrder(Order o);
2.get 方法和简单分库分表一致 因为没有传入分库字段 所以会两个库都会查询,查询的表根据orderId%2=1确定为t_order1
@Select(“select order_id,order_time,customer_id from t_order where order_id=#{id}”)
Order get(@Param(“id”)Long orderId);
}
4.OrderTimePreciseShardingAlgorithm
public class OrderTimePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date>{
Date[] dateRanges = new Date[2];
{
Calendar cal = Calendar.getInstance();
cal.set(2019, 1, 1, 0, 0, 0);
dateRanges[0] = cal.getTime();
cal.set(2020, 1, 1, 0, 0, 0);
dateRanges[1] = cal.getTime();
}
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {
Date d = shardingValue.getValue();
Iterator<String> ite = availableTargetNames.iterator();
String target = null;
for(Date s : dateRanges) {
target = ite.next();
if(d.before(s)) {
break;
}
}
return target;
}
}
全局唯一主键
1.pom包引入和上面一致
2.数据源配置,配置keyGeneratorColumnName、 key-generator-column-name、keyGeneratorClassName三个字段,默认是雪花算法
sharding:
jdbc:
datasource:
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
maxPoolSize: 50
minPoolSize: 1
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
maxPoolSize: 50
minPoolSize: 1
config:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order$->{0..1}
database-strategy:
standard:
sharding-column: order_time
preciseAlgorithmClassName: com.study.mike.sharding.jdbc.sharding.OrderTimePreciseShardingAlgorithm
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: t_order$->{order_id % 2}
keyGeneratorColumnName: order_id order_id当做自增主键,默认是雪花算法
key-generator-column-name: order_id
keyGeneratorClassName: io.shardingsphere.core.keygen.DefaultKeyGenerator
default-data-source-name: ds0
#type: SNOWFLAKE
props:
sql.show: true
mybatis:
configuration:
map-underscore-to-camel-case: true
3.dao 插入数据库 orderid不用传入,由shardingjdbc生成
@Insert("insert into t_order(order_time,customer_id) values(#{orderTime},#{customerId})")
void addOrder(Order o);
绑定表
指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片(路由策略一致),则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。使用bindingTables进行绑定。
绑定示例:
广播表
指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,如字典表。