java算法之靜態(tài)內(nèi)部類實(shí)現(xiàn)雪花算法
概述
在生成表主鍵ID時(shí),我們可以考慮主鍵自增 或者 UUID,但它們都有很明顯的缺點(diǎn)
主鍵自增:1、自增ID容易被爬蟲遍歷數(shù)據(jù)。2、分表分庫會(huì)有ID沖突。
UUID: 1、太長,并且有索引碎片,索引多占用空間的問題 2、無序。
雪花算法就很適合在分布式場(chǎng)景下生成唯一ID,它既可以保證唯一又可以排序。為了提高生產(chǎn)雪花ID的效率,
在這里面數(shù)據(jù)的運(yùn)算都采用的是位運(yùn)算
一、概念
1、原理
SnowFlake算法生成ID的結(jié)果是一個(gè)64bit大小的整數(shù),它的結(jié)構(gòu)如下圖:

算法描述:
1bit 因?yàn)槎M(jìn)制中最高位是符號(hào)位,1表示負(fù)數(shù),0表示正數(shù)。生成的ID都是正整數(shù),所以最高位固定為0。
41bit-時(shí)間戳 精確到毫秒級(jí),41位的長度可以使用69年。時(shí)間位還有一個(gè)很重要的作用是可以根據(jù)時(shí)間進(jìn)行排序。
10bit-工作機(jī)器id 10位的機(jī)器標(biāo)識(shí),10位的長度最多支持部署1024個(gè)節(jié)點(diǎn)。
12bit-序列號(hào) 序列號(hào)即一系列的自增id,可以支持同一節(jié)點(diǎn)同一毫秒生成多個(gè)ID序號(hào)。
12位(bit)可以表示的最大正整數(shù)是,即可以用0、1、2、3、....4094這4095個(gè)數(shù)字,來表示同一機(jī)器同一時(shí)間截(毫秒)內(nèi)產(chǎn)生的4095個(gè)ID序號(hào)。
說明 由于在Java中64bit的整數(shù)是long類型,所以在Java中SnowFlake算法生成的id就是long來存儲(chǔ)的。
二、靜態(tài)類部類單例模式生產(chǎn)雪花ID代碼
下面生成雪花ID的代碼可以用于線上分布式項(xiàng)目中來生成分布式主鍵ID,因?yàn)樵O(shè)計(jì)采用的靜態(tài)內(nèi)部類的單例模式,通過加synchronized鎖來保證在
同一個(gè)服務(wù)器線程安全。至于不同服務(wù)器其實(shí)是不相關(guān)的,因?yàn)樗鼈兊臋C(jī)器碼是不一致的,所以就算同一時(shí)刻兩臺(tái)服務(wù)器都產(chǎn)生了雪花ID,那也不會(huì)一樣的。
1、代碼
public class SnowIdUtils {
/**
* 私有的 靜態(tài)內(nèi)部類
*/
private static class SnowFlake {
/**
* 內(nèi)部類對(duì)象(單例模式)
*/
private static final SnowIdUtils.SnowFlake SNOW_FLAKE = new SnowIdUtils.SnowFlake();
/**
* 起始的時(shí)間戳
*/
private final long START_TIMESTAMP = 1557489395327L;
/**
* 序列號(hào)占用位數(shù)
*/
private final long SEQUENCE_BIT = 12;
/**
* 機(jī)器標(biāo)識(shí)占用位數(shù)
*/
private final long MACHINE_BIT = 10;
/**
* 時(shí)間戳位移位數(shù)
*/
private final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;
/**
* 最大序列號(hào) (4095)
*/
private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
/**
* 最大機(jī)器編號(hào) (1023)
*/
private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);
/**
* 生成id機(jī)器標(biāo)識(shí)部分
*/
private long machineIdPart;
/**
* 序列號(hào)
*/
private long sequence = 0L;
/**
* 上一次時(shí)間戳
*/
private long lastStamp = -1L;
/**
* 構(gòu)造函數(shù)初始化機(jī)器編碼
*/
private SnowFlake() {
//模擬這里獲得本機(jī)機(jī)器編碼
long localIp = 4321;
//localIp & MAX_MACHINE_ID最大不會(huì)超過1023,在左位移12位
machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT;
}
/**
* 獲取雪花ID
*/
public synchronized long nextId() {
long currentStamp = timeGen();
//避免機(jī)器時(shí)鐘回?fù)?
while (currentStamp < lastStamp) {
// //服務(wù)器時(shí)鐘被調(diào)整了,ID生成器停止服務(wù).
throw new RuntimeException(String.format("時(shí)鐘已經(jīng)回?fù)? Refusing to generate id for %d milliseconds", lastStamp - currentStamp));
}
if (currentStamp == lastStamp) {
// 每次+1
sequence = (sequence + 1) & MAX_SEQUENCE;
// 毫秒內(nèi)序列溢出
if (sequence == 0) {
// 阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳
currentStamp = getNextMill();
}
} else {
//不同毫秒內(nèi),序列號(hào)置0
sequence = 0L;
}
lastStamp = currentStamp;
//時(shí)間戳部分+機(jī)器標(biāo)識(shí)部分+序列號(hào)部分
return (currentStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | machineIdPart | sequence;
}
/**
* 阻塞到下一個(gè)毫秒,直到獲得新的時(shí)間戳
*/
private long getNextMill() {
long mill = timeGen();
//
while (mill <= lastStamp) {
mill = timeGen();
}
return mill;
}
/**
* 返回以毫秒為單位的當(dāng)前時(shí)間
*/
protected long timeGen() {
return System.currentTimeMillis();
}
}
/**
* 獲取long類型雪花ID
*/
public static long uniqueLong() {
return SnowIdUtils.SnowFlake.SNOW_FLAKE.nextId();
}
/**
* 獲取String類型雪花ID
*/
public static String uniqueLongHex() {
return String.format("%016x", uniqueLong());
}
/**
* 測(cè)試
*/
public static void main(String[] args) throws InterruptedException {
//計(jì)時(shí)開始時(shí)間
long start = System.currentTimeMillis();
//讓100個(gè)線程同時(shí)進(jìn)行
final CountDownLatch latch = new CountDownLatch(100);
//判斷生成的20萬條記錄是否有重復(fù)記錄
final Map<Long, Integer> map = new ConcurrentHashMap();
for (int i = 0; i < 100; i++) {
//創(chuàng)建100個(gè)線程
new Thread(() -> {
for (int s = 0; s < 2000; s++) {
long snowID = SnowIdUtils.uniqueLong();
log.info("生成雪花ID={}",snowID);
Integer put = map.put(snowID, 1);
if (put != null) {
throw new RuntimeException("主鍵重復(fù)");
}
}
latch.countDown();
}).start();
}
//讓上面100個(gè)線程執(zhí)行結(jié)束后,在走下面輸出信息
latch.await();
log.info("生成20萬條雪花ID總用時(shí)={}", System.currentTimeMillis() - start);
}
}
2、測(cè)試結(jié)果

