sharding-jdbc属于ShardingSphere的一员,定位为轻量级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。
sharding-jdbc数据分片工作原理
核心由 SQL解析 => 执行器优化=> SQL路由 => SQL改写 => SQL执行 => 结果归并 的流程组成。
sql解析
分为词法解析和语法解析。 先通过词法解析器将SQL拆分为一个个不可再分的单词。再使用语
法解析器对SQL进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、
分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。
执行器优化
合并和优化分片条件,如OR等
sql路由
根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。
sql改写
将SQL改写为在真实数据库中可以正确执行的语句。SQL改写分为正确性改写和优化改写
sql执行
通过多线程执行器异步执行
结果归并
将多个执行结果集归并以便于通过统一的JDBC接口输出。结果归并包括流式归并、内存归并和
使用装饰者模式的追加归并这几种方式
sharding-jdbc读写分离
springboot2.x + mybatis + sharding-jdbc
手动配置方式
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
配置数据源
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.minIdle:10}")
private int minIdle;
@Value("${spring.datasource.maxActive:50}")
private int maxActive;
@Value("${spring.datasource.maxLifetime}")
private int maxLifetime;
@Value("${spring.datasource.idleTimeout}")
private int idleTimeout;
@Value("${spring.datasource0.url}")
private String url0;
@Value("${spring.datasource0.username}")
private String username0;
@Value("${spring.datasource0.password}")
private String password0;
@Value("${spring.datasource0.driverClassName}")
private String driverClassName0;
@Value("${spring.datasource1.url}")
private String url1;
@Value("${spring.datasource1.username}")
private String username1;
@Value("${spring.datasource1.password}")
private String password1;
@Value("${spring.datasource1.driverClassName}")
private String driverClassName1;
@Autowired
private CustomerDataSourceFactory customerDataSourceFactory;
@Bean("dataSource0")
public DataSource dataSource0() {
return initDataSource(url0,username0,password0,driverClassName0);
}
@Bean("dataSource1")
public DataSource dataSource1() {
return initDataSource(url1,username1,password1,driverClassName1);
}
@Bean(name = "shardingDataSource")
public DataSource shardingDataSource(@Qualifier("dataSource0") DataSource dataSource0,
@Qualifier("dataSource1") DataSource dataSource1) throws SQLException {
Map<String,DataSource> map= new HashMap<>();
map.put("ds_master",dataSource0);
map.put("ds_slave",dataSource1);
return customerDataSourceFactory.createDataSource(map);
}
private DataSource initDataSource(String url,String username,String password, String driverClassName) {
HikariDataSource datasource = new HikariDataSource();
datasource.setJdbcUrl(url);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setMaximumPoolSize(maxActive);
datasource.setMinimumIdle(minIdle);
datasource.setMaxLifetime(maxLifetime);
datasource.setIdleTimeout(idleTimeout);
datasource.setConnectionTestQuery("select 1");
return datasource;
}
}
@Component
public class CustomerDataSourceFactory {
public DataSource createDataSource(Map<String,DataSource> map ) throws SQLException {
MasterSlaveRuleConfiguration masterSlaveRuleConfiguration = new MasterSlaveRuleConfiguration("ds_master_slave","ds_master", Lists.newArrayList("ds_slave"));
Properties properties = new Properties();
properties.setProperty(ShardingPropertiesConstant.SQL_SHOW.getKey(),
String.valueOf(true));
properties.setProperty(ShardingPropertiesConstant.MAX_CONNECTIONS_SIZE_PER_QUERY.getKey(),
String.valueOf(200));
return MasterSlaveDataSourceFactory.createDataSource(map, masterSlaveRuleConfiguration,
properties);
}
}
mybatis配置
@Configuration
@MapperScan(basePackages = "com.example.dao" ,sqlSessionFactoryRef="shardingSqlSessionFactory" )
@EnableTransactionManagement
public class ShardingMyBatisConfig {
@Autowired
@Qualifier("shardingDataSource")
private DataSource dataSource;
@Bean("shardingSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setTypeAliasesPackage("com.example.entity");
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
return bean.getObject();
}
@Bean("platformTransactionManager")
public PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(dataSource);
}
}
springboot-starter集成
maven依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<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>${mybatis-spring-boot-starter.version}</version>
</dependency>
application.yml配置
server:
port: 8085
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3339/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
ds1:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3340/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
masterslave:
load-balance-algorithm-type: round_robin
name: ms
master-data-source-name: ds0
slave-data-source-names: ds1
props:
sql.show: true
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
启动类上加上mapper扫描路径及开启事务
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("com.example.dao")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在例子中我们使用了mybatis-starter,所以无需配SqlSessionFactory等,如果手动整合则直接通过注入的方式即可使用DataSource,或者将DataSource配置在JPA、Hibernate或MyBatis中使用。
@Resource
private DataSource dataSource;
读写分离配置项说明
masterSlaveRule:
name: #读写分离数据源名称,自定义
masterDataSourceName: #主库数据源名称,数据源处定义的数据源名
slaveDataSourceNames: #从库数据源名称列表,数据源处定义的数据源名
- <data_source_name1>
- <data_source_name2>
- <data_source_name_x>
loadBalanceAlgorithmClassName: #从库负载均衡算法类名称。该类需实现MasterSlaveLoadBalanceAlgorithm接口且提供无参数构造器
loadBalanceAlgorithmType: #从库负载均衡算法类型,可选值:ROUND_ROBIN,RANDOM。`loadBalanceAlgorithmClassName`存在则忽略该配置
sharding-jdbc 可配置项参数说明
props:
sql.show: #是否开启SQL显示,默认值: false
acceptor.size: # accept连接的线程数量,默认为cpu核数2倍
executor.size: #工作线程数量最大,默认值: 无限制
max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1
check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false
proxy.frontend.flush.threshold: # proxy的服务时候,对于单个大查询,每多少个网络包返回一次
proxy.transaction.type: # 默认LOCAL,proxy的事务模型 允许LOCAL,XA,BASE三个值,LOCAL无分布式事务,XA则是采用atomikos实现的分布式事务 BASE目前尚未实现
proxy.opentracing.enabled: # 是否启用opentracing
proxy.backend.use.nio: # 是否采用netty的NIO机制连接后端数据库,默认False ,使用epoll机制
proxy.backend.max.connections: # 使用NIO而非epoll的话,proxy后台连接每个netty客户端允许的最大连接数量(注意不是数据库连接限制) 默认为8
proxy.backend.connection.timeout.seconds: #使用nio而非epoll的话,proxy后台连接的超时时间,默认60s
sharind-jdbc分库分表
分片维度配置
对于分片策略存有数据源分片策略和表分片策略两种维度。两种策略的API完全相同。
- 数据源分片策略
对应于DatabaseShardingStrategy。用于配置数据被分配的目标数据源。 - 表分片策略
对应于TableShardingStrategy。用于配置数据被分配的目标表,该目标表存在与该数据的目标数据源内。故表分片策略是依赖与数据源分片策略的结果的。
分片策略
包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。
- 标准分片策略
对应StandardShardingStrategy。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置
RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。 - 复合分片策略
对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。 - 行表达式分片策略
对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0 到 t_user_7 。 - Hint分片策略
对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。 - 不分片策略
对应NoneShardingStrategy。不分片的策略。
行表达式语法说明
行表达式的使用非常直观,只需要在配置中使用 ${ expression } 或 $->{ expression } 标识行表达式即可。 目前支持数据节点和分片算法这两个部分的配置。行表达式的内容使用的是Groovy的语法,Groovy能够支持的所有操作,行表达式均能够支持。例如:
- ${begin..end} 表示范围区间
- ${[unit1, unit2, unit_x]} 表示枚举值
- 行表达式中如果出现连续多个 ${ expression } 或 $->{ expression } 表达式,整个表达式最终的结果将会根据每个子表达式的结果进行笛卡尔组合。
分布式主键
ShardingSphere不仅提供了内置的分布式主键生成器,例如UUID、SNOWFLAKE、LEAF(进行中),还抽离出分布式主键生成器的接口,方便用户自行实现自定义的自增主键生成器。
配置项说明
defaultKeyGenerator: #默认的主键生成算法 如果没有设置,默认为SNOWFLAKE算法
column: # 自增键对应的列名称
type: #自增键的类型,主要用于调用内置的主键生成算法有三个可用值:SNOWFLAKE(时间戳+worker id+自增id),UUID(java.util.UUID类生成的随机UUID),LEAF,其中Snowflake算法与UUID算法已经实现,LEAF目前尚未实现
props:
# 定制算法需要设置的参数,比如SNOWFLAKE算法的worker.id与max.tolerate.time.difference.milliseconds
自定义主键生成策略
第一步:添加策略类
public class CustomIdGenerator implements ShardingKeyGenerator {
@Override
public Comparable<?> generateKey() {
return UUID.randomUUID().toString().replace("-","");
}
@Override
public String getType() {
return "CUSTOM";
}
@Override
public Properties getProperties() {
return null;
}
@Override
public void setProperties(Properties properties) {
}
}
第二步:配置spi
第三步:通过手动配置或者在yml文件中配置使用
绑定表
指分片规则一致的主表和子表。例如: t_order 表和 t_order_item 表,均按照 order_id 分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
配置示例
bindingTables: # 绑定表,也就是实际上哪些配置的sharidng表规则需要实际生效的列表,配置为yaml列表,并且允许单个条目中以逗号切割,所配置表必须已经配置为逻辑表
- sharding_t1
- sharding_t2,sharding_t3
广播表
指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表
配置实例
broadcastTables: # 广播表 这里配置的表列表,对于发生的所有数据变更,都会不经sharidng处理,而是直接发送到所有数据节点,注意此处为列表,每个项目为一个表名称
- broad_1
- broad_2
手动配置方式
数据源配置
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.minIdle:10}")
private int minIdle;
@Value("${spring.datasource.maxActive:50}")
private int maxActive;
@Value("${spring.datasource.maxLifetime}")
private int maxLifetime;
@Value("${spring.datasource.idleTimeout}")
private int idleTimeout;
@Value("${spring.datasource0.url}")
private String url0;
@Value("${spring.datasource0.username}")
private String username0;
@Value("${spring.datasource0.password}")
private String password0;
@Value("${spring.datasource0.driverClassName}")
private String driverClassName0;
@Value("${spring.datasource1.url}")
private String url1;
@Value("${spring.datasource1.username}")
private String username1;
@Value("${spring.datasource1.password}")
private String password1;
@Value("${spring.datasource1.driverClassName}")
private String driverClassName1;
@Autowired
private CustomerDataSourceFactory customerDataSourceFactory;
@Bean("dataSource0")
public DataSource dataSource0() {
return initDataSource(url0,username0,password0,driverClassName0);
}
@Bean("dataSource1")
public DataSource dataSource1() {
return initDataSource(url1,username1,password1,driverClassName1);
}
@Bean(name = "shardingDataSource")
public DataSource shardingDataSource(@Qualifier("dataSource0") DataSource dataSource0,
@Qualifier("dataSource1") DataSource dataSource1) throws SQLException {
List<DataSource> dataSourceList = new ArrayList<>();
dataSourceList.add(dataSource0);
dataSourceList.add(dataSource1);
return customerDataSourceFactory.createDataSource(dataSourceList);
}
private DataSource initDataSource(String url,String username,String password, String driverClassName) {
HikariDataSource datasource = new HikariDataSource();
datasource.setJdbcUrl(url);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setMaximumPoolSize(maxActive);
datasource.setMinimumIdle(minIdle);
datasource.setMaxLifetime(maxLifetime);
datasource.setIdleTimeout(idleTimeout);
datasource.setConnectionTestQuery("select 1");
return datasource;
}
}
分库分表规则配置
@Component
public class CustomerDataSourceFactory {
public DataSource createDataSource(List<DataSource> dataSourceList) throws SQLException {
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
/**user表分表配置**/
TableRuleConfiguration userTableRuleConfig = new TableRuleConfiguration("t_user","db0.t_user_0,db0.t_user_1");
userTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("CUSTOM","user_id"));
userTableRuleConfig.setDatabaseShardingStrategyConfig(new NoneShardingStrategyConfiguration());
userTableRuleConfig.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id",new UserTableAlgoritm()));
shardingRuleConfig.getTableRuleConfigs().add(userTableRuleConfig);
/**order表分库分表配置**/
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration(
"t_order", "db0.t_order_2020_0,db0.t_order_2020_1,db0.t_order_2019_0,db0.t_order_2019_1,db1.t_order_2019_0,db1.t_order_2019_1,db1.t_order_2020_0,db1.t_order_2020_1");
orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE","order_id"));
// 数据库分片配置
StandardShardingStrategyConfiguration dataSourceStrategyConfiguration = new StandardShardingStrategyConfiguration(
"addr", new OrderDataSourcePreciseShardingAlgorithm());
orderTableRuleConfig.setDatabaseShardingStrategyConfig(dataSourceStrategyConfiguration);
orderTableRuleConfig.setTableShardingStrategyConfig(new ComplexShardingStrategyConfiguration(
"order_year,user_id", new OrderTableComplexKeyAlgorithm()));
shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);
Properties properties = new Properties();
properties.setProperty(ShardingPropertiesConstant.SQL_SHOW.getKey(),
String.valueOf(true));
properties.setProperty(ShardingPropertiesConstant.MAX_CONNECTIONS_SIZE_PER_QUERY.getKey(),
String.valueOf(200));
Map<String, DataSource> dataSourceMap = new HashMap<>();
for (int i = 0; i < dataSourceList.size(); i++) {
dataSourceMap.put("db" + String.valueOf(i), dataSourceList.get(i));
}
return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig,
properties);
}
}
这里分别对user和order进行可分库分表,其中user单分片键只分表不分库,order表分库采用复合分片策略,分表采用单分片键。
对应的分库分表策略如下
@Slf4j
public class UserTableAlgoritm implements PreciseShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
log.info("availableTargetNames={},shardingValue={}", ObjectToStrUtils.JSONString(availableTargetNames),ObjectToStrUtils.JSONString(shardingValue));
String value = shardingValue.getValue();
List<String> list = Lists.newArrayList(availableTargetNames);
return list.get(value.hashCode()%list.size());
}
}
@Slf4j
public class OrderTableComplexKeyAlgorithm implements ComplexKeysShardingAlgorithm<String> {
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValue) {
List<String> list = new ArrayList<>();
log.info("availableTargetNames={},shardingValue={}",availableTargetNames,shardingValue);
Map<String, Collection<String>> columnNameAndShardingValuesMap = shardingValue.getColumnNameAndShardingValuesMap();
if(columnNameAndShardingValuesMap.containsKey("order_year")){
Collection<String> orderYear = columnNameAndShardingValuesMap.get("order_year");
String address = orderYear.iterator().next();
list = availableTargetNames.stream().filter(t -> t.contains(address)).collect(Collectors.toList());
}
if(columnNameAndShardingValuesMap.containsKey("user_id")){
Collection<String> userId = columnNameAndShardingValuesMap.get("user_id");
String stringUserId = userId.iterator().next();
String index = String.valueOf(stringUserId.hashCode()%list.size());
list = list.stream().filter(t -> t.endsWith(index)).collect(Collectors.toList());
}
log.info("actual table :{}",list);
return list;
}
}
@Slf4j
public class OrderDataSourcePreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
public String doSharding(Collection<String> availableDataSourceNames, PreciseShardingValue<String> shardingValue) {
log.info("availableDataSourceNames={},shardingValue={}",availableDataSourceNames,shardingValue);
String value = shardingValue.getValue();
Iterator<String> iterator = availableDataSourceNames.iterator();
List<String> list = Lists.newArrayList(availableDataSourceNames);
int index = value.hashCode()%list.size();
return list.get(index);
}
}
springboot-starter配置读写分离+分库分表
spring:
shardingsphere:
datasource:
names: db0_master,db0_slave,db1_master,db1_slave
db0_master:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3339/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
db0_slave:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3341/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
db1_master:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3340/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
db1_slave:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3342/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
sharding:
tables:
t_order:
actual-data-nodes: db0.t_order_2020_0,db0.t_order_2020_1,db0.t_order_2019_0,db0.t_order_2019_1,db1.t_order_2019_0,db1.t_order_2019_1,db1.t_order_2020_0,db1.t_order_2020_1
database-strategy:
standard:
shardingColumn: addr
preciseAlgorithmClassName: com.example.config.OrderDataSourcePreciseShardingAlgorithm
table-strategy:
complex:
shardingColumns: order_year,user_id
algorithmClassName: com.example.config.OrderTableComplexKeyAlgorithm
keyGenerator:
column: order_id
master-slave-rules:
db0:
master-data-source-name: db0_master
slave-data-source-names: db0_slave
loadBalanceAlgorithmType: ROUND_ROBIN
db1:
master-data-source-name: db1_master
slave-data-source-names: db1_slave
loadBalanceAlgorithmType: ROUND_ROBIN
props:
sql.show: true
default-key-generator:
type: SNOWFLAKE
props:
work.id: 100
需要注意的是分片时的数据源是读写分离中定义的集群名。
参考文档
官方文档:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/
源码地址:https://github.com/apache/shardingsphere/tree/4.1.0/examples/sharding-jdbc-example/sharding-example