因此,此種基於隨機數生成唯一ID或者訂單編號的方式,我們是可以Pass掉了(當然啦,在併發量不是很高的情況下,這種方式還是闊以使用的,因爲簡單而且易於理解啊!),鑑於此種“基於隨機數生成”的方式在高併發的場景下並不符合我們的要求,接下來,我們將介紹另外一種比較流行的、典型的方式,即“分佈式唯一ID生成算法-雪花算法”來實現。
對於“雪花算法”的介紹,各位小夥伴可以參考Github上的這一鏈接,我覺得講得還是挺清晰的:https://github.com/souyunku/SnowFlake ,詳細的Debug在這裏就不贅述了,下面截取了部分概述:
SnowFlake算法在分佈式的環境下,之所以能高效率的生成唯一的ID,我覺得其中很重要的一點在於其底層的實現是通過“位運算”來實現的,簡單來講,就是直接跟機器打交道!其底層數據的存儲結構(64位)如下圖所示:
下面,我們就直接基於雪花算法來生成秒殺系統中需要的訂單編號吧!
(1)同樣的道理,我們首先定義一個Thread類,其run方法的實現邏輯是藉助雪花算法生成訂單編號並將其插入到數據庫中。
/** 基於雪花算法生成全局唯一的訂單編號並插入數據庫表中 * @Author:debug (SteadyJack) * @Date: 2019/7/11 10:30 **/ public class CodeGenerateSnowThread implements Runnable{ private static final SnowFlake SNOW_FLAKE=new SnowFlake(2,3); private RandomCodeMapper randomCodeMapper; public CodeGenerateSnowThread(RandomCodeMapper randomCodeMapper) { this.randomCodeMapper = randomCodeMapper; } @Override public void run() { RandomCode entity=new RandomCode(); //採用雪花算法生成訂單編號 entity.setCode(String.valueOf(SNOW_FLAKE.nextId())); randomCodeMapper.insertSelective(entity); } }
其中,SNOW_FLAKE.nextId() 的方法正是採用雪花算法生成全局唯一的訂單編號的邏輯,其完整的源代碼如下所示:
/** * 雪花算法 * @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(); if (currStamp < lastStamp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currStamp == lastStamp) { //相同毫秒內,序列號自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列數已經達到最大 if (sequence == 0L) { currStamp = getNextMill(); } } else { //不同毫秒內,序列號置爲0 sequence = 0L; } lastStamp = currStamp; return (currStamp - START_STAMP) << TIMESTAMP_LEFT //時間戳部分 | dataCenterId << DATA_CENTER_LEFT //數據中心部分 | machineId << MACHINE_LEFT //機器標識部分 | sequence; //序列號部分 } private long getNextMill() { long mill = getNewStamp(); while (mill <= lastStamp) { mill = getNewStamp(); } return mill; } private long getNewStamp() { return System.currentTimeMillis(); } }
(2)緊接着,我們在BaseController中開發一個請求方法,用於模擬前端觸發高併發產生多線程搶單的場景。
/** * 測試在高併發下多線程生成訂單編號-雪花算法 * @return */ @RequestMapping(value = "/code/generate/thread/snow",method = RequestMethod.GET) public BaseResponse codeThreadSnowFlake(){ BaseResponse response=new BaseResponse(StatusCode.Success); try { ExecutorService executorService=Executors.newFixedThreadPool(10); for (int i=0;i<1000;i++){ executorService.execute(new CodeGenerateSnowThread(randomCodeMapper)); } }catch (Exception e){ response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage()); } return response; }
(3)完了之後,我們採用Postman發起一個Http的Get請求,其請求鏈接如下所示:http://127.0.0.1:8092/kill/base/code/generate/thread/snow ,觀察控制檯的輸出信息,可以看到“一片安然的景象”,再觀察數據庫表的記錄,可以發現,1000個線程成功觸發生成了1000個對應的訂單編號,如下圖所示:
除此之外,各位小夥伴還可以將線程數從1000調整爲10000、100000甚至1000000,然後觀察控制檯的輸出信息以及數據庫表的記錄等等。
Debug親測了1w跟10w的場景下是木有問題的,100w的線程數的測試就交給各位小夥伴去試試了(時間比較長,要有心理準備哦!)至此,我們就可以將雪花算法生成全局唯一的訂單編號的邏輯應用到我們的“秒殺處理邏輯”中,即其代碼(在KillService的commonRecordKillSuccessInfo方法中)如下所示:
ItemKillSuccess entity=new ItemKillSuccess(); String orderNo=String.valueOf(snowFlake.nextId());//雪花算法 entity.setCode(orderNo); //其他代碼省略
補充:
1、目前,這一秒殺系統的整體構建與代碼實戰已經全部完成了,完整的源代碼數據庫地址可以來這裏下載:https://gitee.com/steadyjack/SpringBoot-SecondKill 記得Fork跟Star啊!!!