從圖中我們可以得出
1、在100個(gè)線程并發(fā)下,生成20萬條雪花ID的時(shí)間大概在1.6秒左右,所有所性能還是蠻ok的。
2、生成20萬條雪花ID并沒有一條相同的ID,因?yàn)橛幸粭l就會(huì)拋出異常了。
3、為什么說41位時(shí)間戳最長只能有69年
我們思考41的二進(jìn)制,最大值也就41位都是1,也就是也就是說41位可以表示個(gè)毫秒的值,轉(zhuǎn)化成單位年則是
年
我們可以通過代碼泡一下就知道了。
public static void main(String[] args) {
//41位二進(jìn)制最小值
String minTimeStampStr = "00000000000000000000000000000000000000000";
//41位二進(jìn)制最大值
String maxTimeStampStr = "11111111111111111111111111111111111111111";
//轉(zhuǎn)10進(jìn)制
long minTimeStamp = new BigInteger(minTimeStampStr, 2).longValue();
long maxTimeStamp = new BigInteger(maxTimeStampStr, 2).longValue();
//一年總共多少毫秒
long oneYearMills = 1L * 1000 * 60 * 60 * 24 * 365;
//算出最大可以多少年
System.out.println((maxTimeStamp - minTimeStamp) / oneYearMills);
}
運(yùn)行結(jié)果

