分佈式主鍵解決方案之--Snowflake雪花算法

0--前言

  對於分佈式系統環境,主鍵ID的設計很關鍵,什麼自增intID那些是絕對不用的,比較早的時候,大部分系統都用UUID/GUID來做爲主鍵優勢是方便又能解決問題,缺點是插入時由於UUID/GUID的不規則致使每插入一條數據就須要從新排列一次,性能低下;也有人提出用UUID/GUID轉long的方式,能夠很明確的告訴你,這種方式long不能保證惟一,大併發下會有重複long出現,因此也不可取,這個主鍵設計問題曾經是不少公司系統設計的一個頭疼點,因此大部分公司願意犧牲一部分性能而直接採用簡單粗暴的UUID/GUID來做爲分佈式系統的主鍵;java

  twitter開源了一個snowflake算法,俗稱雪花算法;就是爲了解決分佈式環境下生成不一樣ID的問題;該算法會生成19位的long型有序數字,MySQL中用bigint來存儲(bigint長度爲20位);該算法應該是目前分佈式環境中主鍵ID最好的解決方案之一了;算法

1--snowflake雪花算法實現

  好,廢話很少說,直接上算法實現併發

 1 package com.anson;  2 
 3 import java.lang.management.ManagementFactory;  4 import java.net.InetAddress;  5 import java.net.NetworkInterface;  6 
 7 //雪花算法代碼實現
 8 public class IdWorker {  9     // 時間起始標記點,做爲基準,通常取系統的最近時間(一旦肯定不能變更)
 10     private final static long twepoch = 1288834974657L;  11     // 機器標識位數
 12     private final static long workerIdBits = 5L;  13     // 數據中心標識位數
 14     private final static long datacenterIdBits = 5L;  15     // 機器ID最大值
 16     private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);  17     // 數據中心ID最大值
 18     private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);  19     // 毫秒內自增位
 20     private final static long sequenceBits = 12L;  21     // 機器ID偏左移12位
 22     private final static long workerIdShift = sequenceBits;  23     // 數據中心ID左移17位
 24     private final static long datacenterIdShift = sequenceBits + workerIdBits;  25     // 時間毫秒左移22位
 26     private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;  27 
 28     private final static long sequenceMask = -1L ^ (-1L << sequenceBits);  29     /* 上次生產id時間戳 */
 30     private static long lastTimestamp = -1L;  31     // 0,併發控制
 32     private long sequence = 0L;  33 
 34     private final long workerId;  35     // 數據標識id部分
 36     private final long datacenterId;  37 
 38     public IdWorker(){  39         this.datacenterId = getDatacenterId(maxDatacenterId);  40         this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);  41  }  42     /**
 43  * @param workerId  44  * 工做機器ID  45  * @param datacenterId  46  * 序列號  47      */
 48     public IdWorker(long workerId, long datacenterId) {  49         if (workerId > maxWorkerId || workerId < 0) {  50             throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));  51  }  52         if (datacenterId > maxDatacenterId || datacenterId < 0) {  53             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));  54  }  55         this.workerId = workerId;  56         this.datacenterId = datacenterId;  57  }  58     /**
 59  * 獲取下一個ID  60  *  61  * @return
 62      */
 63     public synchronized long nextId() {  64         long timestamp = timeGen();  65         if (timestamp < lastTimestamp) {  66             throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));  67  }  68 
 69         if (lastTimestamp == timestamp) {  70             // 當前毫秒內,則+1
 71             sequence = (sequence + 1) & sequenceMask;  72             if (sequence == 0) {  73                 // 當前毫秒內計數滿了,則等待下一秒
 74                 timestamp = tilNextMillis(lastTimestamp);  75  }  76         } else {  77             sequence = 0L;  78  }  79         lastTimestamp = timestamp;  80         // ID偏移組合生成最終的ID,並返回ID
 81         long nextId = ((timestamp - twepoch) << timestampLeftShift)  82                 | (datacenterId << datacenterIdShift)  83                 | (workerId << workerIdShift) | sequence;  84 
 85         return nextId;  86  }  87 
 88     private long tilNextMillis(final long lastTimestamp) {  89         long timestamp = this.timeGen();  90         while (timestamp <= lastTimestamp) {  91             timestamp = this.timeGen();  92  }  93         return timestamp;  94  }  95 
 96     private long timeGen() {  97         return System.currentTimeMillis();  98  }  99 
100     /**
101  * <p> 102  * 獲取 maxWorkerId 103  * </p> 104      */
105     protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) { 106         StringBuffer mpid = new StringBuffer(); 107  mpid.append(datacenterId); 108         String name = ManagementFactory.getRuntimeMXBean().getName(); 109         if (!name.isEmpty()) { 110             /*
111  * GET jvmPid 112              */
113             mpid.append(name.split("@")[0]); 114  } 115         /*
116  * MAC + PID 的 hashcode 獲取16個低位 117          */
118         return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); 119  } 120 
121     /**
122  * <p> 123  * 數據標識id部分 124  * </p> 125      */
126     protected static long getDatacenterId(long maxDatacenterId) { 127         long id = 0L; 128         try { 129             InetAddress ip = InetAddress.getLocalHost(); 130             NetworkInterface network = NetworkInterface.getByInetAddress(ip); 131             if (network == null) { 132                 id = 1L; 133             } else { 134                 byte[] mac = network.getHardwareAddress(); 135                 id = ((0x000000FF & (long) mac[mac.length - 1]) 136                         | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6; 137                 id = id % (maxDatacenterId + 1); 138  } 139         } catch (Exception e) { 140             System.out.println(" getDatacenterId: " + e.getMessage()); 141  } 142         return id; 143  } 144 }

 

3--測試

package com.anson; /** * @description: TODO * @author: anson * @Date: 2019/10/7 22:16 * @version: 1.0 */
public class snow { public static  void main(String[] args) throws Exception { try { IdWorker idw = new IdWorker(1,1); long ids = idw.nextId(); for(int i=0;i<10000;i++) { ids = idw.nextId(); System.out.println(ids); } } catch (Exception ex) { } } }

結果以下圖:app

 

 程序生成了19位的有序數字,這個既解決了分佈式ID生成惟一性問題,也解決了性能問題,建議系統ID設計都採用該算法生成。less

相關文章
相關標籤/搜索