0--前言 對於分散式系統環境,主鍵ID的設計很關鍵,什麼自增intID那些是絕對不用的,比較早的時候,大部分系統都用UUID/GUID來作為主鍵,優點是方便又能解決問題,缺點是插入時因為UUID/GUID的不規則導致每插入一條數據就需要重新排列一次,性能低下;也有人提出用UUID/GUID轉lon ...
0--前言
對於分散式系統環境,主鍵ID的設計很關鍵,什麼自增intID那些是絕對不用的,比較早的時候,大部分系統都用UUID/GUID來作為主鍵,優點是方便又能解決問題,缺點是插入時因為UUID/GUID的不規則導致每插入一條數據就需要重新排列一次,性能低下;也有人提出用UUID/GUID轉long的方式,可以很明確的告訴你,這種方式long不能保證唯一,大併發下會有重覆long出現,所以也不可取,這個主鍵設計問題曾經是很多公司系統設計的一個頭疼點,所以大部分公司願意犧牲一部分性能而直接採用簡單粗暴的UUID/GUID來作為分散式系統的主鍵;
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) { } } }
結果如下圖:
程式生成了19位的有序數字,這個既解決了分散式ID生成唯一性問題,也解決了性能問題,建議系統ID設計都採用該演算法生成。