最近帮朋友维护一个Java项目,其中会生成给用户看的订单号。订单号来自数据库自增id,但是客户群体变化不大,因此带来一个算是严重的后果:客户通过两次订单号就能知道系统一天大概有多少订单。更要命的是由于订单数不多,让别人知道这个数有点丢人,另外露底了后续也不再好吹牛。
改进的办法之一是用唯一id生成器,让ID递增但是无规律,这能很好的解决上面的尴尬。知名的整数类型唯一id生成器当属雪花算法(snowflake)及衍生库。但是试用了一下原版雪花算法后发现两个问题:
1. 生成的id太长了,管理后台上ID这一列很长,完全没必要。另外由于是毫秒递增,相邻订单的ID差距有几十上百亿,太浪费可用ID了;
2. 生成的ID超过了前端JS的整数上限,需要用 json-bigint
之类的库单独处理ID。
于是想到了短ID生成器,目前知道好用且知名的就是 Yitter的IdGenerator。相对于其他雪花类算法,其最大的优点就是不固定长度,可按需配置各个段的值或者偏移,大幅减少了ID的长度,避免了号段浪费。如果并发量上来了,可以直接更改配置实现无缝升级。
IdGenerator改进明显,但是对于手上的小项目来说ID还是太长了(默认配置下随手甩出来的ID就是上万亿的数),也用不着这么高的并发(别说一秒5W个id,一天能有5W个订单朋友肯定都笑得合不拢嘴)。此外全局只能一个生成器,不能根据业务场景采用不同的生成器:购买订单和退货订单ID是可以重复的,并且一般情况下退货订单数量比购买订单少一个量级,可以采用更短的ID。
出于上面的考虑,本人对 IdGenerator 库进行了如下改进:
1. 支持不同的时间精度。原版中默认是1毫秒,现在新增了10毫秒 – 10秒(实际上是 8毫秒、128毫秒、1024毫秒 和 8192毫秒,方便用位运算代替整数除法)的精度选项。低精度选项(例如1秒、10秒)适合并发量小的项目(例如1秒内最多30个订单),能大大减少ID的长度;
2. WorkIdBitLength支持为0,让单机应用能生成更短或数值更小的ID;
3. 支持不同场景使用不同的Id生成器,同时兼容原来的接口:
// 兼容原版使用方式 var options = new IdGeneratorOptions(); YitIdHelper.setIdGenerator(options); var newId = YitIdHelper.nextId(); // 退款订单场景 final var REFUND = "refund"; var options2 = new IdGeneratorOptions(); // 退款相当于购买订单频率更低,可以降低时间精度要求 options2.Precision=1; YitIdHelper.setIdGenerator(REFUND, options2); var newId = YitIdHelper.nextId(REFUND);
本人测试了不同算法和时间精度下生成的ID,结果为:
# 调用 Hutool 的 IdUtil.getSnowflakeNextId() 方法生成 这是用方法 snowflake 生成的 Id: 1957067304089067520(长度19位) ===================================== # 使用 IdGenerator的默认配置,无任何改动 这是用方法 默认 生成的 Id: 80881995821061(长度14位) ===================================== # 修改 options.Precision = 1 这是用方法 precision1 生成的 Id:10110249734149(长度14位) ===================================== # 修改 options.Precision = 2 这是用方法 precision2 生成的 Id:631890624517(长度12位) ===================================== # 修改 options.Precision = 3 这是用方法 precision3 生成的 Id:78986326021(长度11位) ===================================== # 修改 options.Precision = 4 这是用方法 precision4 生成的 Id:9873293317(长度10位)
对于本人手上的小项目,没有多大的并发(1天大概几百个订单),可以配置生成更短的ID:
var options = new IdGeneratorOptions(); options.Precision = 4; // 时间精度为10秒 options.WorkerIdBitLength = 1; // 最多两个应用实例 options.SeqBitLength = 6; // 每10秒可以生成29个订单,高峰期超过也没关系,会自动进行时间漂移 YitIdHelper.setIdGenerator("order", options); var id = YitIdHelper.nextId("order"); # 生成的ID: 2707882245
此配置下订单号为10位,这个长度是非常能接受的,并且值也很小。
Java版本的改进代码已经放到 Github ,其他语言版本本人暂时用不着,因为未更改。如果有需要按照Java版本的方式更改即可,代码量其实很少。另外目前没有Maven包,使用时需要将代码下载下来直接使用。
发表回复