java算法之靜態(tài)內(nèi)部類實現(xiàn)雪花算法
在生成表主鍵ID時,我們可以考慮主鍵自增 或者 UUID,但它們都有很明顯的缺點
主鍵自增:1、自增ID容易被爬蟲遍歷數(shù)據(jù)。2、分表分庫會有ID沖突。
UUID: 1、太長,并且有索引碎片,索引多占用空間的問題 2、無序。
雪花算法就很適合在分布式場景下生成唯一ID,它既可以保證唯一又可以排序。為了提高生產(chǎn)雪花ID的效率,
在這里面數(shù)據(jù)的運算都采用的是位運算
一、概念1、原理SnowFlake算法生成ID的結(jié)果是一個64bit大小的整數(shù),它的結(jié)構(gòu)如下圖:
算法描述:
1bit 因為二進制中最高位是符號位,1表示負(fù)數(shù),0表示正數(shù)。生成的ID都是正整數(shù),所以最高位固定為0。
41bit-時間戳 精確到毫秒級,41位的長度可以使用69年。時間位還有一個很重要的作用是可以根據(jù)時間進行排序。
10bit-工作機器id 10位的機器標(biāo)識,10位的長度最多支持部署1024個節(jié)點。
12bit-序列號 序列號即一系列的自增id,可以支持同一節(jié)點同一毫秒生成多個ID序號。12位(bit)可以表示的最大正整數(shù)是,即可以用0、1、2、3、....4094這4095個數(shù)字,來表示同一機器同一時間截(毫秒)內(nèi)產(chǎn)生的4095個ID序號。
說明 由于在Java中64bit的整數(shù)是long類型,所以在Java中SnowFlake算法生成的id就是long來存儲的。
二、靜態(tài)類部類單例模式生產(chǎn)雪花ID代碼下面生成雪花ID的代碼可以用于線上分布式項目中來生成分布式主鍵ID,因為設(shè)計采用的靜態(tài)內(nèi)部類的單例模式,通過加synchronized鎖來保證在
同一個服務(wù)器線程安全。至于不同服務(wù)器其實是不相關(guān)的,因為它們的機器碼是不一致的,所以就算同一時刻兩臺服務(wù)器都產(chǎn)生了雪花ID,那也不會一樣的。
1、代碼public class SnowIdUtils { /** * 私有的 靜態(tài)內(nèi)部類 */ private static class SnowFlake {/** * 內(nèi)部類對象(單例模式) */private static final SnowIdUtils.SnowFlake SNOW_FLAKE = new SnowIdUtils.SnowFlake();/** * 起始的時間戳 */private final long START_TIMESTAMP = 1557489395327L;/** * 序列號占用位數(shù) */private final long SEQUENCE_BIT = 12;/** * 機器標(biāo)識占用位數(shù) */private final long MACHINE_BIT = 10;/** * 時間戳位移位數(shù) */private final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;/** * 最大序列號 (4095) */private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);/** * 最大機器編號 (1023) */private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);/** * 生成id機器標(biāo)識部分 */private long machineIdPart;/** * 序列號 */private long sequence = 0L;/** * 上一次時間戳 */private long lastStamp = -1L;/** * 構(gòu)造函數(shù)初始化機器編碼 */private SnowFlake() { //模擬這里獲得本機機器編碼 long localIp = 4321; //localIp & MAX_MACHINE_ID最大不會超過1023,在左位移12位 machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT;}/** * 獲取雪花ID */public synchronized long nextId() { long currentStamp = timeGen(); //避免機器時鐘回?fù)? while (currentStamp < lastStamp) {// //服務(wù)器時鐘被調(diào)整了,ID生成器停止服務(wù).throw new RuntimeException(String.format('時鐘已經(jīng)回?fù)? Refusing to generate id for %d milliseconds', lastStamp - currentStamp)); } if (currentStamp == lastStamp) {// 每次+1sequence = (sequence + 1) & MAX_SEQUENCE;// 毫秒內(nèi)序列溢出if (sequence == 0) { // 阻塞到下一個毫秒,獲得新的時間戳 currentStamp = getNextMill();} } else {//不同毫秒內(nèi),序列號置0sequence = 0L; } lastStamp = currentStamp; //時間戳部分+機器標(biāo)識部分+序列號部分 return (currentStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | machineIdPart | sequence;}/** * 阻塞到下一個毫秒,直到獲得新的時間戳 */private long getNextMill() { long mill = timeGen(); // while (mill <= lastStamp) {mill = timeGen(); } return mill;}/** * 返回以毫秒為單位的當(dāng)前時間 */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()); } /** * 測試 */ public static void main(String[] args) throws InterruptedException {//計時開始時間long start = System.currentTimeMillis();//讓100個線程同時進行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個線程 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個線程執(zhí)行結(jié)束后,在走下面輸出信息latch.await();log.info('生成20萬條雪花ID總用時={}', System.currentTimeMillis() - start); }}2、測試結(jié)果
從圖中我們可以得出
1、在100個線程并發(fā)下,生成20萬條雪花ID的時間大概在1.6秒左右,所有所性能還是蠻ok的。
2、生成20萬條雪花ID并沒有一條相同的ID,因為有一條就會拋出異常了。
3、為什么說41位時間戳最長只能有69年我們思考41的二進制,最大值也就41位都是1,也就是也就是說41位可以表示
所以說雪花算法生成的ID,只能保證69年內(nèi)不會重復(fù),如果超過69年的話,那就考慮換個服務(wù)器部署吧,并且要保證該服務(wù)器的ID和之前都沒有重復(fù)過。
以上就是java算法之靜態(tài)內(nèi)部類實現(xiàn)雪花算法的詳細(xì)內(nèi)容,更多關(guān)于java算法的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
