在设计随机ID生成方法之前,我通常会考虑这么几个问题:
1、长度多长,是否定长?
2、是否要求纯数字?
3、是否有分布式的要求?
4、业务量是多大?每毫秒至少要求几个序列号?
以上问题是基于常见的随机ID算法提出的,例如UUID,雪花算法等。随机ID的生成常常会涉及到时间戳,MAC地址,ip地址的提取,因此会有第3问和第4问。生成随机数的方法有很多,我们需要根据业务场景来设计合适自己的。
本文设计背景是,业务员在商户充值系统手动生成钱包充值订单时,系统随机生成充值单号,原先的.Net老系统直接调用公司统一封装的UUID包,但是做Java改造时发现工具包没有Java版本,所以只能自己设计。业务场景有以下几个特点:
1、要求与老系统逻辑保持不变,订单号必须是19位定长的纯数字
2、没有专门的自增序列表可以用
3、充值订单新建都是靠客服人工操作,并发很低
4、不是单机,涉及分布式环境
按照通常的做法,我先取了13 位的currentMillis,再取系统ip,ip保留最后两段共6位,不足6位用0补齐。这么做的原因是按照公司分配机器的惯例,同一个系统的多台机器ip的前两段通常是相同的,保留下来没有太大的意义,而且长度的限制摆在这。
这样算下来,时间戳+ip 刚好19位,同一毫秒只能有一笔订单。很显然这样的重复几率有点太大了。但是又不能超过长度,最后我选定的方法是,舍弃时间戳的第一位,留一位用来做序列。12位的时间戳会在30年左右重复,以当前的业务场景来看是符合要求的。
那么最后这个id的生成方式就变成了 12位时间戳 + 6位ip + 1位自增序列。虽然是分布式环境,但是序列并没有分布式,而是维护在了本地。原因很简单,序列的目的是让同一机器同一毫秒下不出现重复订单号即可,因此本地自增是完全可行的。而且重启后重新从0开始问题也不大。只要一毫秒落地的订单不超过10笔就完全没问题。
这样的做法显然不是特别优雅,特别是截取一位时间戳的操作可能会被吐槽。但是在这特定的条件和业务场景下,这也是我能想出的比较好的办法了。后续有新的想法,我会在这里补充。