流量不大的情况下,订单号生成
很久之前写过一篇利用DB生成业务主键的文章,介绍了利用DB来生成唯一的ID。当时便是用这种方式来生成订单号的。只不过拿到ID后,根据订单业务,简单加个前缀而已。
@Service
public class KeyGen{
@Autowired
private KeyGenRepository keyGenRepository;
public long genNo(){
KeyGen keyGen = new KeyGen();
keyGenRepository.genarateNo(keyGen );
//这个就是我们需要的no
long no = keyGen.getNo();
}
}
private String generateOrderNo() {
StringBuffer sbf=new StringBuffer();
//前缀
sbf.append("100");
long no = keyGen.genNo();
sbf.append(no);
return sbf.toString();
}
这种方式用了一段时间,没发现有订单号重复的情况。这种解决方案算是一个基础的思路
,再复杂的订单生成规则,如果订单号要包含一个唯一的属性,利用数据库的自增特性是个不错的方案。
大流量下订单号的生成
如果每个小时的订单量非常大,比如说,一个小时有两百万个订单,只用单独一个key_gen
表是支持不住的,毕竟写入的压力太大了,影响订单号的生成速度。这个时候可以考虑针对订单号的生成,搞单独的
库,并分库,降低insert
的压力,提高生成订单号的速度。
分库的规则
有一种做法是根据仓库来映射,比如说,一家电商公司的仓库总共有50个,那么可以进行如下的映射:
warehouse1:数据库0
warehouse2:数据库0
warehouse3:数据库1
warehouse4:数据库1
warehouse5:数据库2
warehouse6:数据库2
warehouse7:数据库3
warehouse8:数据库3
warehouse9:数据库3
warehouse10:数据库4
。。。。。。
。。。。。。
warehouse50:数据库9
将50个仓库映射到【0-9】对应的数据库上,当下单的时候,如果订单对应的仓库的是warehouse1,则映射到数据库0,对应的仓库如果是warehouse10,则对应的数据库4。这样子,订单号的生成的压力便分配到10个数据库上了。
进行分库后,每个分库里都有一张key_gen
表。
组装订单号
上面的分库分表,目的是为了生成一个唯一的ID,这个ID是订单号的一部分,生成ID借助了数据库,但是后面组装订单号则完全是业务逻辑操作,无需利用数据库了。
订单号的生成规则各个公司都有自己的要求,举个例子:
时间 + 6位随机数 + 数据库生成的唯一ID+仓库标识
时间的生成可以简单的使用如下代码生成:
SimpleDateFormat formatShort = new SimpleDateFormat("yyMMdd");
Date now = new Date();
String currentDate = formatShort.format(now);
六位的随机数可以借助JAVA的AtomicLong
来实现,可以应付并发。
到此订单号完整的生成了。那有没有坑呢?因为进行了分库,每个库都有key_gen
,生成的ID只是库内的唯一,多个库则是未必的。比如说,两个订单创建的请求,仓库分别是warehouse1和warehouse3,根据上面的配置规则,分别路由到了数据库0
和数据库1
这两个库,这个时候,就可能产生相同的ID。但是不要忘记,订单号的生成是包含仓库标识的,一个1,一个是3,是不同的,另外还有随机数,所以订单号重复的机会基本不太可能的。
提供批量生成订单号的接口
尤其是将生成订单号定义为一个微服务接口的,一定要提供批量生成订单号的接口,在流量很大的情况下,每获取一个订单号就要走一次网络调用,开销实在太大了。那么一次要生成多少个才合适呢,这个根据自己的业务情况,一般情况下20-50个是够用的。