前言
分布式場(chǎng)景下,一張表可能分散到多個(gè)數(shù)據(jù)結(jié)點(diǎn)上。因此需要一些分布式ID的解決方案。
分布式ID需要有幾個(gè)特點(diǎn):
- 全局唯一(必要) :在多個(gè)庫(kù)的主鍵放在一起也不會(huì)重復(fù)
- 有序(必要) :避免頻繁觸發(fā)索引重建
- 信息安全:ID連續(xù),可以根據(jù)訂單編號(hào)計(jì)算一天的單量,造成信息泄露
- 包含時(shí)間戳:能夠快速根據(jù)ID得知生成時(shí)間
下面幾種方案按推薦順序排序,越推薦使用越靠前。
一、雪花算法snowflake
64 位的 long 類(lèi)型的唯一 id
1. 組成
1)1位不用
帶符號(hào)整數(shù)第1位是符號(hào)位,正數(shù)是0,ID一般為正數(shù),此位不用。
2)41位毫秒級(jí)時(shí)間戳
41位存儲(chǔ)當(dāng)前時(shí)間截 – 開(kāi)始時(shí)間截得到的差值,可以表示 2 41 2^{41} 241個(gè)毫秒的值,轉(zhuǎn)化成單位年則是: 2 41 1000 ? 60 ? 60 ? 24 ? 365 = 69 年 \frac{2^{41}}{1000?60?60?24?365}=69年 1000?60?60?24?365241?=69年
注:開(kāi)始時(shí)間截由程序指定,一般是id生成器開(kāi)始使用的時(shí)間,設(shè)置好后避免更改。依賴(lài)服務(wù)器時(shí)間,服務(wù)器時(shí)鐘回?fù)軙r(shí)可能會(huì)生成重復(fù) id。
3)10位機(jī)器ID
生成ID的服務(wù)可以部署在1024臺(tái)機(jī)器上
4)12位序列號(hào)
能夠表示4096個(gè)序列號(hào)。
因此,某一毫秒,同一臺(tái)機(jī)器,最多能生成4096個(gè)序號(hào)。理論上單機(jī)QPS最大為4096*1000=409.6w/s
2. 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- ID不重復(fù):用時(shí)間戳+機(jī)器+序號(hào)生成不重復(fù)ID
- 性能高:在內(nèi)存中生成
- 有序
缺點(diǎn):
依賴(lài)服務(wù)器時(shí)間,存在時(shí)鐘回?fù)艿膯?wèn)題。
3. 時(shí)鐘回?fù)茉趺唇鉀Q
時(shí)鐘回?fù)芸赡墚a(chǎn)生重復(fù)ID進(jìn)而影響關(guān)聯(lián)系統(tǒng)。
a. 時(shí)鐘回?fù)?/h4>
什么是時(shí)鐘回?fù)埽?/strong> 服務(wù)器上的時(shí)間倒退回之前的時(shí)間
哪些情況造成時(shí)鐘回?fù)埽?/strong>
- 人為修改服務(wù)器時(shí)間
- 時(shí)鐘同步后,由于機(jī)器之間時(shí)間不同,可能產(chǎn)生時(shí)鐘回?fù)?/li>
b. 解決方案
算法中會(huì)記錄當(dāng)前服務(wù)上次生成ID的最后時(shí)間,只需要保證我下次生成ID的時(shí)間大于上次最后時(shí)間即可。根據(jù)回?fù)芎髸r(shí)間距離上次生成最后時(shí)間大小,可以有不同的解決方案。
- 相差0~100ms :等待直至當(dāng)前時(shí)間超過(guò)上次最后生活時(shí)間
- 相差100ms~1s:采用等待方式可能導(dǎo)致接口超時(shí)??梢杂涗浺焉蒊D的最大ID,在這個(gè)基礎(chǔ)上++。(預(yù)留擴(kuò)展位,在擴(kuò)展位上增加。回?fù)芎笥只負(fù)芸赡苡袉?wèn)題)
- 相差1s~5s:采用最大ID增加的方式,時(shí)間過(guò)長(zhǎng)可能導(dǎo)致范圍溢出??梢陨蒊D服務(wù)響應(yīng)異常,由調(diào)用方例如基于Ribbon調(diào)用其他生成ID服務(wù)。
- 相差超過(guò)5s:采用Ribbon循環(huán)調(diào)用的方式,下次訪(fǎng)問(wèn)到時(shí)鐘回?fù)艿姆?wù)可能還沒(méi)達(dá)到上次生成最后時(shí)間,浪費(fèi)時(shí)間??梢宰尦^(guò)5s的服務(wù)主動(dòng)下線(xiàn),并通知運(yùn)維,人工介入,等待時(shí)鐘正常后再重啟。
4. 項(xiàng)目中如何使用
時(shí)鐘回?fù)艿奶幚磉壿嬙趎extId()里的if (timestamp < lastTimestamp) 邏輯下。這里直接拋出異常。
public class SnowflakeIdWorker {
// ==============================Fields===========================================
/** 開(kāi)始時(shí)間截 (2020-01-01) */
private final long twepoch = 1577808000000L;
/** 機(jī)器id所占的位數(shù) */
private final long workerIdBits = 5L;
/** 數(shù)據(jù)標(biāo)識(shí)id所占的位數(shù) */
private final long datacenterIdBits = 5L;
/** 支持的最大機(jī)器id,結(jié)果是31 (這個(gè)移位算法可以很快的計(jì)算出幾位二進(jìn)制數(shù)所能表示的最大十進(jìn)制數(shù)) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支持的最大數(shù)據(jù)標(biāo)識(shí)id,結(jié)果是31 */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** 序列在id中占的位數(shù) */
private final long sequenceBits = 12L;
/** 機(jī)器ID向左移12位 */
private final long workerIdShift = sequenceBits;
/** 數(shù)據(jù)標(biāo)識(shí)id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** 時(shí)間截向左移22位(5+5+12) */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/** 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作機(jī)器ID(0~31) */
private long workerId;
/** 數(shù)據(jù)中心ID(0~31) */
private long datacenterId;
/** 毫秒內(nèi)序列(0~4095) */
private long sequence = 0L;
/** 上次生成ID的時(shí)間截 */
private long lastTimestamp = -1L;
//==============================Constructors=====================================
/**
* 構(gòu)造函數(shù)
* @param workerId 工作ID (0~31)
* @param datacenterId 數(shù)據(jù)中心ID (0~31)
*/
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
// ==============================Methods==========================================
/**
* 獲得下一個(gè)ID (該方法是線(xiàn)程安全的)
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果當(dāng)前時(shí)間小于上一次ID生成的時(shí)間戳,說(shuō)明系統(tǒng)時(shí)鐘回退過(guò)這個(gè)時(shí)候應(yīng)當(dāng)拋出異常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一時(shí)間生成的,則進(jìn)行毫秒內(nèi)序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒內(nèi)序列溢出
if (sequence == 0) {
//阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//時(shí)間戳改變,毫秒內(nèi)序列重置
else {
sequence = 0L;
}
//上次生成ID的時(shí)間截
lastTimestamp = timestamp;
//移位并通過(guò)或運(yùn)算拼到一起組成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
/**
* 阻塞到下一個(gè)毫秒,直到獲得新的時(shí)間戳
* @param lastTimestamp 上次生成ID的時(shí)間截
* @return 當(dāng)前時(shí)間戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒為單位的當(dāng)前時(shí)間
* @return 當(dāng)前時(shí)間(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
//==============================Test=============================================
/** 測(cè)試 */
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 100; i++) {
long id = idWorker.nextId();
System.out.println(id);
}
}
}
二、基于Redis
三、基于Zookeeper
四、號(hào)段模式
數(shù)據(jù)庫(kù)中保存號(hào)段表,每次從數(shù)據(jù)庫(kù)獲取一個(gè)號(hào)段范圍,由服務(wù)在內(nèi)存中自增生成ID,直到達(dá)到號(hào)段范圍再去獲取。
id | 業(yè)務(wù)類(lèi)型 | 最大可用ID | 號(hào)段長(zhǎng)度 | 版本號(hào) |
---|---|---|---|---|
1 | xxx | 2000 | 1000 | 0 |
update 表 set 最大可用ID = 3000, version = version + 1 where version = 0 and biz_type = xxx
采用樂(lè)觀(guān)鎖的方式避免長(zhǎng)時(shí)間鎖表。
優(yōu)點(diǎn):
- ID有序遞增
- 對(duì)數(shù)據(jù)庫(kù)壓力比較小
缺點(diǎn):
- 存在安全問(wèn)題,用ID可以判斷數(shù)據(jù)量
- 存在單點(diǎn)問(wèn)題,集群實(shí)現(xiàn)困難
五、指定步長(zhǎng)的自增ID
多主集群模式下,表主鍵設(shè)置自增ID,多節(jié)點(diǎn)之間會(huì)有重復(fù)ID。需要采用指定初始值的自增步長(zhǎng)
舉例:兩臺(tái)數(shù)據(jù)庫(kù)A,B。A初始值和步長(zhǎng)為(1,2),B初始值和步長(zhǎng)為(2,2)。兩張表生成的主鍵分別為
A:1,3,5,7…
B:2,4,6,8…
設(shè)置方法:
set @@auto_increment_offset = 1; -- 起始值
set @@auto_increment_increment = 2; -- 步長(zhǎng)
優(yōu)點(diǎn):
- 簡(jiǎn)單、解決單點(diǎn)問(wèn)題
缺點(diǎn):
- 擴(kuò)容困難
六、UUID
128位長(zhǎng)的字符標(biāo)識(shí)串,由32個(gè)16進(jìn)制數(shù)字組成,用-連接,共36個(gè)字符。如:03425604-5462-11ee-80ad-80fa5b8732b1
生成算法與重復(fù)性:
- 基于隨機(jī)數(shù):不重復(fù)
- 基于MAC地址:不重復(fù)
- 基于時(shí)間戳:可能重復(fù)
作為分布式ID的優(yōu)缺點(diǎn):文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-731866.html
- 優(yōu)點(diǎn):本地生成,性能高,無(wú)網(wǎng)絡(luò)損耗
- 缺點(diǎn):
- 無(wú)序:造成索引重建,入庫(kù)性能差
- 字符串長(zhǎng):需要36字符
參考
- UUID會(huì)重復(fù)嗎?
- 雪花算法視頻
- 9種分布式ID生成方式
六、擴(kuò)展
- 百度:UidGenerator
- 美團(tuán):Leaf
總結(jié)
優(yōu)點(diǎn) | 缺點(diǎn) | |
---|---|---|
uuid | 實(shí)現(xiàn)簡(jiǎn)單 | 連續(xù)性差,作為主鍵每次新增數(shù)據(jù)都會(huì)觸發(fā)索引重建。 分布式環(huán)境中可能重復(fù) |
雪花算法 | 性能好,有序 | 依賴(lài)服務(wù)器時(shí)間,時(shí)鐘回?fù)芸赡苌芍貜?fù)ID |
號(hào)段模式 | ||
redis/zookeeper | Redis基于INCR 命令生成 分布式全局唯一id zookeeper一種通過(guò)節(jié)點(diǎn),一種通過(guò)節(jié)點(diǎn)的版本號(hào) |
基因算法文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-731866.html
到了這里,關(guān)于【分布式】分布式ID的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!