订单号查不到订单的尴尬
老王的订单业务量起来了,DBA 动手分库分表:16 个库、每库 16 张表,共 256 张,按 user_id 取模路由。拆完顺一阵子,直到客服接入工单:用户报了个订单号,系统拿着号去查——查哪个库?订单号是雪花 ID,里面只有时间和机器信息,没有 user_id,路由信息是空的。要么全库广播问一遍,要么先查路由表。两条路都有代价,这一篇讲第三条路。
二次路由的三条路
广播查询:拿着订单号把 16 个库全问一遍。简单粗暴,浪费巨大——每次单点查询变成 256 张表的全网扫,高峰期直接打爆所有库。只配当兜底,不配当日常。
路由表/索引表:单独建一张 order_no 到 user_id 的映射表,先查映射再路由。查询翻倍,映射表本身也是大表,还要维护一致性(订单创建失败要回滚映射)。能做,但每条链路都多个绊子。
基因法:发号的时候就把路由信息埋进 ID——让 ID 自己会认路。
基因法的位设计
原理一句话:把 user_id 的路由哈希值截取低位几位(比如 8 位),作为「基因」嵌进订单 ID 的固定位置。订单落库时,既可以用 user_id 路由,也可以直接用订单 ID 的基因位路由——因为基因就是 user_id 路由值的拷贝,两种路由结果必然一致:
user_id 路由基因:hash(user_id) 低 8 位 = 0b10110100
订单 ID 位分配(示例):
[ 1 符号 ][ 40 时间戳 ][ 8 workerID ][ 8 基因位 ][ 7 序列 ]
订单号查库:订单ID 基因位 0b10110100 直接取模路由
用户查单:user_id 基因 0b10110100 取模路由
两条路落进同一张表,天然一致客服拿订单号查单,中间件(ShardingSphere 之类)配一个自定义分片算法,解析 ID 的基因位直接定位库表——一次路由,零额外查询。用户查自己的单,走 user_id 路由,还是同一张表。商家查自己的所有订单(跨用户的场景),才需要ES 或异构索引兜底——基因法解决的是「已知 ID 或已知用户」的精准查,不是复杂维度的搜索。
发号侧怎么嵌
雪花模式:取号接口加一个参数(业务方传入基因值),发号器把序列位砍掉几位腾出基因位。号段模式更简单:各业务取号段时把基因拼在低位,或者干脆每个基因一个 biz_tag——基因值有限的场景(如分 256 张表,基因就是 0-255),直接给每个基因一个独立号段,号段天然带基因,连位运算都省了。
关键纪律只有一条:嵌入基因必须与 user_id 的路由哈希同源——同样的哈希函数、同样的位数、同样的取模规则。发号服务和分片中间件分属两个团队时,这条规则最容易断,一断就是数据路由分裂的大事故。
明码标价的代价
基因法不是免费的:
分片规则焊死在 ID 里:日后扩容(256 表变 1024 表)或换路由算法,存量 ID 里的基因还是老规则的产物,新老 ID 路由并存,迁移方案要特殊设计。这是最大的隐性债,选基因法等于签了一份长期合约。
位数挤占:64 位总量恒定,基因位每加一位,序列位就少一位,单机单毫秒吞吐折半。基因要几事先算清,别拍脑袋。
业务耦合:这个 ID 挪去别的业务用,基因位就是无意义的包袱——带基因的 ID 适合「专用」,不适合「通用」。
| 方案 | 额外查询 | 额外存储 | 主要风险 |
|---|---|---|---|
| 广播查询 | N 倍放大 | 无 | 高峰打爆全库 |
| 路由映射表 | 多一次 | 一张大映射表 | 双写一致性 |
| 基因法 | 零 | 无 | 规则焊死、位数挤占 |
小结
基因法一句话:在 ID 里预埋路由基因,换来零成本的二次定位;同源纪律是生命线,规则焊死是长期债。分库分表场景值得上,通用发号场景别乱上。老王的订单号从此自带门牌号,客服查单一秒定位。但新的问题悬在头顶:发号服务自己挂了怎么办?全公司都依赖它,它可是单点中的单点——下一篇讲高可用与降级。
评论 (0)