首先让我们来介绍一下Sharding-JDBC。
什么是Sharding-JDBC?
Sharding-JDBC是一个开源的分布式数据库中间件,它是为了解决关系型数据库在大数据量、高并发情况下的性能瓶颈而设计的。该项目是ShardingSphere生态圈的一部分,致力于为用户提供分布式数据库的解决方案。
它的作用是什么?
- 数据分片:Sharding-JDBC可以将数据库中的数据按照一定规则(如按照某个字段的取值范围)进行水平切分,将数据分散存储在多个数据库节点中,从而降低单个数据库的压力,提高系统的扩展性和性能。
- 读写分离:它支持将读操作和写操作分发到不同的数据库节点上,以提高数据库的读写性能。
- 路由和负载均衡:Sharding-JDBC具备强大的路由功能,能够根据SQL语句中的条件将请求路由到相应的数据库节点上,同时支持负载均衡策略,保证各个节点的负载相对均衡。
- 分布式事务支持:它提供了分布式事务的支持,可以保证分布式环境下的数据一致性和可靠性。
- 简化开发:通过Sharding-JDBC,开发者无需关心分布式数据库的复杂性,只需按照普通的数据库访问方式编写代码,即可享受到分布式数据库带来的性能提升和扩展性。
接下来通过一个main函数来分析下分库分表主体流程,
数据准备:
首先创建两个数据库和4张表,将user_id设置为分片键。每个数据库中有trade_order_0 trade_order_1 trade_order_2 trade_order_3四张表。
CREATE TABLE `trade_order_0` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`order_id` bigint NOT NULL COMMENT '订单ID',
`user_id` bigint NOT NULL DEFAULT '0' COMMENT '用户ID',
`order_status` tinyint NOT NULL DEFAULT '0' COMMENT '订单状态 1待支付 ',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`) USING BTREE
)
Demo代码:
public static void main(String[] args) throws SQLException {
//=======一、配置数据库
Map<String, DataSource> dataSourceMap = new HashMap<>(2);//为两个数据库的datasource
// 配置第一个数据源
HikariDataSource dataSource0 = new HikariDataSource();
dataSource0.setDriverClassName("com.mysql.jdbc.Driver");
dataSource0.setJdbcUrl("url");
dataSource0.setUsername("user_name");
dataSource0.setPassword("password");
dataSourceMap.put("m0", dataSource0);
// 配置第二个数据源
HikariDataSource dataSource1 = new HikariDataSource();
dataSource1.setDriverClassName("com.mysql.jdbc.Driver");
dataSource1.setJdbcUrl("url");
dataSource1.setUsername("user_name");
dataSource1.setPassword("password");
dataSourceMap.put("m1", dataSource1);
//=======二、配置分库分表策略
// 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
//真实表分布
TableRuleConfiguration courseTableRuleConfig = new TableRuleConfiguration("trade_order",
"m$->{0..1}.trade_order_$->{0..3}");
//真实库分布
courseTableRuleConfig.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id",
"m$->{user_id%2}"));
courseTableRuleConfig.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id",
"trade_order_$->{user_id%2}"));
shardingRuleConfig.getTableRuleConfigs().add(courseTableRuleConfig);
//三、配置属性值
Properties properties = new Properties();
//打开日志输出
properties.setProperty("sql.show", "true");
//K1 创建ShardingSphere的数据源 ShardingDataSource
DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, properties);
Connection conn = null;
try {
//ShardingConnectioin
conn = dataSource.getConnection();
//ShardingStatement
Statement statement = conn.createStatement();
String sql = "SELECT id,order_id,user_id,order_status from trade_order where user_id = 1493538";
//ShardingResultSet
ResultSet result = statement.executeQuery(sql);
while (result.next()) {
System.out.println("result:" + result.getInt("id"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (null != conn) {
conn.close();
}
}
}
在sharding-jdbc中重要的四个对象:
ShardingDataSource:对象内最核心的字段为
- dataSourceMap:负责管理多个数据源,这些数据源可以是主从数据源,也可以是分片数据源,每个数据源可能连接着不同的数据库实例。
- ShardingRule:分片规则,包含了数据分片的配置信息,包括哪些表需要进行分片、分片键是什么、分片算法等。它会根据配置的规则来解析 SQL,并决定将 SQL 发送到哪个具体的数据源执行。
以下是初始化数据源主要过程:
public static DataSource createDataSource(
final Map<String, DataSource> dataSourceMap, final ShardingRuleConfiguration shardingRuleConfig, final Properties props) throws SQLException {
//创建ShardingRule以及ShardingDataSource实例
return new ShardingDataSource(dataSourceMap, new ShardingRule(shardingRuleConfig, dataSourceMap.keySet()), props);
}
//解析ShardingConfig,构建ShardingRule
public ShardingRule(final ShardingRuleConfiguration shardingRuleConfig, final Collection<String> dataSourceNames) {
Preconditions.checkArgument(null != shardingRuleConfig, "ShardingRuleConfig cannot be null.");
Preconditions.checkArgument(null != dataSourceNames && !dataSourceNames.isEmpty(), "Data sources cannot be empty.");
this.ruleConfiguration = shardingRuleConfig;
//获取所有的实际数据库
shardingDataSourceNames = new ShardingDataSourceNames(shardingRuleConfig, dataSourceNames);
//表路由规则
tableRules = createTableRules(shardingRuleConfig);
//获取广播表
broadcastTables = shardingRuleConfig.getBroadcastTables();
//绑定表
bindingTableRules = createBindingTableRules(shardingRuleConfig.getBindingTableGroups());
//创建默认的分库策略
defaultDatabaseShardingStrategy = createDefaultShardingStrategy(shardingRuleConfig.getDefaultDatabaseShardingStrategyConfig());
//创建默认的分表策略
defaultTableShardingStrategy = createDefaultShardingStrategy(shardingRuleConfig.getDefaultTableShardingStrategyConfig());
//分片键
defaultShardingKeyGenerator = createDefaultKeyGenerator(shardingRuleConfig.getDefaultKeyGeneratorConfig());
//主从规则
masterSlaveRules = createMasterSlaveRules(shardingRuleConfig.getMasterSlaveRuleConfigs());
//加密规则
encryptRule = createEncryptRule(shardingRuleConfig.getEncryptRuleConfig());
}
ShardingConnection:主要作用是管理连接和事务,以及提供执行 SQL 操作的接口。
//ShardingConnection中主要字段
public ShardingConnection(final Map<String, DataSource> dataSourceMap, final ShardingRuntimeContext runtimeContext, final TransactionType transactionType) {
this.dataSourceMap = dataSourceMap;
this.runtimeContext = runtimeContext;
this.transactionType = transactionType;
shardingTransactionManager = runtimeContext.getShardingTransactionManagerEngine().getTransactionManager(transactionType);
}
ShardingStatement:主要作用是执行普通的SQL语句,并处理与之相关的操作。
- SQL 执行:ShardingStatement负责执行普通的 SQL 语句。与预处理语句不同,普通 SQL 语句是直接执行的,而不需要预先编译成模板。
- SQL 路由:在执行普通 SQL 语句时,ShardingStatement会根据分片规则对 SQL 进行路由,确定应该将 SQL 发送到哪个具体的数据源或数据节点执行。
- 结果集处理:如果 SQL 是查询语句,ShardingStatement负责处理执行结果集,并将结果返回给调用方。
在prepare方法中主要负责路由算法,重写逻辑SQL,将逻辑SQL转化为实际SQL,将真实执行sql单元保存到上下文中。
// 分片语句的执行过程
@Override
public ResultSet executeQuery(final String sql) throws SQLException {
if (Strings.isNullOrEmpty(sql)) {
throw new SQLException(SQLExceptionConstant.SQL_STRING_NULL_OR_EMPTY);
}
ResultSet result;
try {
//这个上下文中包含了实际运行所需要的全部信息。包括真实数据源、真实SQL、真实参数
//执行引擎的准备阶段
executionContext = prepare(sql);
//执行引擎的执行阶段
List<QueryResult> queryResults = statementExecutor.executeQuery();
//结果归并阶段
MergedResult mergedResult = mergeQuery(queryResults);
result = new ShardingResultSet(statementExecutor.getResultSets(), mergedResult, this, executionContext);
} finally {
currentResultSet = null;
}
currentResultSet = result;
return result;
}
ShardingResultSet:主要作用是负责处理执行查询操作后返回的结果集,以及结果集的归并。包括获取查询结果的行和列,以及获取各种数据类型的值。提供了操作结果集游标的方法,可以将游标移动到结果集的下一行或上一行,并获取当前行的数据。
public ShardingResultSet(final List<ResultSet> resultSets, final MergedResult mergeResultSet, final Statement statement, final ExecutionContext executionContext) throws SQLException {
super(resultSets, statement, executionContext);
this.mergeResultSet = mergeResultSet;
columnLabelAndIndexMap = createColumnLabelAndIndexMap(resultSets.get(0).getMetaData());
}
总结:
sharding-jdbc的主体流程与 JDBC 的主体流程有相似之处,但也有一些重要的区别。
- 获取分片数据源:应用程序通过ShardingDataSource 获取分片数据源,而不是直接获取单个数据库连接。
- SQL 解析与路由:在执行 SQL 操作时,Sharding-JDBC 会解析 SQL,并根据分片规则对 SQL 进行路由,确定应该将 SQL 发送到哪个具体的数据源或数据节点执行。
- 处理结果集:如果 SQL 查询涉及多个数据节点,每个数据节点都会返回一个单独的结果集。Sharding-JDBC 需要将这些结果集合并成一个整体的结果集,并返回给应用程序。而在 JDBC 中,通常只涉及单个数据库,因此只会返回一个结果集,无需合并。同样的对于 SQL 包含了排序或分页操作,并且涉及多个数据节点,Sharding-JDBC 需要对每个数据节点返回的结果集进行排序和分页,然后再进行结果集合并。