本文是“Java秒杀系统实战系列文章”的第七篇,在本文中我们将重点介绍 “在高并发,如秒杀的业务场景下如何生成全局唯一、趋势递增的订单编号”,我们将介绍两种方法,一种是传统的采用随机数生成的方式,另外一种是采用当前比较流行的“分布式唯一ID生成算法-雪花算法”来实现。

在上一篇文章,我们完成了商品秒杀业务逻辑的代码实战,在该代码中,我们还实现了“当用户秒杀成功后,需要在数据库表中为其生成一笔秒杀成功的订单记录”的功能,其对应的代码如下所示:

//通用的方法-记录用户秒杀成功后生成的订单-并进行异步邮件消息的通知private void commonRecordKillSuccessInfo(ItemKillkill, Integer userId) throws Exception{ //TODO:记录抢购成功后生成的秒杀订单记录 ItemKillSuccess entity=new ItemKillSuccess(); //此处为订单编号的生成逻辑 String orderNo=String.valueOf(snowFlake.nextId()); //entity.setCode(RandomUtil.generateOrderCode()); //传统时间戳+N位随机数 entity.setCode(orderNo);//雪花算法 entity.setItemId(kill.getItemId()); entity.setKillId(kill.getId()); entity.setUserId(userId.toString()); entity.setStatus(SysConstant.OrderStatus.SuccessNotPayed.getCode().byteValue()); entity.setCreateTime(DateTime.now().toDate()); //TODO:学以致用,举一反三 -> 仿照单例模式的双重检验锁写法 if (itemKillSuccessMapper.countByKillUserId(kill.getId(),userId)<= 0){ intres=itemKillSuccessMapper.insertSelective(entity); //其他逻辑省略 }}

在该实现逻辑中,其核心要点在于“在高并发的环境下,如何高效的生成订单编号”,那么如何才算是高效呢?Debug认为应该满足以下两点:

1、保证订单编号的生成逻辑要快、稳定,减少时延;

2、要保证生成的订单编号全局唯一、不重复、趋势递增、有时序性。

下面,我们采用两种方式来生成“订单编号”,并自己写一个多线程的程序模拟生成的订单编号是否满足条件。

值得一提的是,为了能直观的观察多线程并发生成的订单编号是否具有唯一性、趋势递增,在这里Debug借助了一张数据库表 random_code来存储生成的订单编号,其DDL如下所示:

CREATE TABLE `random_code` ( `id`int(11) NOT NULL AUTO_INCREMENT, `code`varchar(255) DEFAULT NULL, PRIMARY KEY(`id`), UNIQUE KEY`idx_code` (`code`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8;

从该数据库表数据结构定义语句中可以看出,我们设定了 订单编号字段code 为唯一!所以如果高并发多线程生成的订单编号出现重复,那么在插入数据库表的时候必然会出现错误。

下面,首先开始我们的第一种方式吧:基于随机数的方式生成订单编号。

(1)首先是建立一个Thread类,其run方法的执行逻辑为生成订单编号,并将生成的订单编号插入数据库表中,其代码如下所示:

/** * 随机数生成的方式-Thread * @Author:debug (SteadyJack) * @Date:2019/7/11 10:30 **/public class CodeGenerateThread implementsRunnable{ private RandomCodeMapper randomCodeMapper;  public CodeGenerateThread(RandomCodeMapper randomCodeMapper) { this.randomCodeMapper = randomCodeMapper; }  @Override public void run() { //生成订单编号并插入数据库 RandomCode entity=new RandomCode(); entity.setCode(RandomUtil.generateOrderCode()); randomCodeMapper.insertSelective(entity); }}

其中,RandomUtil.generateOrderCode()的生成逻辑是借助ThreadLocalRandom来实现的,其完整的源代码如下所示:

/** * 随机数生成util * @Author:debug (SteadyJack) * @Date:2019/6/20 21:05 **/public class RandomUtil { private static final SimpleDateFormat dateFormatOne=newSimpleDateFormat("yyyyMMddHHmmssSS"); private static final ThreadLocalRandom random=ThreadLocalRandom.current(); //生成订单编号-方式一 public static String generateOrderCode(){ //TODO:时间戳+N为随机数流水号 return dateFormatOne.format(DateTime.now().toDate()) + generateNumber(4); } //N为随机数流水号 public static String generateNumber(final int num){ StringBuffer sb=new StringBuffer(); for(int i=1;i<=num;i++){ sb.append(random.nextInt(9)); } return sb.toString(); }}

(2)紧接着是在 BaseController控制器 中开发一个请求方法,目的正是用来模拟前端高并发触发产生多线程并生成订单编号的逻辑,在这里我们暂且用1000个线程进行模拟,其源代码如下所示:

@Autowiredprivate RandomCodeMapper randomCodeMapper;//测试在高并发下多线程生成订单编号-传统的随机数生成方法@RequestMapping(value ="/code/generate/thread