短码即分片键:一次免迁移扩容的分库分表设计
整理旧项目时翻到短链服务的分库分表方案,当时觉得理所当然,现在回头看是整个服务里最省事的一个决定。记一下。
问题
短链服务的核心表是”短码 → 原始链接”。量大,要分库分表。常规做法是拿短码做哈希取模路由,但取模有个绕不开的坑:扩容要重新取模,旧数据得迁移。短链这种表,迁移期间还得保证跳转不中断,谁做谁头疼。
做法:让短码自己带位置
短码不是纯随机生成的,而是三段拼出来的:
库前缀 + murmurHash32(原始链接 + 随机数) 的 62 进制 + 表后缀
库前缀和表后缀各一位,从当前启用的库表集合里随机选。当时启用的是 3 个库、每库 2 张表,前缀从 0 / 1 / a 里取,后缀从 0 / a 里取。
路由算法就变得非常直接:拿短码首位查库,拿末位查表,不需要哈希,不需要取模:
// 自定义精确分片算法(简化)
String prefix = shortCode.substring(0, 1); // 库
String suffix = shortCode.substring(len-1); // 表
扩容为什么不用迁移
新增一个库,只要往”启用集合”里加一个新前缀,之后生成的短码有一部分会落到新库。老短码的前缀还是老的,照旧路由到老库。没有任何数据需要动。
代价是新库一开始数据少、老库数据多,负载不均。但短链是写少读多、读又走缓存的场景,这点不均可以接受。想快一点均衡,就把新前缀的随机权重调高。
数据倾斜
murmurHash 本身够均匀,62 进制编码不改变分布。真正会倾斜的是前缀和后缀的选择——如果集合里某个值被选中的概率高,对应库表就重。用等概率随机选,或者按容量配权重,就够了。
另一个问题:B 端怎么查
短码路由解决的是 C 端”按短码找链接”。B 端要”按商家分组分页看短链列表”,分片键不是短码,查不动。
当时的解法是冗余双写:再存一张按分组分片的映射表,两张表通过消息队列异步双写,各自服务各自的查询。双写的一致性靠消息可靠投递兜底。这个方案单独值得写一篇,下次。
现在回头看
这个设计的本质是把路由信息编码进业务标识里。它不通用——只有当标识是系统自己生成、又允许携带额外信息的时候才能用。短码正好满足。订单号其实也满足,很多公司的订单号里就藏着日期和分片位。
能用的时候,它比一致性哈希简单得多。