一般情況,實現全局唯一ID,有三種方案,分別是通過中間件方式、UUID、雪花算法。
方案一,通過中間件方式,可以是把數據庫或者redis緩存作為媒介,從中間件獲取ID。這種呢,優點是可以體現全局的遞增趨勢(優點只能想到這個),缺點呢,倒是一大堆,比如,依賴中間件,假如中間件掛了,就不能提供服務了;依賴中間件的寫入和事務,會影響效率;數據量大了的話,你還得考慮部署集群,考慮走代理。這樣的話,感覺問題復雜化了
方案二,通過UUID的方式,java.util.UUID就提供了獲取UUID的方法,使用UUID來實現全局唯一ID,優點是操作簡單,也能實現全局唯一的效果,缺點呢,就是不能體現全局視野的遞增趨勢;太長了,UUID是32位,有點浪費;最重要的,是插入的效率低,因為呢,我們使用mysql的話,一般都是B+tree的結構來存儲索引,假如是數據庫自帶的那種主鍵自增,節點滿了,會裂變出新的節點,新節點滿了,再去裂變新的節點,這樣利用率和效率都很高。而UUID是無序的,會造成中間節點的分裂,也會造成不飽和的節點,插入的效率自然就比較低下了。
方案三,基於redis生成全局id策略,因為Redis是單線的天生保證原子性,可以使用原子性操作INCR和INCRBY來實現,注意在Redis集群情況下,同MySQL一樣需要設置不同的增長步長,同時key一定要設置有效期,可以使用Redis集群來獲取更高的吞吐量
方案四,通過snowflake算法如下:
SnowFlake算法生成id的結果是一個64bit大小的整數,它的結構如下圖:
1位
,不用。二進制中最高位為1的都是負數,但是我們生成的id一般都使用整數,所以這個最高位固定是0-
41位
,用來記錄時間戳(毫秒)。- 41位可以表示$2^{41}-1$個數字,
- 如果只用來表示正整數(計算機中正數包含0),可以表示的數值范圍是:0 至 $2^{41}-1$,減1是因為可表示的數值范圍是從0開始算的,而不是1。
- 也就是說41位可以表示$2^{41}-1$個毫秒的值,轉化成單位年則是$(2^{41}-1) / (1000 * 60 * 60 * 24 * 365) = 69$年
-
10位
,用來記錄工作機器id。- 可以部署在$2^{10} = 1024$個節點,包括
5位datacenterId
和5位workerId
5位(bit)
可以表示的最大正整數是$2^{5}-1 = 31$,即可以用0、1、2、3、....31這32個數字,來表示不同的datecenterId或workerId
- 可以部署在$2^{10} = 1024$個節點,包括
-
12位
,序列號,用來記錄同毫秒內產生的不同id。12位(bit)
可以表示的最大正整數是$2^{12}-1 = 4095$,即可以用0、1、2、3、....4094這4095個數字,來表示同一機器同一時間截(毫秒)內產生的4095個ID序號
由於在Java中64bit的整數是long類型,所以在Java中SnowFlake算法生成的id就是long來存儲的。
SnowFlake可以保證:
- 所有生成的id按時間趨勢遞增
- 整個分布式系統內不會產生重復id(因為有datacenterId和workerId來做區分)
以下是Twitter官方原版的,用Scala寫的:

