# 第12天:实现哈希路由策略 ## 📚 今日目标 1. 实现DBRouterStrategyHashCode类 2. 理解哈希算法的实现 3. 理解路由计算的逻辑 4. 实现DBRouterBase基类 --- ## 🛠️ 实践任务1:实现DBRouterStrategyHashCode ### 步骤1:创建DBRouterStrategyHashCode类 在 `src/main/java/cn/bugstack/middleware/db/router/strategy/impl/` 目录下创建 `DBRouterStrategyHashCode.java`: ```java package cn.bugstack.middleware.db.router.strategy.impl; import cn.bugstack.middleware.db.router.DBRouterConfig; import cn.bugstack.middleware.db.router.DBContextHolder; import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 哈希路由策略 * * 使用hashCode计算路由 * * @author 小傅哥 */ public class DBRouterStrategyHashCode implements IDBRouterStrategy { private Logger logger = LoggerFactory.getLogger(DBRouterStrategyHashCode.class); private DBRouterConfig dbRouterConfig; public DBRouterStrategyHashCode(DBRouterConfig dbRouterConfig) { this.dbRouterConfig = dbRouterConfig; } @Override public void doRouter(String dbKeyAttr) { int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount(); // 哈希计算 int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16)); // 计算库索引和表索引 int dbIdx = idx / dbRouterConfig.getTbCount() + 1; int tbIdx = idx % dbRouterConfig.getTbCount() + 1; // 设置到ThreadLocal setDBKey(dbIdx); setTBKey(tbIdx); logger.debug("数据库路由 dbIdx: {} tbIdx: {}", dbIdx, tbIdx); } @Override public void setDBKey(int dbIdx) { DBContextHolder.setDBKey(String.format("%02d", dbIdx)); } @Override public void setTBKey(int tbIdx) { DBContextHolder.setTBKey(String.format("%02d", tbIdx)); } @Override public int dbCount() { return dbRouterConfig.getDbCount(); } @Override public int tbCount() { return dbRouterConfig.getTbCount(); } @Override public void clear() { DBContextHolder.clearDBKey(); DBContextHolder.clearTBKey(); } } ``` ### 代码解释 1. **哈希计算**: ```java int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16)); ``` - 使用HashMap的哈希算法 - `hashCode ^ (hashCode >>> 16)`:扰动函数,减少哈希冲突 - `(size - 1) &`:取模运算(size必须是2的幂) 2. **计算库和表索引**: ```java int dbIdx = idx / tbCount + 1; // 库索引(从1开始) int tbIdx = idx % tbCount + 1; // 表索引(从1开始) ``` 3. **设置到ThreadLocal**: - 格式化为两位数字(01, 02, ...) - 设置到DBContextHolder --- ## 🛠️ 实践任务2:实现DBRouterBase基类 ### 步骤1:创建DBRouterBase类 在 `src/main/java/cn/bugstack/middleware/db/router/` 目录下创建 `DBRouterBase.java`: ```java package cn.bugstack.middleware.db.router; /** * 数据库路由基类 * * 用于存储表索引信息 * * @author 小傅哥 */ public class DBRouterBase { private String tbIdx; public String getTbIdx() { return tbIdx; } public void setTbIdx(String tbIdx) { this.tbIdx = tbIdx; } } ``` ### 使用场景 这个基类可以用于: - 实体类继承,自动获取表索引 - 查询条件中指定表索引 --- ## 🎓 知识点拓展 ### 拓展1:哈希算法的选择 **为什么用HashMap的哈希算法?** ```java // HashMap的哈希算法 int hash = key.hashCode() ^ (key.hashCode() >>> 16); int index = (n - 1) & hash; ``` **优点**: - ✅ 分布均匀 - ✅ 减少冲突 - ✅ 性能好 **其他哈希算法**: - MD5:安全性高,但性能差 - SHA:安全性高,但性能差 - CRC32:性能好,但分布可能不均匀 ### 拓展2:取模运算的优化 **传统取模**: ```java int index = hashCode % size; // 慢 ``` **位运算取模**(size是2的幂): ```java int index = (size - 1) & hashCode; // 快 ``` **为什么快?** - 位运算比除法快 - 但要求size必须是2的幂 ### 拓展3:哈希冲突处理 **问题**:不同的key可能计算出相同的索引 **解决方案**: 1. **开放地址法**:找下一个空位置 2. **链地址法**:用链表存储冲突的元素 3. **再哈希法**:用另一个哈希函数 **我们的项目**: - 使用哈希算法,冲突概率低 - 如果冲突,数据会分布到不同的库表 - 这是可以接受的 --- ## ✅ 今日检查清单 - [ ] 实现了DBRouterStrategyHashCode类 - [ ] 理解了哈希算法的实现 - [ ] 理解了路由计算的逻辑 - [ ] 实现了DBRouterBase基类 - [ ] 理解了哈希冲突的处理 - [ ] 完成了拓展阅读 --- ## 🎯 明日预告 明天我们将完善AOP切面: - 完善DBRouterJoinPoint类 - 处理类级别注解 - 处理异常情况 --- ## 💡 思考题 1. 为什么用HashMap的哈希算法? 2. 位运算取模为什么比除法快? 3. 如何处理哈希冲突? --- ## 📚 参考资源 - [HashMap源码分析](https://www.baeldung.com/java-hashmap) - [哈希算法原理](https://en.wikipedia.org/wiki/Hash_function) - [位运算优化](https://www.baeldung.com/java-bitwise-operators)