# 基本介绍
分库分表可以提升系统的稳定性跟负载能力,不存在单库/单表大数据。没有高并发的性能瓶颈,增加系统可用性。缺点是分库表无法join,只能通过接口方式解决,提高了系统复杂度。
# shardingsphere 数据库中间件
- Sharding-JDBC最早是当当网内部使用的分库分表框架,2017年对外开源现已更名为ShardingSphere。
- ShardingSphere是一套开源的分布式数据库中间件,由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar3款相互独立的产品组成。
- 他们均提供标准化的数据分片、读写分离、多数据副本、数据加密、影子库压测、分布式事务和数据库治理等功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
# shardingsphere 特点
- 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC
- 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL
# 主要的配置类
sharding-sphere主要的配置有三类:
MasterSlaveRuleConfiguration // 主备数据源配置
ShardingRuleConfiguration // 分片规则配置
TableRuleConfiguration // 数据表配置
# 主备数据源配置
从配置上看,符合一主多从的结构,主数据源只有一个,从数据源有多个
public final class MasterSlaveRuleConfiguration {
//名称
private final String name;
//主数据源
private final String masterDataSourceName;
//从数据源
private final Collection<String> slaveDataSourceNames;
//负载均衡算法
private MasterSlaveLoadBalanceAlgorithm loadBalanceAlgorithm;
}
MasterSlaveLoadBalanceAlgorithm算法有两种实现:
public interface MasterSlaveLoadBalanceAlgorithm {
String getDataSource(String name,
String masterDataSourceName,
List<String> slaveDataSourceNames);
}
//随机算法
public final class RandomMasterSlaveLoadBalanceAlgorithm
implements MasterSlaveLoadBalanceAlgorithm {
@Override
public String getDataSource(final String name,
final String masterDataSourceName,
final List<String> slaveDataSourceNames) {
return slaveDataSourceNames.get(new Random().nextInt(slaveDataSourceNames.size()));
}
}
//轮询算法
public final class RoundRobinMasterSlaveLoadBalanceAlgorithm
implements MasterSlaveLoadBalanceAlgorithm {
private static final ConcurrentHashMap<String, AtomicInteger> COUNT_MAP =
new ConcurrentHashMap<>();
@Override
public String getDataSource(final String name,
final String masterDataSourceName,
final List<String> slaveDataSourceNames) {
AtomicInteger count = COUNT_MAP.containsKey(name) ?
COUNT_MAP.get(name) : new AtomicInteger(0);
COUNT_MAP.putIfAbsent(name, count);
count.compareAndSet(slaveDataSourceNames.size(), 0);
return slaveDataSourceNames.get(count.getAndIncrement()
% slaveDataSourceNames.size());
}
}
# 分片规则配置
public final class ShardingRuleConfiguration {
//默认数据源名称
private String defaultDataSourceName;
//表规则配置
private Collection<TableRuleConfiguration> tableRuleConfigs = new LinkedList<>();
//相同表分片规则的组,如果表分片规则相同,则可以放在一个组里。
private Collection<String> bindingTableGroups = new LinkedList<>();
//默认数据库的分片算法配置
private ShardingStrategyConfiguration defaultDatabaseShardingStrategyConfig;
//默认表的分片算法配置
private ShardingStrategyConfiguration defaultTableShardingStrategyConfig;
//默认键的生成工具类
private KeyGenerator defaultKeyGenerator;
//主备配置信息
private Collection<MasterSlaveRuleConfiguration> masterSlaveRuleConfigs =
new LinkedList<>();
}
分片规则
public final class ShardingRule {
//分片规则配置
private final ShardingRuleConfiguration shardingRuleConfig;
//数据源信息
private final ShardingDataSourceNames shardingDataSourceNames;
//表规则
private final Collection<TableRule> tableRules = new LinkedList<>();
//相同分片规则的表规则
private final Collection<BindingTableRule> bindingTableRules = new LinkedList<>();
//数据库配置
private final ShardingStrategy defaultDatabaseShardingStrategy;
//默认表分片规则
private final ShardingStrategy defaultTableShardingStrategy;
//默认字段生成器
private final KeyGenerator defaultKeyGenerator;
//主备配置信息
private final Collection<MasterSlaveRule> masterSlaveRules = new LinkedList<>();
}
# 表配置信息
public final class TableRuleConfiguration {
//逻辑表名
private String logicTable;
//真实的数据节点名称
private String actualDataNodes;
//数据库分片算法配置
private ShardingStrategyConfiguration databaseShardingStrategyConfig;
//表分片算法配置
private ShardingStrategyConfiguration tableShardingStrategyConfig;
//自动生成键的名称
private String keyGeneratorColumnName;
//自动生成键的工具类
private KeyGenerator keyGenerator;
private String logicIndex;
}
表分片规则,logicTable为逻辑实体表的表名,DataNode为实际存储的节点表的信息
public final class TableRule {
//逻辑实体表
private final String logicTable;
//实际存储的表节点数据
private final List<DataNode> actualDataNodes;
private final ShardingStrategy databaseShardingStrategy;
private final ShardingStrategy tableShardingStrategy;
//默认生成值的表字段名称
private final String generateKeyColumn;
//生成器
private final KeyGenerator keyGenerator;
private final String logicIndex;
}
# Sharding-JDBC相关使用
# 数据分配
- 引入Maven依赖
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
- 基于Java编码的规则配置
Sharding-JDBC的分库分表通过规则配置描述,以下例子是根据user_id取模分库, 且根据order_id取模分表的两库两表的配置。
// 配置真实数据源
Map<String, DataSource> dataSourceMap = new HashMap<>();
// 配置第一个数据源
BasicDataSource dataSource1 = new BasicDataSource();
dataSource1.setDriverClassName("com.mysql.jdbc.Driver");
dataSource1.setUrl("jdbc:mysql://localhost:3306/ds0");
dataSource1.setUsername("root");
dataSource1.setPassword("");
dataSourceMap.put("ds0", dataSource1);
// 配置第二个数据源
BasicDataSource dataSource2 = new BasicDataSource();
dataSource2.setDriverClassName("com.mysql.jdbc.Driver");
dataSource2.setUrl("jdbc:mysql://localhost:3306/ds1");
dataSource2.setUsername("root");
dataSource2.setPassword("");
dataSourceMap.put("ds1", dataSource2);
// 配置Order表规则
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
orderTableRuleConfig.setLogicTable("t_order");
orderTableRuleConfig.setActualDataNodes("ds${0..1}.t_order${0..1}");
// 配置分库策略
orderTableRuleConfig.setDatabaseShardingStrategyConfig(
new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")
);
// 配置分表策略
orderTableRuleConfig.setTableShardingStrategyConfig(
new InlineShardingStrategyConfiguration("order_id", "t_order${order_id % 2}")
);
// 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);
// 省略配置order_item表规则...
// ...
// 获取数据源对象
DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap,
shardingRuleConfig,
new ConcurrentHashMap(),
new Properties());
- 基于Yaml的规则配置
dataSources:
ds0: !!org.apache.commons.dbcp.BasicDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ds0
username: root
password:
ds1: !!org.apache.commons.dbcp.BasicDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ds1
username: root
password:
tables:
t_order:
actualDataNodes: ds${0..1}.t_order${0..1}
databaseStrategy:
inline:
shardingColumn: user_id
algorithmInlineExpression: ds${user_id % 2}
tableStrategy:
inline:
shardingColumn: order_id
algorithmInlineExpression: t_order${order_id % 2}
t_order_item:
actualDataNodes: ds${0..1}.t_order_item${0..1}
databaseStrategy:
inline:
shardingColumn: user_id
algorithmInlineExpression: ds${user_id % 2}
tableStrategy:
inline:
shardingColumn: order_id
algorithmInlineExpression: t_order_item${order_id % 2}
# 读写分离
- 引入Maven依赖
<!-- for spring boot -->
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<!-- for spring namespace -->
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
- 基于Java编码的规则配置
// 配置真实数据源
Map<String, DataSource> dataSourceMap = new HashMap<>();
// 配置主库
BasicDataSource masterDataSource = new BasicDataSource();
masterDataSource.setDriverClassName("com.mysql.jdbc.Driver");
masterDataSource.setUrl("jdbc:mysql://localhost:3306/ds_master");
masterDataSource.setUsername("root");
masterDataSource.setPassword("");
dataSourceMap.put("ds_master", masterDataSource);
// 配置第一个从库
BasicDataSource slaveDataSource1 = new BasicDataSource();
slaveDataSource1.setDriverClassName("com.mysql.jdbc.Driver");
slaveDataSource1.setUrl("jdbc:mysql://localhost:3306/ds_slave0");
slaveDataSource1.setUsername("root");
slaveDataSource1.setPassword("");
dataSourceMap.put("ds_slave0", slaveDataSource1);
// 配置第二个从库
BasicDataSource slaveDataSource2 = new BasicDataSource();
slaveDataSource2.setDriverClassName("com.mysql.jdbc.Driver");
slaveDataSource2.setUrl("jdbc:mysql://localhost:3306/ds_slave1");
slaveDataSource2.setUsername("root");
slaveDataSource2.setPassword("");
dataSourceMap.put("ds_slave1", slaveDataSource2);
// 配置读写分离规则
MasterSlaveRuleConfiguration masterSlaveRuleConfig =
new MasterSlaveRuleConfiguration("ds_master_slave",
"ds_master",
Arrays.asList("ds_slave0", "ds_slave1"));
// 获取数据源对象
DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource(createDataSourceMap()
,masterSlaveRuleConfig
,new HashMap<String, Object>()
,new Properties()
);
- 基于Yaml的规则配置
dataSources:
ds_master: !!org.apache.commons.dbcp.BasicDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ds_master
username: root
password:
ds_slave0: !!org.apache.commons.dbcp.BasicDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ds_slave0
username: root
password:
ds_slave1: !!org.apache.commons.dbcp.BasicDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ds_slave1
username: root
password:
masterSlaveRule:
name: ds_ms
masterDataSourceName: ds_master
slaveDataSourceNames: [ds_slave0, ds_slave1]
props:
sql.show: true
configMap:
key1: value1
# 强制路由
ShardingSphere使用ThreadLocal管理分片键值进行Hint强制路由。可以通过编程的方式向HintManager中添加分片条件,该分片条件仅在当前线程内生效。
- 分片字段不存在SQL中、数据库表结构中,而存在于外部业务逻辑。因此,通过Hint实现外部指定分片结果进行数据操作
- 强制在主库进行某些数据操作