---
title: "01-用户服务分库分表设计思维全景"
created: 2025-12-10
aliases:
- 用户服务分库分表设计思维全景
tags:
- 项目
---
# 用户服务分库分表设计思维全景
![[image-4890b528.png]]
damai\_pro\damai-server\damai-user-service\src\main\resources\shardingsphere-user-local.yaml
[官方文档](https://shardingsphere.apache.org/document/5.3.2/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/sharding/#%E8%83%8C%E6%99%AF%E4%BF%A1%E6%81%AF)
![[image-4191437e.png]]
## **一、设计思维框架**
![[分库分表设计思维-50963729.jpg]]
## **二、需求分析阶段:问对问题**
### **2.1 拿到需求后的灵魂五问**
![[用户服务需求分析-a464cf9f.jpg]]
### **2.2 用户服务的实体关系梳理**
![[用户服务实体关系分析-84e23298.jpg]]
## **三、问题建模阶段:识别核心挑战**
### **3.1 分库分表面临的通用挑战**
![[分库分表核心挑战-dd1b922a.jpg]]
### **3.2 用户服务的特有挑战**
![[用户服务痛点-9870e6ef.jpg]]
## **四、策略选择阶段:权衡利弊**
### **4.1 多维查询的三大解决策略**
![[多维查询的三大解决策略-3ef658b0.jpg]]
### **4.2 用户服务的策略选择决策树**
![[策略选择决策过程-29230dcb.jpg]]
### **4.3 购票人表的策略选择**
![[购票人表的策略选择-5da20db5.jpg]]
## **五、方案设计阶段:详细设计**
### **5.1 整体架构设计**
![[用户服务分库分表架构-d9199980.jpg]]
### **5.2 表设计与分片策略**
![[表分片策略设计-431f8a64.jpg]]
### **5.3 业务流程设计**
![[用户注册流程-5d3a4ca7.jpg]]
![[手机号登录流程-a0770da1.jpg]]
## **六、分片算法设计:避免同频共振**
### **6.1 问题复现**
![[问题复现-6cd670ce.jpg]]
### **6.2 正确设计:层级分片**
![[层级分片-4d6b4bbc.jpg]]
### **6.3 算法实现**
```java
package com.damai.sharding;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import org.apache.shardingsphere.sharding.spi.ShardingAlgorithm;
import java.util.Collection;
import java.util.Properties;
/**
* 层级分片算法 (Hierarchical Sharding Algorithm)
*
* 解决场景:当分库数量和分表数量一致(如 2库2表)且使用相同分片键时,
* 如果单纯使用 hash%2,会导致奇数落入库1表1,偶数落入库0表0,
* 造成一半的表(库0表1、库1表0)永远无数据的数据倾斜问题。
*
* 核心逻辑:
* 将分库分表视为一个整体,计算全局索引 (Global Index)。
* 假设 2库 * 2表 = 4个全局槽位 (0, 1, 2, 3)。
*
* 分库逻辑 (sharding-type=DATABASE): globalIndex / tableCount (整除) -> 0,0,1,1
* 分表逻辑 (sharding-type=TABLE): globalIndex % tableCount (取余) -> 0,1,0,1
*/
public class HierarchicalShardingAlgorithm implements StandardShardingAlgorithm> {
private int dbCount;
private int tableCount;
private String shardingType; // 标识当前是用于分库(DATABASE)还是分表(TABLE)
/**
* 初始化配置,从 YAML 的 props 中读取属性
*/
@Override
public void init(Properties props) {
// 数据库总数
this.dbCount = Integer.parseInt(props.getProperty("db-count", "2"));
// 每个库中的表总数
this.tableCount = Integer.parseInt(props.getProperty("table-count", "2"));
// 算法类型:用于区分当前是计算库路由还是表路由
this.shardingType = props.getProperty("sharding-type", "DATABASE");
}
/**
* 执行精确分片路由 (=, IN)
*/
@Override
public String doSharding(Collection availableTargetNames,
PreciseShardingValue> shardingValue) {
// 1. 计算哈希值 (使用 hashCode 并取绝对值,防止负数)
// 注意:生产环境建议对 String 使用更均匀的 Hash 算法(如 MurmurHash),这里使用 hashCode 演示
int hashCode = Math.abs(shardingValue.getValue().hashCode());
// 2. 计算全局索引 (Global Slot)
// 公式:总槽位 = 库数 * 表数
// 例如 2库2表,总槽位为 4。hash % 4 结果为 0, 1, 2, 3
int totalSlots = dbCount * tableCount;
int globalIndex = hashCode % totalSlots;
// 3. 根据类型计算目标索引 (Target Index)
int targetIndex;
if ("DATABASE".equals(shardingType)) {
// 分库策略:使用 商 (Quotient)
// 0 / 2 = 0 -> ds_0
// 1 / 2 = 0 -> ds_0
// 2 / 2 = 1 -> ds_1
// 3 / 2 = 1 -> ds_1
targetIndex = globalIndex / tableCount;
} else {
// 分表策略:使用 余数 (Remainder)
// 0 % 2 = 0 -> table_0
// 1 % 2 = 1 -> table_1
// 2 % 2 = 0 -> table_0
// 3 % 2 = 1 -> table_1
targetIndex = globalIndex % tableCount;
}
// 4. 匹配目标名称
// availableTargetNames 包含实际的节点名列表,如 ["ds_0", "ds_1"] 或 ["d_user_mobile_0", "d_user_mobile_1"]
// 我们通过后缀匹配来找到正确的目标
String suffix = String.valueOf(targetIndex);
return availableTargetNames.stream()
.filter(name -> name.endsWith(suffix)) // 匹配以 targetIndex 结尾的节点
.findFirst()
.orElseThrow(() -> new UnsupportedOperationException(
"Cannot find target node for index: " + targetIndex + ", available: " + availableTargetNames));
}
// 处理范围查询 (BETWEEN, > , <) 的逻辑,此处省略,生产环境需根据业务决定是否支持范围扫描
@Override
public Collection doSharding(Collection availableTargetNames,
org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue> shardingValue) {
// 默认返回所有节点(全路由),或者抛出异常禁止范围查询
return availableTargetNames;
}
@Override
public String getType() {
return "HIERARCHICAL";
}
}
```
## **七、完整配置方案**
```yaml
# shardingsphere-user.yaml 配置详解
# =================================================================================
# 1. 数据源配置 (Data Sources)
# 定义了底层的物理数据库实例。
# =================================================================================
dataSources:
# 第一个物理库 (Database 0)
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
# 实际连接地址,指向 damai_user_0 库
jdbcUrl: jdbc:mysql://127.0.0.1:3306/damai_user_0?...
username: xxx
password: xxx
# 第二个物理库 (Database 1)
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
# 实际连接地址,指向 damai_user_1 库
jdbcUrl: jdbc:mysql://127.0.0.1:3306/damai_user_1?...
username: xxx
password: xxx
# =================================================================================
# 2. 规则配置 (Rules)
# 包含分片规则 (!SHARDING) 和 加密规则 (!ENCRYPT)
# =================================================================================
rules:
# -------------------------------------------------------------------------------
# 2.1 分片规则 (!SHARDING)
# -------------------------------------------------------------------------------
- !SHARDING
tables:
# ========== 用户手机号映射表 (d_user_mobile) ==========
# 作用:解决使用手机号登录时的“全路由”问题,通过手机号定位到 user_id。
d_user_mobile:
# 实际数据节点:ds_0.d_user_mobile_0, ds_0.d_user_mobile_1, ds_1.d_user_mobile_0, ds_1.d_user_mobile_1
actualDataNodes: ds_${0..1}.d_user_mobile_${0..1}
# 分库策略:使用自定义的层级算法 (HIERARCHICAL)
# 解决了 Hash(mobile)%2 同时用于分库和分表导致的数据倾斜问题(一半表为空)。
databaseStrategy:
standard:
shardingColumn: mobile
shardingAlgorithmName: mobileDbAlgorithm
# 分表策略:同样使用层级算法,但参数不同 (sharding-type: TABLE)
tableStrategy:
standard:
shardingColumn: mobile
shardingAlgorithmName: mobileTableAlgorithm
# ========== 用户邮箱映射表 (d_user_email) ==========
# 作用:同上,解决使用邮箱登录时的路由问题。
d_user_email:
actualDataNodes: ds_${0..1}.d_user_email_${0..1}
# 分库策略:使用层级算法
databaseStrategy:
standard:
shardingColumn: email
shardingAlgorithmName: emailDbAlgorithm
# 分表策略:使用层级算法
tableStrategy:
standard:
shardingColumn: email
shardingAlgorithmName: emailTableAlgorithm
# ========== 用户主表 (d_user) ==========
# 核心业务表,使用 user_id 进行分片。
d_user:
actualDataNodes: ds_${0..1}.d_user_${0..1}
# 分库策略:标准取模 (MOD)
# ID 为 Long 类型数值,直接取模即可,不需要层级算法。
databaseStrategy:
standard:
shardingColumn: id
shardingAlgorithmName: userDbAlgorithm
# 分表策略:使用自定义类算法 (CLASS_BASED)
# 这里演示了如何指向一个具体的 Java 类实现分表逻辑。
tableStrategy:
standard:
shardingColumn: id
shardingAlgorithmName: userTableAlgorithm
# ========== 购票人表 (d_ticket_user) ==========
# 业务关联表,绑定在 user_id 上,确保同一用户的购票人数据和用户数据在同一个库中。
d_ticket_user:
actualDataNodes: ds_${0..1}.d_ticket_user_${0..1}
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: ticketUserDbAlgorithm
tableStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: ticketUserTableAlgorithm
# -------------------------------------------------------------------------------
# 分片算法定义 (Sharding Algorithms)
# -------------------------------------------------------------------------------
shardingAlgorithms:
# 1. 手机号 - 分库算法 (sharding-type: DATABASE)
mobileDbAlgorithm:
type: CLASS_BASED
props:
strategy: STANDARD
algorithmClassName: com.damai.sharding.HierarchicalShardingAlgorithm # 修改为实际包路径
db-count: 2
table-count: 2
sharding-type: DATABASE
# 2. 手机号 - 分表算法 (sharding-type: TABLE)
mobileTableAlgorithm:
type: CLASS_BASED
props:
strategy: STANDARD
algorithmClassName: com.damai.sharding.HierarchicalShardingAlgorithm # 修改为实际包路径
db-count: 2
table-count: 2
sharding-type: TABLE
# 3. 邮箱 - 分库算法
emailDbAlgorithm:
type: CLASS_BASED
props:
strategy: STANDARD
algorithmClassName: com.damai.sharding.HierarchicalShardingAlgorithm # 修改为实际包路径
db-count: 2
table-count: 2
sharding-type: DATABASE
# 4. 邮箱 - 分表算法
emailTableAlgorithm:
type: CLASS_BASED
props:
strategy: STANDARD
algorithmClassName: com.damai.sharding.HierarchicalShardingAlgorithm # 修改为实际包路径
db-count: 2
table-count: 2
sharding-type: TABLE
# 用户主表分库 - 标准取模
# 逻辑:id % 2
userDbAlgorithm:
type: MOD
props:
sharding-count: 2 # 分片总数
# 用户主表分表 - 基于类实现
# 指向具体的 Java 类,灵活性最高
userTableAlgorithm:
type: CLASS_BASED
props:
strategy: STANDARD
algorithmClassName: com.damai.sharding.UserTableAlgorithm
# 购票人表分库 - 标准取模 (为了和用户主表路由一致)
ticketUserDbAlgorithm:
type: MOD
props:
sharding-count: 2
# 购票人表分表 - 基于类实现
ticketUserTableAlgorithm:
type: CLASS_BASED
props:
strategy: STANDARD
algorithmClassName: com.damai.sharding.UserTableAlgorithm
# -------------------------------------------------------------------------------
# 2.2 加密规则 (!ENCRYPT)
# 确保敏感数据在数据库中以密文存储,防止拖库导致隐私泄露。
# -------------------------------------------------------------------------------
- !ENCRYPT
tables:
# 用户主表加密
d_user:
columns:
mobile:
cipherColumn: mobile
encryptorName: user_encryption_algorithm
password:
cipherColumn: password
encryptorName: user_encryption_algorithm
id_number:
cipherColumn: id_number
encryptorName: user_encryption_algorithm
# [重要新增] 用户手机映射表加密
# 必须加密!否则:1. 数据泄露风险;2. 若分片键使用密文计算,此处不加密会导致路由逻辑不一致。
d_user_mobile:
columns:
mobile:
cipherColumn: mobile
encryptorName: user_encryption_algorithm
# [重要新增] 用户邮箱映射表加密
d_user_email:
columns:
email:
cipherColumn: email
encryptorName: user_encryption_algorithm
# 加密算法定义 (此处省略了 encryptors 部分的详细配置,通常需要配合 encryptors 定义算法类型和密钥)
# =================================================================================
# 3. 属性配置 (Props)
# =================================================================================
props:
# 开发调试时开启,可在控制台打印出真实的 SQL 重写和路由结果
sql-show: true
```
## **八、设计验证清单**
![[设计验证清单-82ecf30b.jpg]]
## **九、设计思维总结**
![[设计思维总结-aa970ea2.jpg]]
---
**企业级项目导航**:⬅️ [[02-分库分表最佳实践:从基础原理到高阶架构设计|02-分库分表最佳实践:从基础原理到高阶架构设计]] | 01-用户服务分库分表设计思维全景 | ➡️ [[02-节目服务与支付服务分库分表设计思维全景|02-节目服务与支付服务分库分表设计思维全景]]