Java生成訂單號(hào)或唯一id的高并發(fā)方案(4種方法)
1、直接使用uuid
public static String getUUID() { String replaceUUID = UUID.randomUUID().toString().replace("-", ""); return replaceUUID; }
但由于生成的數(shù)據(jù)沒(méi)有規(guī)律性,并且太長(zhǎng);
測(cè)試:循環(huán)1000w次
測(cè)試代碼:
public static void main(String[] args) { long startTime = System.currentTimeMillis(); Set set=new HashSet<>(); for(int i=0;i<10000000;i++){ String uuid = getUUID(); System.out.println("uuid---"+i+"======="+uuid); set.add(uuid); } long endTime = System.currentTimeMillis(); System.out.println("set.size():"+set.size()); System.out.println("endTime-startTime:"+(endTime-startTime)); }
控制臺(tái)提示:
2、用時(shí)間(精確到毫秒)+隨機(jī)數(shù)
//時(shí)間(精確到毫秒) DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); String localDate = LocalDateTime.now().format(ofPattern); //隨機(jī)數(shù) String randomNumeric = RandomStringUtils.randomNumeric(8);
for循環(huán)1000w次,發(fā)現(xiàn)重復(fù)數(shù)據(jù)太多。因此光靠隨機(jī)數(shù)并不可靠。
3、使用 時(shí)間(精確到毫秒)+隨機(jī)數(shù)+用戶id(業(yè)務(wù)id)
注意:如果是類似用戶id,項(xiàng)目當(dāng)中集成了權(quán)限框架,使用工具類獲取即可,就不用傳參了
/** * 生成訂單號(hào)(25位):時(shí)間(精確到毫秒)+3位隨機(jī)數(shù)+5位用戶id */ public static synchronized String getOrderNum(Long userId) { //時(shí)間(精確到毫秒) DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); String localDate = LocalDateTime.now().format(ofPattern); //3位隨機(jī)數(shù) String randomNumeric = RandomStringUtils.randomNumeric(3); //5位用戶id int subStrLength = 5; String sUserId = userId.toString(); int length = sUserId.length(); String str; if (length >= subStrLength) { str = sUserId.substring(length - subStrLength, length); } else { str = String.format("%0" + subStrLength + "d", userId); } String orderNum = localDate + randomNumeric + str; log.info("訂單號(hào):{}", orderNum); return orderNum; }
在2的基礎(chǔ)上改造,加入用戶的id等其他的業(yè)務(wù)id。
4.Java實(shí)現(xiàn)Snowflake算法的方案(高并發(fā)下,推薦使用這個(gè))
package com.lucifer.order.util.idgenerate; /** * Twitter_Snowflake<br> * SnowFlake的結(jié)構(gòu)如下(每部分用-分開):<br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br> * 1位標(biāo)識(shí),由于long基本類型在Java中是帶符號(hào)的,最高位是符號(hào)位,正數(shù)是0,負(fù)數(shù)是1,所以id一般是正數(shù),最高位是0<br> * 41位時(shí)間截(毫秒級(jí)),注意,41位時(shí)間截不是存儲(chǔ)當(dāng)前時(shí)間的時(shí)間截,而是存儲(chǔ)時(shí)間截的差值(當(dāng)前時(shí)間截 - 開始時(shí)間截) * 得到的值),這里的的開始時(shí)間截,一般是我們的id生成器開始使用的時(shí)間,由我們程序來(lái)指定的(如下下面程序IdWorker類的startTime屬性)。41位的時(shí)間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br> * 10位的數(shù)據(jù)機(jī)器位,可以部署在1024個(gè)節(jié)點(diǎn),包括5位datacenterId和5位workerId<br> * 12位序列,毫秒內(nèi)的計(jì)數(shù),12位的計(jì)數(shù)順序號(hào)支持每個(gè)節(jié)點(diǎn)每毫秒(同一機(jī)器,同一時(shí)間截)產(chǎn)生4096個(gè)ID序號(hào)<br> * 加起來(lái)剛好64位,為一個(gè)Long型。<br> * SnowFlake的優(yōu)點(diǎn)是,整體上按照時(shí)間自增排序,并且整個(gè)分布式系統(tǒng)內(nèi)不會(huì)產(chǎn)生ID碰撞(由數(shù)據(jù)中心ID和機(jī)器ID作區(qū)分),并且效率較高,經(jīng)測(cè)試,SnowFlake每秒能夠產(chǎn)生26萬(wàn)ID左右。 * * @author Lucifer */ public class SnowFlake { // ==============================Fields=========================================== /** * 開始時(shí)間截 (2018-07-03) */ private final long twepoch = 1530607760000L; /** * 機(jī)器id所占的位數(shù) */ private final long workerIdBits = 5L; /** * 數(shù)據(jù)標(biāo)識(shí)id所占的位數(shù) */ private final long datacenterIdBits = 5L; /** * 支持的最大機(jī)器id,結(jié)果是31 (這個(gè)移位算法可以很快的計(jì)算出幾位二進(jìn)制數(shù)所能表示的最大十進(jìn)制數(shù)) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** * 支持的最大數(shù)據(jù)標(biāo)識(shí)id,結(jié)果是31 */ private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); /** * 序列在id中占的位數(shù) */ private final long sequenceBits = 12L; /** * 機(jī)器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** * 數(shù)據(jù)標(biāo)識(shí)id向左移17位(12+5) */ private final long datacenterIdShift = sequenceBits + workerIdBits; /** * 時(shí)間截向左移22位(5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; /** * 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** * 工作機(jī)器ID(0~31) */ private long workerId; /** * 數(shù)據(jù)中心ID(0~31) */ private long datacenterId; /** * 毫秒內(nèi)序列(0~4095) */ private long sequence = 0L; /** * 上次生成ID的時(shí)間截 */ private long lastTimestamp = -1L; //==============================Constructors===================================== /** * 構(gòu)造函數(shù) * * @param workerId 工作ID (0~31) * @param datacenterId 數(shù)據(jù)中心ID (0~31) */ public SnowFlake(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } // ==============================Methods========================================== /** * 獲得下一個(gè)ID (該方法是線程安全的) * * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); //如果當(dāng)前時(shí)間小于上一次ID生成的時(shí)間戳,說(shuō)明系統(tǒng)時(shí)鐘回退過(guò)這個(gè)時(shí)候應(yīng)當(dāng)拋出異常 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //如果是同一時(shí)間生成的,則進(jìn)行毫秒內(nèi)序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; //毫秒內(nèi)序列溢出 if (sequence == 0) { //阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳 timestamp = tilNextMillis(lastTimestamp); } } //時(shí)間戳改變,毫秒內(nèi)序列重置 else { sequence = 0L; } //上次生成ID的時(shí)間截 lastTimestamp = timestamp; //移位并通過(guò)或運(yùn)算拼到一起組成64位的ID return (((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence); } /** * 阻塞到下一個(gè)毫秒,直到獲得新的時(shí)間戳 * * @param lastTimestamp 上次生成ID的時(shí)間截 * @return 當(dāng)前時(shí)間戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒為單位的當(dāng)前時(shí)間 * * @return 當(dāng)前時(shí)間(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } //==============================Test============================================= /** * 測(cè)試 */ public static void main(String[] args) { long startTime = System.currentTimeMillis(); SnowFlake idWorker = new SnowFlake(0, 0); Set set = new HashSet(); for (int i = 0; i < 10000000; i++) { long id = idWorker.nextId(); set.add(id); System.out.println("id----"+i+":"+id); } long endTime = System.currentTimeMillis(); System.out.println("set.size():" + set.size()); System.out.println("endTime-startTime:" + (endTime - startTime)); } }
也可以在雪花算法生成的id的基礎(chǔ)上拼接日期,不過(guò)性能有所損耗。
public static String timestampConversionDate(String param) { Instant timestamp = Instant.ofEpochMilli(new Long(param)); System.out.println("timestamp:"+param); LocalDateTime localDateTime = LocalDateTime.ofInstant(timestamp, ZoneId.systemDefault()); String format = localDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd")); return format; }
測(cè)試1:
循環(huán)1000w次,發(fā)現(xiàn)并無(wú)重復(fù)
測(cè)試2:100個(gè)線程,每個(gè)線程負(fù)責(zé)生成10w個(gè)id
//多線程測(cè)試 public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); CountDownLatch countDownLatch=new CountDownLatch(10000000); final SnowFlake idWorker = new SnowFlake(0, 0); Set set = Collections.synchronizedSet(new HashSet()); for (int i = 0; i < 100; i++) { Thread thread = new Thread(() -> { for (int i1 = 0; i1 < 100000; i1++) { long id = idWorker.nextId(); System.out.println("id:"+id); set.add(id); countDownLatch.countDown(); } }); thread.start(); } countDownLatch.await(); long endTime = System.currentTimeMillis(); System.out.println("set.size():" + set.size()); System.out.println("endTime-startTime:" + (endTime - startTime)); }
到此這篇關(guān)于Java生成訂單號(hào)或唯一id的高并發(fā)方案(4種方法)的文章就介紹到這了,更多相關(guān)Java生成訂單號(hào)或唯一id內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(51)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-08-08Springboot為什么加載不上application.yml的配置文件
這篇文章主要介紹了Springboot為什么加載不上application.yml的配置文件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10java使用dbcp2數(shù)據(jù)庫(kù)連接池
這篇文章主要為大家詳細(xì)介紹了java使用dbcp2數(shù)據(jù)庫(kù)連接池的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10SpringMvc自動(dòng)裝箱及GET請(qǐng)求參數(shù)原理解析
這篇文章主要介紹了SpringMvc自動(dòng)裝箱及GET請(qǐng)求參數(shù)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Java基于swing實(shí)現(xiàn)的彈球游戲代碼
這篇文章主要介紹了Java基于swing實(shí)現(xiàn)的彈球游戲代碼,包含了窗體界面設(shè)計(jì)與游戲的邏輯功能處理,具有不錯(cuò)的參考借鑒價(jià)值,需要的朋友可以參考下2014-11-11