京东的订单号为什么那么短? Posted by xmpace on 2019-09-13

京东订单号

研究订单号生成策略的同学一定都关注过阿里和京东的单号,阿里的订单号现在是 18 位数字,而京东的订单号只有区区 12 位数字(2019年9月13日),换算成二进制,只占 37 位而已。

我在上一篇文章中介绍的 Snowflake,用来做订单号的话,长度是多少呢?

假设系统已运行 10 年,所流逝的秒数为 315,360,000 秒,换算成二进制占 29 位,即使 workerId 只占 2 位,要做到京东的长度,序列号的位数也只能占到区区 6 位,6 位也就是 64 个数,表示一秒钟最多只能创建 64 个订单,这对于京东来说显然是不够的。

据公开资料显示,京东超市上线 4 年累计单量就达到了 60 亿,再加上京东非超市的单量,保守估计都是百亿级别,12 位数字也就千亿级别,百亿与千亿的二进制表示也就 3 到 4 位的差距,用来表示其他信息实在有限,因此,大胆猜测是京东采用的自增加混淆的生成策略。

据观察京东的订单号虽不是严格递增,但也是趋势增加的。

那么对于京东这种量级的系统真的直接用自增加混淆就行了吗?

自增的问题

如果我们用 MySQL 来存储,那么百亿的订单存在一个表里肯定是不行的,势必需要分库分表,分库分表的话就不适合再用数据库的自增 id 了,因为不同的表生成的 id 有可能是一样的,除非你每张表都去手动配置自增起点,但那样的话以后要扩容怎么办呢?怎么去手动维护呢?

所以像这种业务量大的系统,考虑到要分库分表,不会直接采用数据库自增 id,而是会用一个外部的全局发号器。那么如何搭建一个高性能高可用,生成的 id 又贼短的发号服务呢?

最简单的,还是用数据库的自增 id,只不过这个用来自增的表全局唯一,专门用来给别人发号。简单是简单,但是有单点问题,而且性能也不好。

于是有人搭两个库,一个发奇数,一个发偶数,两个库不够还可以继续加。这样可以是可以,但是不灵活,运行起来后要扩容的话基本没法搞。

别担心,还有更好的办法,分段发号。

分段发号

什么叫分段发号呢?还是自增 id,每台发号机器预先取一大批号,比如自增步长设为 10000,每次自增就相当于取了 10000 个号,1 号机取 0-10000,2 号机取 10001-20000,以此类推。这样,每次发号直接从本地内存里拿预取的号就行,不需要访问数据库,性能刚刚的,预取的号发完,再去数据库拿下一批号。如果发号程序意外重启,那么它预取的没发完的号就直接丢弃不管了,这样,可以保证不会发放重复的 id。

由于每次拿号时请求会均匀地发到发号集群的不同机器上,所以拿到的号是不连续的,但是从比较长的时间角度来看,号码是趋势增加的。因此,不能依赖于这种方案来隐藏业务数据,竞对可能难以猜到一个小时的业务数据,但他可以猜到一天,或者一个月的业务数据。

因此,为了隐藏业务数据,对 id 的加密还是不能省略,可以采用上一篇文章提到的加密算法对 id 加密。这里要注意,如果对 id 的整个 64 位加密,会导致加密后的数字比较大,整个 id 长度较长,这样就达不到京东这种短 id 的效果了,因此,可以只对 id 的低 32 位加密,32 位的加密算法可以用 Skip32。

分段发号的方案由于强依赖于数据库,仍然存在单点问题,那么如何提高可用性呢?

高可用

我们看下美团的开源产品 Leaf 是怎么做的。(上篇文章介绍了 Leaf 的 Snowflake 方案,其实它还支持分段发号的方案)

美团采用的是半同步的方案,也就是数据库主从结构,每次写入必须从机写入完毕才算写入完毕,是一种强一致性的方案,这样主机出现问题,可以切换到从机。

但是加入从机,其实故障的概率也会加大,只不过在故障发生时有了备用方案。因此,建议发号步长设得大一点,每次拿的号越多,访问数据库的频率就越低,这样,在故障的时候,能争取到更多的时间处理故障。

总结

以上就是类似京东短订单 id 的生成方案,但是如上篇文章所说,这种方案业务信息的安全性依赖于加密规则,而这个加密规则并不能灵活变更,有可能被员工泄露出去,因此还是有泄露业务数据的风险,安全性上不如 Snowflake 高。