java⽣成唯⼀有序序列号_Java秒杀系统实战系列~分布式唯⼀
ID⽣成订单编号
摘要:
本篇博⽂是“Java秒杀系统实战系列⽂章”的第七篇,在本博⽂中我们将重点介绍 “在⾼并发,如秒杀的业务场景下如何⽣成全局唯⼀、趋势递增的订单编号”,我们将介绍两种⽅法,⼀种是传统的采⽤随机数⽣成的⽅式,另外⼀种是采⽤当前⽐较流⾏的“分布式唯⼀ID⽣成算法-雪花算法”来实现。
内容:
在上⼀篇博⽂,我们完成了商品秒杀业务逻辑的代码实战,在该代码中,我们还实现了“当⽤户秒杀成功后,需要在数据库表中为其⽣成⼀笔秒杀成功的订单记录”的功能,其对应的代码如下所⽰:
//通⽤的⽅法-记录⽤户秒杀成功后⽣成的订单-并进⾏异步邮件消息的通知
private void commonRecordKillSuccessInfo(ItemKill kill, Integer userId) throws Exception{
//TODO:记录抢购成功后⽣成的秒杀订单记录
ItemKillSuccess entity=new ItemKillSuccess();
//此处为订单编号的⽣成逻辑
String orderNo=String.Id());
//entity.ateOrderCode()); //传统时间戳+N位随机数
entity.setCode(orderNo); //雪花算法
entity.ItemId());
entity.Id());
entity.String());
entity.setStatus(SysConstant.Code().byteValue());
entity.w().toDate());
//TODO:学以致⽤,举⼀反三 -> 仿照单例模式的双重检验锁写法
if (Id(),userId) <= 0){
int res=itemKillSuccessMapper.insertSelective(entity);
//其他逻辑省略
}
}
在该实现逻辑中,其核⼼要点在于“在⾼并发的环境下,如何⾼效的⽣成订单编号”,那么如何才算是⾼效呢?Debug认为应该满⾜以下两点:
(1)保证订单编号的⽣成逻辑要快、稳定,减少时延
(2)要保证⽣成的订单编号全局唯⼀、不重复、趋势递增、有时序性
下⾯,我们采⽤两种⽅式来⽣成“订单编号”,并⾃⼰写⼀个多线程的程序模拟⽣成的订单编号是否满⾜条件。
值得⼀提的是,为了能直观的观察多线程并发⽣成的订单编号是否具有唯⼀性、趋势递增,在这⾥De
bug借助了⼀张数据库表
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 implements Runnable{
private RandomCodeMapper randomCodeMapper;
public CodeGenerateThread(RandomCodeMapper randomCodeMapper) {
this.randomCodeMapper = randomCodeMapper;
}
@Override
public void run() {
//⽣成订单编号并插⼊数据库
RandomCode entity=new RandomCode();
entity.ateOrderCode());
randomCodeMapper.insertSelective(entity);
}
}
其中,ateOrderCode()的⽣成逻辑是借助ThreadLocalRandom来实现的,其完整的源代码如下所⽰:
/**
* 随机数⽣成util
* @Author:debug (SteadyJack)
* @Date: 2019/6/20 21:05
**/
public class RandomUtil {
private static final SimpleDateFormat dateFormatOne=new SimpleDateFormat("yyyyMMddHHmmssSS");
private static final ThreadLocalRandom random=ThreadLocalRandom.current();
//⽣成订单编号-⽅式⼀
public static String generateOrderCode(){
//TODO:时间戳+N为随机数流⽔号
return dateFormatOne.w().toDate()) + generateNumber(4);
}
//N为随机数流⽔号
public static String generateNumber(final int num){
StringBuffer sb=new StringBuffer();
for (int i=1;i<=num;i++){
sb.Int(9));
}
String();
}
}
(2)紧接着是在 BaseController控制器 中开发⼀个请求⽅法,⽬的正是⽤来模拟前端⾼并发触发产⽣多线程并⽣成订单编号的逻辑,在这⾥我们暂且⽤1000个线程进⾏模拟,其源代码如下所⽰:
@Autowired
private RandomCodeMapper randomCodeMapper;
//测试在⾼并发下多线程⽣成订单编号-传统的随机数⽣成⽅法
@RequestMapping(value = "/code/generate/thread",method = RequestMethod.GET)
public BaseResponse codeThread(){
BaseResponse response=new BaseResponse(StatusCode.Success);
try {
ExecutorService wFixedThreadPool(10);
for (int i=0;i<1000;i++){
}
}catch (Exception e){
response=new BaseResponse(Code(),e.getMessage());
}
return response;
}
java生成随机数的方法竟然会出现“重复⽣成了重复的订单编号”!⽽且,打开数据库表进⾏观察,会发现“他娘的1000个线程
⽣成订单编号,竟然只有900多个记录”,这就说明了这么多个线程在执⾏⽣成订单编号的逻辑期间出现了“重复的订单编号”!如下图所⽰:
因此,此种基于随机数⽣成唯⼀ID或者订单编号的⽅式,我们是可以Pass掉了(当然啦,在并发量不是很⾼的情况下,这种⽅式还是阔以使⽤的,因为简单⽽且易于理解啊!)
鉴于此种“基于随机数⽣成”的⽅式在⾼并发的场景下并不符合我们的要求,接下来,我们将介绍另外⼀种⽐较流⾏的、典型的⽅式,
即“分布式唯⼀ID⽣成算法-雪花算法”来实现。
现的,简单来讲,就是直接跟机器打交道!其底层数据的存储结构(64位)如下图所⽰:
下⾯,我们就直接基于雪花算法来⽣成秒杀系统中需要的订单编号吧!
(1)同样的道理,我们⾸先定义⼀个Thread类,其run⽅法的实现逻辑是借助雪花算法⽣成订单编号并将其插⼊到数据库中。/** 基于雪花算法⽣成全局唯⼀的订单编号并插⼊数据库表中
其中,Id() 的⽅法正是采⽤雪花算法⽣成全局唯⼀的订单编号的逻辑,其完整的源代码如下所⽰:
/** * 雪花算法
* @author: zhonglinsen
* @date: 2019/5/20
*/
public class SnowFlake {
//起始的时间戳
private final static long START_STAMP = 1480166465631L;
//每⼀部分占⽤的位数
private final static long SEQUENCE_BIT = 12; //序列号占⽤的位数
private final static long MACHINE_BIT = 5; //机器标识占⽤的位数
private final static long DATA_CENTER_BIT = 5;//数据中⼼占⽤的位数
//每⼀部分的最⼤值
private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
//每⼀部分向左的位移
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
private long dataCenterId; //数据中⼼
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastStamp = -1L;//上⼀次时间戳
public SnowFlake(long dataCenterId, long machineId) {
if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
throw new IllegalArgumentException("dataCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}
//产⽣下⼀个ID
public synchronized long nextId() {
long currStamp = getNewStamp();
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论