所以說雪花算法生成的ID,只能保證69年內(nèi)不會(huì)重復(fù),如果超過69年的話,那就考慮換個(gè)服務(wù)器部署吧,并且要保證該服務(wù)器的ID和之前都沒有重復(fù)過。
以上就是java算法之靜態(tài)內(nèi)部類實(shí)現(xiàn)雪花算法的詳細(xì)內(nèi)容,更多關(guān)于java算法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java實(shí)現(xiàn)雪花算法(snowflake)
- Java實(shí)現(xiàn)雪花算法的原理
- 帶你入門java雪花算法原理
- Java實(shí)現(xiàn)雪花算法的原理和實(shí)戰(zhàn)教程
- java實(shí)現(xiàn)雪花算法ID生成器工具類
- Java雪花算法的原理和實(shí)現(xiàn)方法
- Java雪花算法的實(shí)現(xiàn)詳解
- Java使用雪花算法生成唯一ID的實(shí)現(xiàn)示例
- java中使用雪花算法(Snowflake)為分布式系統(tǒng)生成全局唯一ID代碼示例
- Java分布式ID中Snowflake雪花算法應(yīng)用實(shí)現(xiàn)
相關(guān)文章
Mybatis中的@Param及動(dòng)態(tài)SQL詳解
這篇文章主要介紹了Mybatis中的@Param及動(dòng)態(tài)SQL詳解,@Param是MyBatis所提供的作為Dao層的注解,作用是用于傳遞參數(shù),從而可以與SQL中的的字段名相對(duì)應(yīng),需要的朋友可以參考下2023-10-10
IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】
這篇文章主要介紹了IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
SpringBoot使用JUL實(shí)現(xiàn)日志記錄功能
在SpringBoot中,我們可以使用多種日志框架進(jìn)行日志記錄,其中,JUL(Java Util Logging)是Java平臺(tái)自帶的日志框架,它提供了簡單的 API 和配置,可以輕松地進(jìn)行日志記錄,本文將介紹如何在 SpringBoot中使用JUL進(jìn)行日志記錄,并提供示例代碼2023-06-06
SpringBoot采用AJAX實(shí)現(xiàn)異步發(fā)布帖子詳解
Ajax是一種web應(yīng)用技術(shù),可以借助客戶端腳本(javascript)與服務(wù)端應(yīng)用進(jìn)行異步通訊,獲取服務(wù)端數(shù)據(jù)以后,可以進(jìn)行局部刷新,進(jìn)而提高數(shù)據(jù)的響應(yīng)和渲染速度。所有的Ajax請(qǐng)求都會(huì)基于DOM(HTML元素)事件,通過XHR(XMLHttpRequest)對(duì)象實(shí)現(xiàn)與服務(wù)端異步通訊局部更新2022-08-08
Java面向?qū)ο蠡A(chǔ)知識(shí)之委托和lambda
這篇文章主要介紹了Java面向?qū)ο蟮闹泻?lambda,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有很好的幫助,需要的朋友可以參考下2021-11-11
深入學(xué)習(xí)java中的Groovy 和 Scala 類
本文將探討三種下一代 JVM 語言:Groovy、Scala 和 Clojure,比較并對(duì)比新的功能和范例,讓 Java 開發(fā)人員對(duì)自己近期的未來發(fā)展有大體的認(rèn)識(shí)。,需要的朋友可以參考下2019-06-06
idea創(chuàng)建maven項(xiàng)目速度慢的三種解決方案
這篇文章主要介紹了idea創(chuàng)建maven項(xiàng)目速度慢的三種解決方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01