1 /** Copyright 2010-2012 Twitter, Inc.*/ 2 package com.twitter.service.snowflake 3 4 import com.twitter.ostrich.stats.Stats 5 import com.twitter.service.snowflake.gen._ 6 import java.util.Random 7 import com.twitter.logging.Logger 8 9 /** 10 * An object that generates IDs. 11 * This is broken into a separate class in case 12 * we ever want to support multiple worker threads 13 * per process 14 */ 15 class IdWorker(val workerId: Long, val datacenterId: Long, private val reporter: Reporter, var sequence: Long = 0L) 16 extends Snowflake.Iface { 17 private[this] def genCounter(agent: String) = { 18 Stats.incr("ids_generated") 19 Stats.incr("ids_generated_%s".format(agent)) 20 } 21 private[this] val exceptionCounter = Stats.getCounter("exceptions") 22 private[this] val log = Logger.get 23 private[this] val rand = new Random 24 25 val twepoch = 1288834974657L 26 27 private[this] val workerIdBits = 5L 28 private[this] val datacenterIdBits = 5L 29 private[this] val maxWorkerId = -1L ^ (-1L << workerIdBits) 30 private[this] val maxDatacenterId = -1L ^ (-1L << datacenterIdBits) 31 private[this] val sequenceBits = 12L 32 33 private[this] val workerIdShift = sequenceBits 34 private[this] val datacenterIdShift = sequenceBits + workerIdBits 35 private[this] val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits 36 private[this] val sequenceMask = -1L ^ (-1L << sequenceBits) 37 38 private[this] var lastTimestamp = -1L 39 40 // sanity check for workerId 41 if (workerId > maxWorkerId || workerId < 0) { 42 exceptionCounter.incr(1) 43 throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0".format(maxWorkerId)) 44 } 45 46 if (datacenterId > maxDatacenterId || datacenterId < 0) { 47 exceptionCounter.incr(1) 48 throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0".format(maxDatacenterId)) 49 } 50 51 log.info("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", 52 timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId) 53 54 def get_id(useragent: String): Long = { 55 if (!validUseragent(useragent)) { 56 exceptionCounter.incr(1) 57 throw new InvalidUserAgentError 58 } 59 60 val id = nextId() 61 genCounter(useragent) 62 63 reporter.report(new AuditLogEntry(id, useragent, rand.nextLong)) 64 id 65 } 66 67 def get_worker_id(): Long = workerId 68 def get_datacenter_id(): Long = datacenterId 69 def get_timestamp() = System.currentTimeMillis 70 71 protected[snowflake] def nextId(): Long = synchronized { 72 var timestamp = timeGen() 73 74 if (timestamp < lastTimestamp) { 75 exceptionCounter.incr(1) 76 log.error("clock is moving backwards. Rejecting requests until %d.", lastTimestamp); 77 throw new InvalidSystemClock("Clock moved backwards. Refusing to generate id for %d milliseconds".format( 78 lastTimestamp - timestamp)) 79 } 80 81 if (lastTimestamp == timestamp) { 82 sequence = (sequence + 1) & sequenceMask 83 if (sequence == 0) { 84 timestamp = tilNextMillis(lastTimestamp) 85 } 86 } else { 87 sequence = 0 88 } 89 90 lastTimestamp = timestamp 91 ((timestamp - twepoch) << timestampLeftShift) | 92 (datacenterId << datacenterIdShift) | 93 (workerId << workerIdShift) | 94 sequence 95 } 96 97 protected def tilNextMillis(lastTimestamp: Long): Long = { 98 var timestamp = timeGen() 99 while (timestamp <= lastTimestamp) { 100 timestamp = timeGen() 101 } 102 timestamp 103 } 104 105 protected def timeGen(): Long = System.currentTimeMillis() 106 107 val AgentParser = """([a-zA-Z][a-zA-Z\-0-9]*)""".r 108 109 def validUseragent(useragent: String): Boolean = useragent match { 110 case AgentParser(_) => true 111 case _ => false 112 } 113 }
Java版:
1 package com.test.util; 2 /** 3 * Twitter_Snowflake<br> 4 * SnowFlake的結構如下(每部分用-分開):<br> 5 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br> 6 * 1位標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0<br> 7 * 41位時間截(毫秒級),注意,41位時間截不是存儲當前時間的時間截,而是存儲時間截的差值(當前時間截 - 開始時間截) 8 * 得到的值),這里的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程序來指定的(如下下面程序IdWorker類的startTime屬性)。41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br> 9 * 10位的數據機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId<br> 10 * 12位序列,毫秒內的計數,12位的計數順序號支持每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號<br> 11 * 加起來剛好64位,為一個Long型。<br> 12 * SnowFlake的優點是,整體上按照時間自增排序,並且整個分布式系統內不會產生ID碰撞(由數據中心ID和機器ID作區分),並且效率較高,經測試,SnowFlake每秒能夠產生26萬ID左右。 13 */ 14 public class SnowflakeIdWorker { 15 16 // ==============================Fields=========================================== 17 /** 開始時間截 (2015-01-01) */ 18 private final long twepoch = 1420041600000L; 19 20 /** 機器id所占的位數 */ 21 private final long workerIdBits = 5L; 22 23 /** 數據標識id所占的位數 */ 24 private final long datacenterIdBits = 5L; 25 26 /** 支持的最大機器id,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數) */ 27 private final long maxWorkerId = -1L ^ (-1L << workerIdBits); 28 29 /** 支持的最大數據標識id,結果是31 */ 30 private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 31 32 /** 序列在id中占的位數 */ 33 private final long sequenceBits = 12L; 34 35 /** 機器ID向左移12位 */ 36 private final long workerIdShift = sequenceBits; 37 38 /** 數據標識id向左移17位(12+5) */ 39 private final long datacenterIdShift = sequenceBits + workerIdBits; 40 41 /** 時間截向左移22位(5+5+12) */ 42 private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; 43 44 /** 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */ 45 private final long sequenceMask = -1L ^ (-1L << sequenceBits); 46 47 /** 工作機器ID(0~31) */ 48 private long workerId; 49 50 /** 數據中心ID(0~31) */ 51 private long datacenterId; 52 53 /** 毫秒內序列(0~4095) */ 54 private long sequence = 0L; 55 56 /** 上次生成ID的時間截 */ 57 private long lastTimestamp = -1L; 58 59 //==============================Constructors===================================== 60 /** 61 * 構造函數 62 * @param workerId 工作ID (0~31) 63 * @param datacenterId 數據中心ID (0~31) 64 */ 65 public SnowflakeIdWorker(long workerId, long datacenterId) { 66 if (workerId > maxWorkerId || workerId < 0) { 67 throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); 68 } 69 if (datacenterId > maxDatacenterId || datacenterId < 0) { 70 throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); 71 } 72 this.workerId = workerId; 73 this.datacenterId = datacenterId; 74 } 75 76 // ==============================Methods========================================== 77 /** 78 * 獲得下一個ID (該方法是線程安全的) 79 * @return SnowflakeId 80 */ 81 public synchronized long nextId() { 82 long timestamp = timeGen(); 83 84 //如果當前時間小於上一次ID生成的時間戳,說明系統時鍾回退過這個時候應當拋出異常 85 if (timestamp < lastTimestamp) { 86 throw new RuntimeException( 87 String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); 88 } 89 90 //如果是同一時間生成的,則進行毫秒內序列 91 if (lastTimestamp == timestamp) { 92 sequence = (sequence + 1) & sequenceMask; 93 //毫秒內序列溢出 94 if (sequence == 0) { 95 //阻塞到下一個毫秒,獲得新的時間戳 96 timestamp = tilNextMillis(lastTimestamp); 97 } 98 } 99 //時間戳改變,毫秒內序列重置 100 else { 101 sequence = 0L; 102 } 103 104 //上次生成ID的時間截 105 lastTimestamp = timestamp; 106 107 //移位並通過或運算拼到一起組成64位的ID 108 return ((timestamp - twepoch) << timestampLeftShift) // 109 | (datacenterId << datacenterIdShift) // 110 | (workerId << workerIdShift) // 111 | sequence; 112 } 113 114 /** 115 * 阻塞到下一個毫秒,直到獲得新的時間戳 116 * @param lastTimestamp 上次生成ID的時間截 117 * @return 當前時間戳 118 */ 119 protected long tilNextMillis(long lastTimestamp) { 120 long timestamp = timeGen(); 121 while (timestamp <= lastTimestamp) { 122 timestamp = timeGen(); 123 } 124 return timestamp; 125 } 126 127 /** 128 * 返回以毫秒為單位的當前時間 129 * @return 當前時間(毫秒) 130 */ 131 protected long timeGen() { 132 return System.currentTimeMillis(); 133 } 134 135 //==============================Test============================================= 136 /** 測試 */ 137 public static void main(String[] args) { 138 SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); 139 140 for (int i = 0; i < 100; i++) { 141 long id = idWorker.nextId(); 142 System.out.println(Long.toBinaryString(id)); 143 System.out.println(id); 144 } 145 } 146 }