跳至主要內容

DB-Router

博识笔记大约 2 分钟projectmiddleware

分库分表路由组件

DB-Router

设计分库分表:

  • 路由策略 哈希散列
  • 数据源切换 AbstractRoutingDataSource
  • SQL 拦截 MyBatis Plugin

流程

应用程序加载 db-router 路由组件,加载 DataSourceAutoConfig 配置类,
该类实现 EnvironmentAware 接口重写了 setEnvironment 方法,
可读取 application.yml 中的数据源相关信息(driver、url、username、password),
将这些数据源信息进行封装,
用于配置数据源(包含了默认数据源和目标数据源,通过实现 AbstractRoutingDataSource 可以进行动态数据源切换)以及配置路由相关信息,
在执行相应的 DAO 接口时,
依据自定义注解进行切面拦截,获取到分库分表的路由字段,然后进行路由计算,
得到相应的库、表值,将指定数据库存放至 ThreadLocal 中,
(若无分表操作,则在 MyBatis Plugin 处理完成后)在进行数据源动态切换时确定数据源,至此完成分库操作,
(若有分表操作)依据自定义注解是否需要分表,在 MyBatis 拦截器中进行读取,若分表则将表名拼接到 SQL 语句中,分表操作完成。

路由计算

public class DBRouterStrategyHashCode implements IDBRouterStrategy {

    ...
    /*
        HashMap 中的散列算法:
            if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
            
        static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
        即: i = (size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16)
    */
    @Override
    public void doRouter(String dbKeyAttr) {
        int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount();

        // 扰动函数
        int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));
        
        /*
            if (!isTwoToTheNTHPower(size)) {
                idx = Math.abs(dbKeyAttr.hashCode() % (size - 1));
            }
        */
        int dbIdx = idx / dbRouterConfig.getTbCount() + 1;
        int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1);

        // 设置到 ThreadLocal;
        DBContextHolder.setDBKey(String.format("%02d", dbIdx));
        DBContextHolder.setTBKey(String.format("%03d", tbIdx));
        logger.debug("数据库路由 dbIdx:{} tbIdx:{}",  dbIdx, tbIdx);
    }
    
    private boolean isTwoToTheNTHPower(int size) {
        return (size & (size - 1)) == 0;
    }
    ...
}
关于路由算法:
    采用 HashMap 中的 `哈希散列算法` , 但需保证表的总数是 2 的 n 次幂,否则可能导致无法路由到某一库中 
    举例: 对于 3 库 4 表   </br>
        idx = key.hashCode & 1011
        导致 idx = {11, 10, 9, 8, 3, 2, 1, 0}  
        0  1  2  3   db01                       
        4  5  6  7   db02   无数据                 
        8  9  10 11  db03  
    非 2 的 n 次幂采用取模即可
    计算方式也可以是: 
        dbIdx = idx / tb + 1;
        tbIdx = idx % tb;