生成分散式唯一ID的幾種解決方案
分散式ID的特性
唯一性:確保生成的ID是全網唯一的。
有序遞增性:確保生成的ID是對於某個使用者或者業務是按一定的數字有序遞增的。
高可用性:確保任何時候都能正確的生成ID。
帶時間:ID裡面包含時間,一眼掃過去就知道哪天的交易。
1. UUID
UUID 是 通用唯一識別碼(Universally Unique Identifier)的縮寫,是一種軟體建構的標準,亦為開放軟體基金會組織在分散式計算環境領域的一部分。其目的,是讓分散式系統中的所有元素,都能有唯一的辨識資訊,而不需要通過中央控制端來做辨識資訊的指定。如此一來,每個人都可以建立不與其它人衝突的UUID。在這樣的情況下,就不需考慮資料庫建立時的名稱重複問題。
核心思想是結合機器的網路卡、當地時間、一個隨記數來生成UUID。
UUID是指在一臺機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。
UUID由以下幾部分的組合:
(1)當前日期和時間,UUID的第一個部分與時間有關,如果你在生成一個UUID之後,過幾秒又生成一個UUID,則第一個部分不同,其餘相同。
(2)時鐘序列。
(3)全域性唯一的IEEE機器識別號,如果有網路卡,從網路卡MAC地址獲得,沒有網路卡以其他方式獲得。
優點:本地生成,生成簡單,效能好,沒有高可用風險
缺點:長度過長,儲存冗餘,且無序不可讀,查詢效率低
public class createUUID {
public static void main(String... args) {
String uuid = UUID.randomUUID().toString(); //轉化為String物件
uuid = uuid.replace("-", ""); //因為UUID本身為32位只是生成時多了“-”,所以將它們去點就可
System.out.println(uuid);
}
}
資料庫自增ID
使用資料庫的id自增策略,如 MySQL 的 auto_increment。並且可以使用兩臺資料庫分別設定不同步長,生成不重複ID的策略來實現高可用。
優點:資料庫生成的ID絕對有序,高可用實現方式簡單
缺點:需要獨立部署資料庫例項,成本高,有效能瓶頸
1.select max(id) from tablename
2.SELECT LAST_INSERT_ID() 函式
LAST_INSERT_ID 是與table無關的,如果向表a插入資料後,再向表b插入資料,LAST_INSERT_ID會改變。
在多使用者交替插入資料的情況下max(id)顯然不能用。這時就該使用LAST_INSERT_ID了,因為LAST_INSERT_ID是基於Connection的,只要每個執行緒都使用獨立的 Connection物件,LAST_INSERT_ID函式將返回該Connection對AUTO_INCREMENT列最新的insert or update 操作生成的第一個record的ID。這個值不能被其它客戶端(Connection)影響,保證了你能夠找回自己的 ID 而不用擔心其它客戶端的活動,而且不需要加鎖。使用單INSERT語句插入多條記錄, LAST_INSERT_ID返回一個列表。
3. select @@IDENTITY;
@@identity 是表示的是最近一次向具有identity屬性(即自增列)的表插入資料時對應的自增列的值,是系統定義的全域性變數。一般系統定義的全域性變數都是以@@開頭,使用者自定義變數以@開頭。
比如有個表A,它的自增列是id,當向A表插入一行資料後,如果插入資料後自增列的值自動增加至101,則通過select @@identity得到的值就是101。使用@@identity的前提是在進行insert操作後,執行select @@identity的時候連線沒有關閉,否則得到的將是NULL值。
4. SHOW TABLE STATUS;
得出的結果裡邊對應表名記錄中有個Auto_increment欄位,裡邊有下一個自增ID的數值就是當前該表的最大自增ID.
預先生成一批ID
一次按需批量生成多個ID,每次生成都需要訪問資料庫,將資料庫修改為最大的ID值,並在記憶體中記錄當前值及最大值。
優點:避免了每次生成ID都要訪問資料庫並帶來壓力,提高效能
缺點:屬於本地生成策略,存在單點故障,服務重啟造成ID不連續
適用於,重複利用某固定數量ID進行展示或運算的場景
Redis生成ID
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 叢集也可以方式單點故障的問題。
另外,比較適合使用 Redis 來生成每天從0開始的流水號。比如訂單號 = 日期 + 當日自增長號。可以每天在 Redis 中生成一個 Key ,使用 INCR 進行累加。
Twitter的snowflake演算法
Twitter 利用 zookeeper 實現了一個全域性ID生成的服務 Snowflake:github.com/twitter/sno…
https://user-gold-cdn.xitu.io/2018/7/2/1645b1a7a9beb2b6?imageslim
如圖的所示,Twitter 的 Snowflake 演算法由下面幾部分組成:
1位符號位:
由於 long 型別在 java 中帶符號的,最高位為符號位,正數為 0,負數為 1,且實際系統中所使用的ID一般都是正數,所以最高位為 0。
41位時間戳(毫秒級):
需要注意的是此處的 41 位時間戳並非儲存當前時間的時間戳,而是儲存時間戳的差值(當前時間戳 - 起始時間戳),這裡的起始時間戳一般是ID生成器開始使用的時間戳,由程式來指定,所以41位毫秒時間戳最多可以使用 (1 << 41) / (1000x60x60x24x365) = 69年。
10位資料機器位:
包括5位資料標識位和5位機器標識位,這10位決定了分散式系統中最多可以部署 1 << 10 = 1024 s個節點。超過這個數量,生成的ID就有可能會衝突。
12位毫秒內的序列:
這 12 位計數支援每個節點每毫秒(同一臺機器,同一時刻)最多生成 1 << 12 = 4096個ID
加起來剛好64位,為一個Long型。
優點:高效能,低延遲,按時間有序,一般不會造成ID碰撞
缺點:需要獨立的開發和部署,依賴於機器的時鐘
簡單實現
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;
}
}
百度UidGenerator
UidGenerator是百度開源的分散式ID生成器,基於於snowflake演算法的實現,看起來感覺還行。不過,國內開源的專案維護性真是擔憂。
參考官網說明:https://github.com/baidu/uid-generator
美團Leaf
Leaf 是美團開源的分散式ID生成器,能保證全域性唯一性、趨勢遞增、單調遞增、資訊保安,裡面也提到了幾種分散式方案的對比,但也需要依賴關聯式資料庫、Zookeeper等中介軟體。
參考官網說明:https://tech.meituan.com/
相關文章
- 分散式唯一ID的幾種生成方案分散式
- php生成唯一id的幾種解決方法PHP
- 分散式唯一ID解決方案-雪花演算法分散式演算法
- 分散式唯一id生成策略分散式
- 分散式ID生成器的解決方案總結分散式
- 分散式唯一 ID 生成器分散式
- 分散式唯一ID生成服務分散式
- 最常用的分散式ID解決方案,你知道幾個分散式
- 分散式唯一 ID 生成器 - IDGen分散式
- 分散式系統中唯一 ID 的生成方法分散式
- Redis實現分散式鎖的幾種方案Redis分散式
- 分散式 ID 解決方案之美團 Leaf分散式
- 搞懂分散式技術16:淺談分散式鎖的幾種方案分散式
- 分散式全域性ID生成方案分散式
- 研究分散式唯一ID生成,看完這篇就夠分散式
- PHP 實現 Snowflake 生成分散式唯一 IDPHP分散式
- 分散式鎖的解決方案分散式
- 搞懂分散式技術12:分散式ID生成方案分散式
- 跨域的幾種解決方案跨域
- 【分散式鎖的演化】常用鎖的種類以及解決方案分散式
- 五種分散式事務解決方案(圖文總結)分散式
- 分散式唯一ID生成方案選型!詳細解析雪花演算法Snowflake分散式演算法
- 分散式下的WebSocket解決方案分散式Web
- 常用的分散式事務解決方案介紹有多少種?分散式
- 高精度定位的幾種解決方案
- js 非同步的幾種解決方案JS非同步
- 分散式全域性唯一ID分散式
- Redis分散式鎖解決方案Redis分散式
- Redis 分散式鎖解決方案Redis分散式
- SAP HANA分散式解決方案分散式
- 分散式事務解決方案分散式
- 分庫分表的 9種分散式主鍵ID 生成方案,挺全乎的分散式
- 微服務分散式事務4種解決方案實戰微服務分散式
- 詳解 Redis 分散式鎖的 5 種方案Redis分散式
- 常用的分散式事務解決方案分散式
- 聊聊分散式下的WebSocket解決方案分散式Web
- 高併發下,php使用uniqid函式生成唯一識別符號的四種方案PHP函式符號
- Android的延遲實現的幾種解決方案以及原理分析Android