--- 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-节目服务与支付服务分库分表设计思维全景]]