在分佈式系統下惟一id問題,就是id咋生成?好比分表分庫,由於要是一個表分紅多個表以後,每一個表的id都是從1開始累加自增加,那是不對的。舉個例子,一個表拆分爲了2張表,每一個表的id都從1開始累加,這個確定有問題了!你的系統就沒辦法根據表主鍵來查詢了,好比id = 10這個記錄,在兩個表裏都有!因此此時就須要分佈式架構下的全局惟一id生成的方案了,保證每一個表內的某個id,全局惟一。全局惟一id主要有如下幾種方案。html
算法的核心思想是結合機器的網卡、當地時間、一個隨記數來生成UUID。java
使用數據庫的id自增策略,如 MySQL 的 auto_increment。而且可使用兩臺數據庫分別設置不一樣步長,生成不重複ID的策略來實現高可用。git
一次按需批量生成多個ID,每次生成都須要訪問數據庫,將數據庫修改成最大的ID值,並在內存中記錄當前值及最大值。github
Redis的全部命令操做都是單線程的,自己提供像 incr 和 increby 這樣的自增原子命令,因此能保證生成的 ID 確定是惟一有序的。算法
優勢:不依賴於數據庫,靈活方便,且性能優於數據庫;數字ID自然排序,對分頁或者須要排序的結果頗有幫助。數據庫
缺點:若是系統中沒有Redis,還須要引入新的組件,增長系統複雜度;須要編碼和配置的工做量比較大。安全
考慮到單節點的性能瓶頸,可使用 Redis 集羣來獲取更高的吞吐量。假如一個集羣中有5臺 Redis。能夠初始化每臺 Redis 的值分別是1, 2, 3, 4, 5,而後步長都是 5。各個 Redis 生成的 ID 爲:架構
A:1, 6, 11, 16, 21 B:2, 7, 12, 17, 22 C:3, 8, 13, 18, 23 D:4, 9, 14, 19, 24 E:5, 10, 15, 20, 25
隨便負載到哪一個機肯定好,將來很難作修改。步長和初始值必定須要事先肯定。使用 Redis 集羣也能夠方式單點故障的問題。less
另外,比較適合使用 Redis 來生成天天從0開始的流水號。好比訂單號 = 日期 + 當日自增加號。能夠天天在 Redis 中生成一個 Key ,使用 INCR 進行累加。分佈式
Twitter 利用 zookeeper 實現了一個全局ID生成的服務 Snowflake:https://github.com/twitter-archive/snowflake
如上圖的所示,Twitter 的 Snowflake 算法由下面幾部分組成:
因爲 long 類型在 java 中帶符號的,最高位爲符號位,正數爲 0,負數爲 1,且實際系統中所使用的ID通常都是正數,因此最高位爲 0。
須要注意的是此處的 41 位時間戳並不是存儲當前時間的時間戳,而是存儲時間戳的差值(當前時間戳 - 起始時間戳),這裏的起始時間戳通常是ID生成器開始使用的時間戳,由程序來指定,因此41位毫秒時間戳最多可使用 (1 << 41) / (1000x60x60x24x365) = 69年
。
包括5位數據標識位和5位機器標識位,這10位決定了分佈式系統中最多能夠部署 1 << 10 = 1024
s個節點。超過這個數量,生成的ID就有可能會衝突。
這 12 位計數支持每一個節點每毫秒(同一臺機器,同一時刻)最多生成 1 << 12 = 4096個ID
加起來恰好64位,爲一個Long型。
代碼實現:
public class IdWorker { /** * 起始時間戳 2017-04-01 */ private final long epoch = 1491004800000L; /** * 機器ID所佔的位數 */ private final long workerIdBits = 5L; /** * 數據標識ID所佔的位數 */ private final long dataCenterIdBits = 5L; /** * 支持的最大機器ID,結果是31 */ private final long maxWorkerId = ~(-1L << workerIdBits); /** * 支持的最大數據標識ID,結果是31 */ private final long maxDataCenterId = ~(-1 << dataCenterIdBits); /** * 毫秒內序列在id中所佔的位數 */ private final long sequenceBits = 12L; /** * 機器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** * 數據標識ID向左移17(12+5)位 */ private final long dataCenterIdShift = sequenceBits + workerIdBits; /** * 時間戳向左移22(12+5+5)位 */ private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits; /** * 生成序列的掩碼,這裏爲4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = ~(-1L << sequenceBits); /** * 數據標識ID(0~31) */ private long dataCenterId; /** * 機器ID(0~31) */ private long workerId; /** * 毫秒內序列(0~4095) */ private long sequence; /** * 上次生成ID的時間戳 */ private long lastTimestamp = -1L; public IdWorker(long dataCenterId, long workerId) { if (dataCenterId > maxDataCenterId || dataCenterId < 0) { throw new IllegalArgumentException(String.format("dataCenterId can't be greater than %d or less than 0", maxDataCenterId)); } if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } this.dataCenterId = dataCenterId; this.workerId = workerId; } /** * 得到下一個ID (該方法是線程安全的) * @return snowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); //若是當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過,這個時候應當拋出異常 if (timestamp < lastTimestamp) { throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //若是是同一時間生成的,則進行毫秒內序列 if (timestamp == lastTimestamp) { sequence = (sequence + 1) & sequenceMask; //毫秒內序列溢出 if (sequence == 0) { //阻塞到下一個毫秒,得到新的時間戳 timestamp = nextMillis(lastTimestamp); } } else {//時間戳改變,毫秒內序列重置 sequence = 0L; } lastTimestamp = timestamp; //移位並經過按位或運算拼到一塊兒組成64位的ID return ((timestamp - epoch) << timestampShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence; } /** * 返回以毫秒爲單位的當前時間 * @return 當前時間(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } /** * 阻塞到下一個毫秒,直到得到新的時間戳 * @param lastTimestamp 上次生成ID的時間截 * @return 當前時間戳 */ protected long nextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = lastTimestamp; } return timestamp; } }
Leaf 是美團開源的分佈式ID生成器,能保證全局惟一性、趨勢遞增、單調遞增、信息安全,裏面也提到了幾種分佈式方案的對比,但也須要依賴關係數據庫、Zookeeper等中間件。官網連接:https://tech.meituan.com/2017/04/21/mt-leaf.html