订单号生成的一些想法
背景
早上QA小姐姐发现线上有个报错,过去一看,采购单号生成异常,后台duplicate key,也就是说生成了重复单号。这个模块之前不是我写的(这个哥们刚离职了),而后来的领料单号的生成我重新写了下,规则比较简单,重复率也比较低,大家有好的方案也可以分享下。
正文
首先,背景是我们这里需要一个18位的单号(有多种类型:采购、发货、领料等),订单号之类的基本规则:唯一、无序(特殊情况会要求排序、增量等),我们这里定了相应的格式:4位shopId + 2位订单类型编码 + 8位年月日 + 4位字符,
例如:0004CK201805080005。而原来哥们最后四位是用递增来生成的,0001之类的,而且是从数据库读取最新生成的单号,截取最后四位然后加1,这样的问题很明显:多机器(集群)时,或者有并发时很容易生成重复单号导致报错,由于我们这里是内部管理系统,并发量不大,但还是发生报错。还有连续生成的规则,容易被恶意爬取推算出每日的单量等。
然后,基于原来的规则,我把最后的4为字符改为随机,加入了0-9、A-Z共36个字符,方法如下:
private static String[] chars = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; /** * * @Description:生成指定位数随机字符 * @param length * 位数 * @return String * @exception: * @author: wws * @time:2018年5月8日 下午4:18:58 */ private static String generateCode(int length) { StringBuilder shortBuffer = new StringBuilder(); String uuid = UUID.randomUUID().toString().replace("-", ""); for (int i = 0; i < length; i++) { String str = uuid.substring(i * 4, i * 4 + 4); int x = Integer.parseInt(str, 16); shortBuffer.append(chars[x % chars.length]); } return shortBuffer.toString(); }
这样我们生成的4位数的组合理论上重复的概率是很低的,适合中小的系统使用。
其他生成方法
1. 使用mysql自增id,优点:简单、保证唯一性;缺点:过于简单、且数据日渐增加、没有区分度(不同业务订单类型无法分辨)等
2. UUID存储,优点:生成简单、无消耗;缺点:长度过长、没区分度、无序(数据位置无规则,可能影响到索引)、不安全(基于mac地址生成,可能导致泄露)等
3. snowflake(Twitter),优点:不依赖数据库、性能好、单机递增;缺点:强依赖机器时钟、分布式下可能不同步等
4. 美团的leaf(https://tech.meituan.com/MT_Leaf.html)。美团这里提供的思路很清晰,针对取号、时钟回拨等问题进行了分析,也提供了相应的解决(预警)方案,推荐大家看下。