事故清单摊开看
前十四篇把主干讲完了,但老王复盘半年的工单发现,真正把人逼疯的往往是边角料:不是架构选错,而是位数算错、类型选错、约定没写下来。他把这些坑列成清单,一共七条,条条都流过血。逐条过。
坑一:JS 精度丢失,尾号集体变 0
现象:前端反馈订单详情查不到,抓包一看,后端返回的雪花 ID 尾部好几位变成了 0,而数据库里明明是正常数字。排查半天 Java 日志——后端发的号是对的,问题出在浏览器:JavaScript 的 Number 是 IEEE 754 双精度浮点,安全整数范围只有 2 的 53 次方减一,而 64 位雪花 ID 轻松越线,超出部分被静默舍入——尾号丢精度,详情接口自然对不上。
解法:序列化时把 Long 型 ID 统一转字符串返回(Jackson 配置 Long 序列化为 String,或全局加一层转换)。这条要写进团队规范——凡是超过 53 位整数语义的字段,出网一律字符串。名字都别叫 number,叫 string。
坑二:秒级时间戳的八年之约
现象:接手别人项目,UidGenerator 秒级时间戳 28 位,算了一下距号耗尽只剩不到三年——上一任规划时按「当前时间 + 8.5 年」倒推,规划完两年没人记得这茬。
解法:位数分配表必须写进代码注释与运维文档,并把「ID 耗尽日」当成一个监控指标(按当前消耗速率动态预估)。改造方案提前一年排期:要么时间位扩位(序列位缩位换),要么换发号方案。耗尽不是某天的突发事件,是每天逼近一天的既定事实——把它变成看得见的倒计时。
坑三:纪元起点用了默认值
现象:雪花的时间位是从「纪元起点」开始计的偏移量。图省事直接用了库默认起点(比如 2010 年),白白烧掉十几年时间位——等于人生下来就从 35 岁开始算退休倒计时。
解法:纪元起点自定义成项目上线日附近,一分时间位都不浪费。同时警告:纪元一旦定下不能改——换了起点,全体历史 ID 的时间语义整体漂移,按时间范围查数据的逻辑全炸。纪元和分片基因一样,属于「焊死」决策,进架构决策记录。
坑四:号段长度的心理战
现象:小业务团队为了「ID 好看」把 step 设成 100,高峰期一分钟取十几次号段,DB 被打挂;另一极端设成 1000 万,应用重启一次就烧掉一千万个号,量大业务一年浪费的号比存量还多。
解法:step 是「DB 压力」与「浪费与洞」的杠杆,默认值建议按 峰值 QPS × 10 秒到 60 秒的量估——buffer 里常备十秒到一分钟的号,DB 每分钟最多被每实例打扰几次。有条件的上动态 step(Leaf 的思路),让系统自己找平衡。
坑五:workerID 没带机房维度
现象:同城双机房上线三个月相安无事,直到老王做单元化改造把两地同时切主——两地各有一台 workerID=7 的机器,同一毫秒发出同一个 ID。第 8 篇的分配表是按单机房规划的,机房维度压根没留。
解法:workerID 位一拆为二:高位做机房号(dataCenterID),低位做机器号。哪怕暂时单机房,也要在位分配里预留这个维度——位分配是十年合约,机房是迟早的事。
坑六:把 ID 当业务字段解析
现象:有业务方发现订单 ID 尾号是基因位,直接在代码里写死「ID 后 8 位取模定位库表」,绕过分片中间件。半年后位分配调整,所有硬编码解析的地方集体报错。
解法:ID 的位语义只属于发号器,对外只承诺「唯一 + 趋势递增」两条,位级别的解析必须收敛到统一的工具类(分片算法内部),别处一律当黑盒。谁解析谁负责——这规矩要写进 code review 清单。
坑七:序列位塞私货
现象:有同学脑洞大开,把渠道号塞进序列位高位「省一次字段存储」,单机自增没问题,一到多机并发——序列位被私货占掉几位,单毫秒容量从 4096 掉到 256,高峰期大量自旋等待;更要命的是两个渠道塞了同一个值,唯一性靠运气。
解法:序列位是唯一性的生命线,纯净保留,禁止复用。业务信息想跟 ID 走,要么走第 12 篇的外部 ID 映射(外部单号里随便编),要么老老实实存字段。
小结
七坑一表收尾:出网转字符串防精度丢失,倒计时监控防号耗尽,纪元焊死防语义漂移,step 按峰值估防两头挨打,workerID 留机房位防多活撞号,位解析收敛防硬编码,序列位保纯净防唯一性破功。每一条都不是高深技术,是纪律。主干加边角都补齐了,下一篇收官——把十六篇串成一张作战地图。
评论 (0)