系 统 在 按 照
订单号查询的时候一直抛错,也没法正常回调,而且事情发生的不止一
次,所以 这次系统升级一定要解决掉。
经手的同事之前也改过几次,不过效果始终不好:总会出现订单号重
复的问题, 所以趁着这次问题我好好的理了一下我同事写的代码。
这里简要展示下当时的代码:
/**
* OD 单 号 生 成
* 订 单 号 生 成 规 则 : OD + yyMMddHHmmssSSS + 5 位 数 ( 商 户 ID3 位 + 随 机 数 2 位 ) 22 位
*/
public static String getYYMMDDHHNumber(String merchId){
StringBuffer orderNo = new StringBuffer(new SimpleDateFormat("yyMM
ddHHmmssSSS").format(new Date()));
if(StringUtils.isNotBlank(merchId)){
if(merchId.length()>3){
orderNo.append(merchId.substring(0,3));
}else {
orderNo.append(merchId);
}
}
int orderLength = orderNo.toString().length();
String randomNum = getRandomByLength(20-orderLength);
orderNo.append(randomNum);
return orderNo.toString();
}
/** 生 成 指 定 位 数 的 随 机 数 **/
public static String getRandomByLength(int size){
if(size>8 || size<1){
return "";
}
Random ne = new Random();
StringBuffer endNumStr = new StringBuffer("1");
StringBuffer staNumStr = new StringBuffer("9");
for(int i=1;i<size;i++){
endNumStr.append("0");
staNumStr.append("0");
}
int randomNum = ne.nextInt(Integer.valueOf(staNumStr.toString()))+
Integer.valueOf(endNumStr.toString());
return String.valueOf(randomNum);
}
可以看到,这段代码写的其实不怎么好,代码部分暂且不议,代码中
使订单号不重复的主要因素点是随机数和毫秒,可是这里的随机数只
有 两 位
在高并发环境下极容易出现重复问题,同时毫秒这一选择也不是很好
,在多核CPU多线程下,一定时间内(极小的)这个毫秒可以说是固定
不 变 的 ( 测 试 验 证 过 ) , 所
以这里我先以100个并发测试下这个订单号生成,测试代码如下:
public static void main(String[] args) {
final String merchId = "12334";
List<String> orderNos = Collections.synchronizedList(new ArrayList<S
tring>());
IntStream.range(0,100).parallel().forEach(i->{
orderNos.add(getYYMMDDHHNumber(merchId));
});
List<String> filterOrderNos = orderNos.stream().distinct().collect(C
ollectors.toList());
System.out.println(" 生 成 订 单 数 : "+orderNos.size());
System.out.println(" 过 滤 重 复 后 订 单 数 : "+filterOrderNos.size());
System.out.println(" 重 复 订 单 数 : "+(orderNos.size()-
filterOrderNos.size()));
}
果然,测试的结果如下:
生 成 订 单 数 : 100
过 滤 重 复 后 订 单 数 : 87
重复订单数:13
当时我就震惊�了,一百个并发里面竟然有13个重复的!!!,我赶
紧让同事先不要发版,这活儿我接了!
对这一烫手的山竽拿到手里没有一个清晰的解决方案可是不行的,我
大概花了6+分钟和同事商量了下业务场景,决定做如下更改:
去掉商户ID的传入(按同事的说法,传入商户ID也是为了防止重复订
单的,事实证明并没有叼用)
毫秒仅保留三位(缩减长度同时保证应用切换不存在重复的可能)
使用线程安全的计数器做数字递增(三位数最低保证并发800不重
复,代码中我给了4位)
更换日期转换为java8的日期类以格式化(线程安全及代码简洁性考
量)
经过以上思考后我的最终代码是:
/** 订 单 号 生 成 (NEW) **/
private static final AtomicInteger SEQ = new AtomicInteger(1000);
private static final DateTimeFormatter DF_FMT_PREFIX = DateTimeFormatter
.ofPattern("yyMMddHHmmssSS");
private static ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai");
public static String generateOrderNo(){