美文网首页
使用 zebra 对数据库表进行水平拆分

使用 zebra 对数据库表进行水平拆分

作者: DJN_ | 来源:发表于2018-12-07 11:00 被阅读0次

    zebra 是美团点评开发的数据库访问层中间件,代码维护在 GitHub:Meituan-Dianping/Zebra

    Zebra是一个基于JDBC API协议上开发出的高可用、高性能的数据库访问层解决方案,是美团点评内部使用的数据库访问层中间件。具有以下的功能点:

    • 配置集中管理,动态刷新
    • 支持读写分离、分库分表
    • 丰富的监控信息在CAT上展现
    • 异步化数据库请求,多数据源支持

    zebra 的 QuickStart 提供了完整的使用说明文档,这里就不再赘述用法,而是直接提供一个使用 zebra 进行数据库分表的 demo。

    数据库目标表结构

    数据库表为签到表(sign_in),表结构如下:
    签到表用于记录用户签到数据,CUSTOMER_ID 为用户 id,其余字段是为了 demo 测试而建,无特殊含义。

    image.png

    主维度选取

    选择签到表的 CUSTOMER_ID 作为维度。

    选取维度时需要考虑下面的情况:
    在 sql 语句中,顾客 CUSTOMER_ID 是在 CRUD 操作中作为查询目标属性和条件属性中出现次数最多的字段,这样能满足 zebra 要求中的维度必须出现在 sql 语句中的限制;同时很多查询,检索的 sql 都是基于同一顾客进行的,方便业务逻辑变更;此外,顾客 id 在签到表中与主键直接相关,一条签到记录必然有一个顾客 id ,根据顾客 id 能更快的定位签到记录所在范围。

    路由规则

    <?xml version="1.0" encoding="UTF-8"?>
    <router-rule>
        <table-shard-rule table="sign_in" generatedPK="id">
            <shard-dimension
                    dbRule="#customer_id# * 0"
                    dbIndexes="db"
                    tbRule="0 + ((4 - 1) &amp; (crc32(#customer_id#) ^ (crc32(#customer_id#) &gt;&gt;&gt; 16)))"
                    tbSuffix="everydb:[0,4]"
                    isMaster="true">
            </shard-dimension>
    
        </table-shard-rule>
    </router-rule>
    

    通过维度和路由规则定位到对应表的过程,在针对签到表的 CRUD 操作中,通过维度 CUSTOMER_ID 字段及其值,经路由规则计算后能定位到对应表。

    路由规则说明

    数据库的路由规则

    dbRule="#CUSTOMER_ID# * 0"
    单库,无需定义规则,指出 [维度] 即可。

    数据库名后缀

    dbIndexes="db"
    与zebra配置中的ShardDataSource数据源的key相同即可。

    表的路由规则

    参考 jdk8 HashMap 原理,路由规则思路如下:

    1. 主表:sign_in
    2. 维度:key
    3. 表下标偏移:offset
    4. 表数量:len
    5. 散列值:hash = crc32(key)
    6. 扰动函数:ha = (hash ^ (hash >>> 16))
    7. 取模运算:in = (len - 1) & ha
    8. 映射到的表下标:index = offset + in
    9. tbRule: offset + ((len - 1) & (crc32(key) ^ (crc32(key) >>> 16)))

    如:offset = 3,len = 2,key 为 #customer_id#
    tbRule = "3 + ((2 - 1) & (crc32(#customer_id#) ^ ((crc32(#customer_id#) >>> 16)))"
    在表sign_in3,sign_in4中路由。

    注意:

    • tbRule 格式必须严格相同
    • len只能为2的整次幂,否则路由分布不均匀,表现形式之一为插入大量数据,路由到的各个表中新增数据量分别不均匀。
    • offset 不能小于 0,即最小为 0

    说明:

    • 扰动函数:使碰撞(hash冲突)更平均,即 hash 映射到的 index 在可选 index 中被选中的概率趋于相同。
    • 取模运算:高位全部归零,只保留低位,用作下标访问
    • 表数量取2的整数次幂:长度减1,便于取模。
    表名后缀

    tbSuffix="everydb:[0,999]"
    everydb:[a,b]:a始终为0,b不小于实际表的最大index即可。

    主维度

    isMaster="true"
    只有一个维度

    测试

    部分测试 sql 如下:

        <!--记得将【维度】明确表示-->
        <insert id="insert" parameterType="SignInEntity">
            insert into sign_in (id, customer_id, date, current_sign_in_store_id, type, create_eid, create_date)
                value (#{id}, #{customerId}, #{date}, #{currentSignInStoreId}, #{type}, #{createEid}, now())
        </insert>
    
        <select id="listByCustomerId" resultType="SignInEntity">
            select *
            from sign_in
            where customer_id = #{value};
        </select>
    
        <!--Select、Update或者Delete,该SQL对所有的库和表进行执行,因为没有带维度-->
        <select id="get" resultType="SignInEntity">
            select *
            from sign_in
            where id = #{value}
        </select>
    

    指定 CustomerId ,执行 sql 时将被路由到具体的分表中。

        private void testInsert() {
    
            SignInEntity entity = new SignInEntity();
            Random r = new Random();
            // 路由规则将在三张表中插入数据,数据将平均分布
            for (int i = 1; i <= 10 * 10000; i++) {
                entity.setId(i);
                entity.setType(r.nextInt(3));
                entity.setCustomerId(r.nextInt(9999));
                entity.setCurrentSignInStoreId(r.nextInt(9999));
                entity.setCreateEid(r.nextInt(9999));
                entity.setDate(new Timestamp(System.currentTimeMillis()));
                signInDao.insert(entity);
            }
    
        }
    

    完整代码上传 GitHub,你可以在 这里 找到

    参考文章

    JDK 源码中 HashMap 的 hash 方法原理是什么
    java 集合 3 - HashMap
    数据迁移测试实施方案

    相关文章

      网友评论

          本文标题:使用 zebra 对数据库表进行水平拆分

          本文链接:https://www.haomeiwen.com/subject/cxbncqtx.html