---
title: "04-分库分表-订单服务"
created: 2025-12-10
aliases:
- 分库分表-订单服务
tags:
- 项目
---
# 分库分表-订单服务
## 配置
引入 ShardingSphere 的相关依赖
```xml
5.3.2
org.apache.shardingsphere
shardingsphere-jdbc-core
${shardingsphere.version}
logback-classic
ch.qos.logback
```
## 根据规则进行分库分表的规则配置
节目项目相关配置:
```yaml
spring:
datasource:
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:shardingsphere-order.yaml
```
shardingsphere-order.yaml配置:
```yaml
dataSources:
# 第一个订单库
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/damai_order_0?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
username: root
password: root
# 第二个订单库
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/damai_order_1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
username: root
password: root
rules:
- !SHARDING
tables:
# 对d_order表进行分库分表
d_order:
# 库为damai_order_0 damai_order_1 表为d_order_0 至 d_order_3
actualDataNodes: ds_${0..1}.d_order_${0..3}
# 分库策略
databaseStrategy:
complex:
# 使用order_number,user_id作为分片键
shardingColumns: order_number,user_id
# 使用order_number,user_id分库算法
shardingAlgorithmName: databaseOrderComplexGeneArithmetic
# 分表策略
tableStrategy:
complex:
# 使用order_number,user_id作为分片键
shardingColumns: order_number,user_id
# 使用order_number,user_id分表算法
shardingAlgorithmName: tableOrderComplexGeneArithmetic
# 对d_order_ticket_user表进行分库分表
d_order_ticket_user:
# 库为damai_order_0 damai_order_1 表为d_order_ticket_user_0 至 d_order_ticket_user_3
actualDataNodes: ds_${0..1}.d_order_ticket_user_${0..3}
# 分库策略
databaseStrategy:
complex:
# 使用order_number,user_id作为分片键
shardingColumns: order_number,user_id
# 使用order_number,user_id分库算法
shardingAlgorithmName: databaseOrderTicketUserComplexGeneArithmetic
# 分表策略
tableStrategy:
complex:
# 使用order_number,user_id作为分片键
shardingColumns: order_number,user_id
# 使用order_number,user_id分表算法
shardingAlgorithmName: tableOrderTicketUserComplexGeneArithmetic
# 绑定表
bindingTables:
- d_order,d_order_ticket_user
# 具体的算法
shardingAlgorithms:
# d_order表分库算法
databaseOrderComplexGeneArithmetic:
# 通过自定义实现类实现分库算法
type: CLASS_BASED
props:
# 分库数量
sharding-count: 2
# 分表数量
table-sharding-count: 4
# 分库策略,复合多分片
strategy: complex
# 具体的分库逻辑在此自定义类中
algorithmClassName: com.damai.shardingsphere.DatabaseOrderComplexGeneArithmetic
# d_order表分表算法
tableOrderComplexGeneArithmetic:
# 通过自定义实现类实现分表算法
type: CLASS_BASED
props:
# 分表数量
sharding-count: 4
# 分表策略,复合多分片
strategy: complex
# 具体的分表逻辑在此自定义类中
algorithmClassName: com.damai.shardingsphere.TableOrderComplexGeneArithmetic
# d_order_ticket_user表分库算法
databaseOrderTicketUserComplexGeneArithmetic:
# 通过自定义实现类实现分库算法
type: CLASS_BASED
props:
# 分库数量
sharding-count: 2
# 分表数量
table-sharding-count: 4
# 分库策略,复合多分片
strategy: complex
# 具体的分库逻辑在此自定义类中
algorithmClassName: com.damai.shardingsphere.DatabaseOrderComplexGeneArithmetic
# d_order_ticket_user表分表算法
tableOrderTicketUserComplexGeneArithmetic:
# 通过自定义实现类实现分表算法
type: CLASS_BASED
props:
# 分表数量
sharding-count: 4
# 分表策略,复合多分片
strategy: complex
# 具体的分表逻辑在此自定义类中
algorithmClassName: com.damai.shardingsphere.TableOrderComplexGeneArithmetic
props:
# 打印真实sql
sql-show: true
```
## 总结
- d\_order表的分库分表都使用了order\_number,user\_id这两个字段一起作为分片键,并自定义了复合多分片类型的分库算法、分表算法
- d\_order\_ticket\_user表的分库分表都使用了order\_number,user\_id这两个字段一起作为分片键,并自定义了复合多分片类型的分库算法、分表算法
- d\_order和d\_order\_ticket\_user指定了绑定表的关系
- d\_order和d\_order\_ticket\_user的自定义分库算法的实现类是com.damai.shardingsphere.DatabaseOrderComplexGeneArithmetic
- d\_order和d\_order\_ticket\_user的自定义分表算法的实现类是com.damai.shardingsphere.TableOrderComplexGeneArithmetic
在订单服务中,没有使用附属表路由的方式,而是使用了分片基因法来进行分库分表,无需额外的数据表路由,保证了执行的高效,建议小伙伴先学习分片基因法,再回来继续学习本文,跳转文档地址:
[[07-技术精华-解锁分库分表新姿势:基因法完全解读|技术精华-解锁分库分表新姿势:基因法完全解读]]
当理解了基因法后,我们开始介绍订单服务的分片算法。为了便于理解,我们先从自定义分表算法开始介绍
## 自定义分表算法
```java
public class TableOrderComplexGeneArithmetic implements ComplexKeysShardingAlgorithm {
/**
* 属性分表名
* */
private static final String SHARDING_COUNT_KEY_NAME = "sharding-count";
/**
* 分表数量
* */
private int shardingCount;
@Override
public void init(Properties props) {
shardingCount = Integer.parseInt(props.getProperty(SHARDING_COUNT_KEY_NAME));
}
@Override
public Collection doSharding(Collection allActualSplitTableNames, ComplexKeysShardingValue complexKeysShardingValue) {
//返回的真实表名集合
List actualTableNames = new ArrayList<>(allActualSplitTableNames.size());
//逻辑表名
String logicTableName = complexKeysShardingValue.getLogicTableName();
//查询中的列名和值
Map> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
//如果没有条件查询,那么就查所有的分表
if (CollectionUtil.isEmpty(columnNameAndShardingValuesMap)) {
return actualTableNames;
}
//order_number条件的值
Collection orderNumberValues = columnNameAndShardingValuesMap.get("order_number");
//user_id条件的值
Collection userIdValues = columnNameAndShardingValuesMap.get("user_id");
//分片键的值
Long value = null;
//如果是order_number查询
if (CollectionUtil.isNotEmpty(orderNumberValues)) {
value = orderNumberValues.stream().findFirst().orElseThrow(() -> new DaMaiFrameException(BaseCode.ORDER_NUMBER_NOT_EXIST));
//如果是user_id查询
} else if (CollectionUtil.isNotEmpty(userIdValues)) {
value = userIdValues.stream().findFirst().orElseThrow(() -> new DaMaiFrameException(BaseCode.USER_ID_NOT_EXIST));
}
//如果order_number或者user_id的值存在
if (Objects.nonNull(value)) {
//逻辑表名_分片键的值对分表数量进行取模
actualTableNames.add(logicTableName + "_" + ((shardingCount - 1) & value));
return actualTableNames;
}
//如果没有分片键查询,则把所有真实表返回
return allActualSplitTableNames;
}
}
```
## 总结
- 先获取到逻辑表名,也就是开发中sql的表名,比如d\_order,用于后续真实表的构成
- 从complexKeysShardingValue获取到columnNameAndShardingValuesMap,key为String 要查询或者操作数据的字段名,value为Collection 为具体的值
- 分别从columnNameAndShardingValuesMap分别取order\_number和user\_id的值,哪个存在就用哪个值
- 用取得值的对shardingCount分表数量进行取模,经过分片基因法替换后订单编号取模后得值,和使用用户id取模后的值是相同的,
- 获得值就是定位到分片的索引位置
- 将逻辑表和获取的索引值进行拼接成真实的表名
- 将真实的表名返回
- 如果操作中的条件没有order\_number或者user\_id的分片键,那么把所有的真实表返回,进行全路由读取
这里在对分表数量进行取模时是使用了HashMap中的思想,这样执行起来效率更高
```java
actualTableNames.add(logicTableName + "_" + ((shardingCount - 1) & value));
```
shardingCount需要是2的n方
如果小伙伴不能理解的话,可以替换成正常的取模操作,获得的结果都是相同的
```java
actualTableNames.add(logicTableName + "_" + value % shardingCount);
```
自定义分表算法介绍完后,接下来介绍分库算法
## 自定义分库算法
```java
public class DatabaseOrderComplexGeneArithmetic implements ComplexKeysShardingAlgorithm {
/**
* 属性分库名
* */
private static final String SHARDING_COUNT_KEY_NAME = "sharding-count";
/**
* 属性分表名
* */
private static final String TABLE_SHARDING_COUNT_KEY_NAME = "table-sharding-count";
/**
* 分库数量
* */
private int shardingCount;
/**
* 分表数量
* */
private int tableShardingCount;
@Override
public void init(Properties props) {
this.shardingCount = Integer.parseInt(props.getProperty(SHARDING_COUNT_KEY_NAME));
this.tableShardingCount = Integer.parseInt(props.getProperty(TABLE_SHARDING_COUNT_KEY_NAME));
}
@Override
public Collection doSharding(Collection allActualSplitDatabaseNames, ComplexKeysShardingValue complexKeysShardingValue) {
//返回的真实库名集合
List actualDatabaseNames = new ArrayList<>(allActualSplitDatabaseNames.size());
//查询中的列名和值
Map> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
//如果没有条件查询,那么就查所有的分表
if (CollectionUtil.isEmpty(columnNameAndShardingValuesMap)) {
return allActualSplitDatabaseNames;
}
//order_number条件的值
Collection orderNumberValues = columnNameAndShardingValuesMap.get("order_number");
//user_id条件的值
Collection userIdValues = columnNameAndShardingValuesMap.get("user_id");
Long value = null;
//如果是order_number查询
if (CollectionUtil.isNotEmpty(orderNumberValues)) {
value = orderNumberValues.stream().findFirst().orElseThrow(() -> new DaMaiFrameException(BaseCode.ORDER_NUMBER_NOT_EXIST));
//如果是user_id查询
} else if (CollectionUtil.isNotEmpty(userIdValues)) {
value = userIdValues.stream().findFirst().orElseThrow(() -> new DaMaiFrameException(BaseCode.USER_ID_NOT_EXIST));
}
//如果order_number或者user_id的值存在
if (Objects.nonNull(value)) {
//获得值后再获得实际的分库的索引
long databaseIndex = calculateDatabaseIndex(shardingCount,value,tableShardingCount);
String databaseIndexStr = String.valueOf(databaseIndex);
for (String actualSplitDatabaseName : allActualSplitDatabaseNames) {
//将所有的分库名和得到的分库索引进行匹配
if (actualSplitDatabaseName.contains(databaseIndexStr)) {
actualDatabaseNames.add(actualSplitDatabaseName);
break;
}
}
return actualDatabaseNames;
}else {
//如果没有分片键查询,则把所有真实库返回
return allActualSplitDatabaseNames;
}
}
/**
* 计算给定表索引应分配到的数据库编号。
*
* @param databaseCount 数据库总数
* @param splicingKey 分片键
* @param tableCount 表总数
* @return 分配到的数据库编号
*/
public long calculateDatabaseIndex(Integer databaseCount, Long splicingKey, Integer tableCount) {
// 将分片键换成二进制字符串
String splicingKeyBinary = Long.toBinaryString(splicingKey);
//找到被替换基因的长度
long replacementLength = log2N(tableCount);
//截取获得基因
String geneBinaryStr = splicingKeyBinary.substring(splicingKeyBinary.length() - (int) replacementLength);
if (StringUtil.isNotEmpty(geneBinaryStr)) {
int h;
//对基因的hashCode进行优化,使其分布更加均匀
int geneOptimizeHashCode = (h = geneBinaryStr.hashCode()) ^ (h >>> 16);
//进行取模
return (databaseCount - 1) & geneOptimizeHashCode;
}
throw new DaMaiFrameException(BaseCode.NOT_FOUND_GENE);
}
public long log2N(long count) {
return (long)(Math.log(count)/ Math.log(2));
}
}
```
(h=geneBinaryStr.hashCode()) ^ (h >>> 16) Java HashMap 中的 哈希值扰动函数
的操作,将哈希值的高位与低位混合,减少哈希冲突的概率。
## 总结
- 开始的步骤和自定义分表的算法逻辑是相同的,获取到分表中的索引值
- 执行calculateDatabaseIndex方法通过分库数量,分片表的索引,分表数量来获得分片库的索引
下面来介绍calculateDatabaseIndex这个算法
1. 首先将分片键(订单编号或者用户id)转换成二进制字符串
2. 找到被替换基因的长度,这里计算tableCount分表数量的log2n对数
3. 获取到长度后,根据长度开始截取二进制的分片键,获得被替换后的基因
4. 对截取到的基因字符串的hashcode进行优化,使其分布更加均匀
5. 对分库数量进行取模
这里的hashcode优化
```java
int geneOptimizeHashCode = (h = geneBinaryStr.hashCode()) ^ (h >>> 16);
```
哈希值扰动函数(Hash Perturbation Function),通过 右移 16 位并异或 的操作,将哈希值的高位与低位混合,减少哈希冲突的概率。
以及对分库数量进行取模
```text
(databaseCount - 1) & geneOptimizeHashCode;
```
是使用了HashMap中的源码,执行起来效率更高
除运算会消耗cpu的多个时钟周期,与运算cpu通常在一个时钟周期内就能完成。
---
**企业级项目导航**:⬅️ [[03-分库分表-节目服务|03-分库分表-节目服务]] | 04-分库分表-订单服务 | ➡️ [[05-技术精华-全面剖析分库分表|05-技术精华-全面剖析分库分表]]