Mysql中雪花算法(Snowflake)的使用
一、基本概念
雪花算法(Snowflake)是一種生成全局唯一ID的分布式算法。它的主要功能是在分布式系統(tǒng)中生成一個全局唯一的ID,且ID是按照時間有序遞增的。
Snowflake 中文的意思為雪花,所以 Snowflake算法 常被稱為 雪花算法,是 Twitter(現(xiàn)“X”)開源的分布式 ID 生成算法,是一種分布式主鍵ID生成的解決方案。雪花算法生成后是一個 64bit 的 long 型的數(shù)值,組成部分引入了時間戳,基本保持了自增。
互聯(lián)網(wǎng)大廠實(shí)現(xiàn)的雪花開源項(xiàng)目:
美團(tuán) Leaf:https://github.com/Meituan-Dianping/Leaf
百度 Uid:https://github.com/baidu/uid-generator
二、核心思想
Snowflake算法使用一個64位的二進(jìn)制數(shù)字作為ID。這64位long型ID被分割成四個部分:符號位、時間戳、工作機(jī)器ID、序列號。通過這幾部分來表示不同的信息,將數(shù)據(jù)映射到具有特定結(jié)構(gòu)的分布式系統(tǒng)中,實(shí)現(xiàn)數(shù)據(jù)的存儲和查詢。

該算法由一系列節(jié)點(diǎn)組成,每個節(jié)點(diǎn)負(fù)責(zé)存儲數(shù)據(jù)的一部分。這些節(jié)點(diǎn)通過哈希函數(shù)將數(shù)據(jù)映射到特定的位置,形成類似于雪花結(jié)構(gòu)的分布式系統(tǒng)。通過這種方式,雪花算法能夠在分布式系統(tǒng)中保證ID的唯一性和有序性。
因?yàn)?/strong>雪花算法有序自增,保障了 MySQL 中 B+ Tree 索引結(jié)構(gòu)插入高性能。所以,日常業(yè)務(wù)使用中,雪花算法更多是被應(yīng)用在數(shù)據(jù)庫的主鍵 ID 和業(yè)務(wù)關(guān)聯(lián)主鍵。
上面有說過雪花算法會生成 64bit 的 long 型的數(shù)值,而這64bit 可以分為四個組成部分:
固定值:
1bit,最高位是符號位,0 表示正,1 表示負(fù),固定為 0,如果是 1 就是負(fù)數(shù)了。
第一位為什么不使用
在雪花算法中,第一位是符號位,0表示整數(shù),第一位如果是1則表示負(fù)數(shù),我們用的ID默認(rèn)就是正數(shù),所以默認(rèn)就是0,那么這一位默認(rèn)就沒有意義。
時間戳:
41bit,存儲毫秒級時間戳(41 位的長度可以使用 69 年)。
標(biāo)識位(存儲機(jī)器碼):
10bit,上面中的 機(jī)器id(5bit)和 服務(wù)id(5bit)統(tǒng)一叫作“標(biāo)識位”,兩個標(biāo)識位組合起來最多可以支持部署 1024 個節(jié)點(diǎn)。
標(biāo)識位怎么用
標(biāo)識位一共10 bit,如果全部表示機(jī)器,那么可以表示1024臺機(jī)器,如果拆分,5 bit 表示機(jī)房,5bit表示機(jī)房里面的機(jī)器,那么可以有32個機(jī)房,每個機(jī)房可以用32臺機(jī)器。
序列號:
12bit,用于表示在同一毫秒內(nèi)生成的多個ID的序號。如果在同一毫秒內(nèi)生成的ID超過了4096個(2的12次方),則需要等到下一毫秒再生成ID。
默認(rèn)的雪花算法是 64 bit,具體的長度可以自行配置。
如果希望運(yùn)行更久,增加時間戳的位數(shù);如果需要支持更多節(jié)點(diǎn)部署,增加標(biāo)識位長度;如果并發(fā)很高,增加序列號位數(shù)。
總結(jié):雪花算法并不是一成不變的,可以根據(jù)系統(tǒng)內(nèi)具體場景進(jìn)行定制。
三、為何要使用雪花算法
?現(xiàn)在的服務(wù)基本是分布式、微服務(wù)形式的,而且大數(shù)據(jù)量也導(dǎo)致分庫分表的產(chǎn)生,對于水平分表就需要保證表中 id 的全局唯一性。 對于 MySQL 而言,一個表中的主鍵 id 一般使用自增的方式,但是如果進(jìn)行水平分表之后,多個表中會生成重復(fù)的 id 值。那么如何保證水平分表后的多張表中的 id 是全局唯一性的呢?。
1,數(shù)據(jù)庫主鍵自增:
可以讓不同表初始化一個不同的初始值,然后按指定的步長進(jìn)行自增。
例如有3張拆分表,初始主鍵值為1,2,3,自增步長為3。
2,UUID:
用 UUID 來作為主鍵,但是 UUID 生成的是一個無序的字符串,
對于 MySQL 推薦使用增長的數(shù)值類型值作為主鍵來說不適合。
3,Redis:
使用 Redis 的自增原子性來生成唯一 id,但是這種方式業(yè)內(nèi)比較少用。
當(dāng)然還有其他解決方案,不同互聯(lián)網(wǎng)公司也有自己內(nèi)部的實(shí)現(xiàn)方案。雪花算法廣泛應(yīng)用于分布式系統(tǒng)中的唯一ID生成。它可以保證在分布式環(huán)境中生成的ID是唯一且有序的。常見的應(yīng)用場合包括訂單號生成、分布式數(shù)據(jù)庫中的數(shù)據(jù)主鍵、分布式鎖等。通過使用雪花算法生成全局唯一ID,可以方便地進(jìn)行分布式系統(tǒng)的數(shù)據(jù)管理和查詢。
優(yōu)點(diǎn):

缺點(diǎn):
依賴服務(wù)器時間,服務(wù)器時間回?fù)軙r可能會生成重復(fù) id。
小小的解決方案:算法中可通過記錄最后一個生成 id 時的時間戳來解決,每次生成 id 之前比較當(dāng)前服務(wù)器時鐘是否被回?fù)?,避免生成重?fù) id。
由于時間回?fù)軐?dǎo)致的生產(chǎn)重復(fù)的ID的問題,其實(shí)百度和美團(tuán)都有自己的解決方案了,有興趣可以去看看。
美團(tuán) Leaf:https://github.com/Meituan-Dianping/Leaf
百度 Uid:https://github.com/baidu/uid-generator
四、代碼實(shí)現(xiàn)
以下是雪花算法的Java代碼實(shí)現(xiàn)示例:
public class SnowflakeIdWorker{
/** 開始時間截 (2015-01-01) */
private final long twepoch = 1288834974657L;
/** 機(jī)器id所占的位數(shù) */
private final long workerIdBits = 5L;
/** 數(shù)據(jù)標(biāo)識id所占的位數(shù) */
private final long datacenterIdBits = 5L;
/** 支持的最大機(jī)器id,結(jié)果是31 (這個移位算法可以很快的計(jì)算出幾位二進(jìn)制數(shù)所能表示的最大十進(jìn)制數(shù)) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支持的最大數(shù)據(jù)標(biāo)識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)識id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** 時間截向左移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的時間截 */
private long lastTimestamp = -1L;
/**
* 構(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;
}
/**
* 獲得下一個ID (該方法是線程安全的)
*
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
// 如果當(dāng)前時間小于上一次ID生成的時間戳,說明系統(tǒng)時鐘回退過這個時候應(yīng)當(dāng)拋出異常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds",
(lastTimestamp - timestamp)));
}
// 如果是同一時間生成的,則進(jìn)行毫秒內(nèi)序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
// 毫秒內(nèi)序列溢出
if (sequence == 0) {
// 阻塞到下一個毫秒,獲得新的時間戳
timestamp = tilNextMillis(lastTimestamp);
}
}
// 時間戳改變,毫秒內(nèi)序列重置
else {
sequence = 0L;
}
// 上次生成ID的時間截
lastTimestamp = timestamp;
// 移位并通過或運(yùn)算拼到一起組成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
/**
* 阻塞到下一個毫秒,直到獲得新的時間戳
*
* @param lastTimestamp
* 上次生成ID的時間截
* @return 當(dāng)前時間戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒為單位的當(dāng)前時間
*
* @return 當(dāng)前時間(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
//測試方法
public static void main(String[] args) {
// 假設(shè)我們有一個工作機(jī)器ID為1,數(shù)據(jù)中心ID為1的環(huán)境
long workerId = 1L;
long datacenterId = 1L;
// 創(chuàng)建一個SnowflakeIdWorker實(shí)例
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(workerId, datacenterId);
// 生成并打印10個ID作為示例
for (int i = 0; i < 10; i++) {
long id = idWorker.nextId();
System.out.println(id);
}
}
}五、總結(jié)
雪花算法依賴于時間的一致性,如果發(fā)生時間回?fù)埽赡軙?dǎo)致問題。為了解決這個問題,通常會使用拓展位來擴(kuò)展時間戳的位數(shù)。原本雪花算法只能支持69年的時間范圍,但根據(jù)實(shí)際需求,可以增加時間戳的位數(shù)來延長可使用的年限,比如使用42位可以支持139年的時間范圍。然而,對于很多公司來說,首要任務(wù)是生存下來,因此可能會權(quán)衡取舍,不過度追求時間戳位數(shù)的增加。
需要注意的是,雪花算法也有一些缺點(diǎn)。在單機(jī)上,生成的ID是遞增的,但在多臺機(jī)器上,只能大致保持遞增趨勢,并不能嚴(yán)格保證遞增。這是因?yàn)槎嗯_機(jī)器之間的時鐘不一定完全同步。因此,在多機(jī)器環(huán)境下,對于嚴(yán)格的遞增需求,需要考慮其他解決方案。
總而言之,雪花算法是一種常用的分布式唯一ID生成算法,但并非完美解決方案。在使用時,需要根據(jù)實(shí)際需求和限制條件進(jìn)行權(quán)衡和選擇,以尋找適合自己情況的解決方案。
到此這篇關(guān)于Mysql中雪花算法(Snowflake)的使用的文章就介紹到這了,更多相關(guān)Mysql 雪花算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL如何從數(shù)據(jù)庫中刪除表中所有數(shù)據(jù)
這篇文章主要介紹了MySQL如何從數(shù)據(jù)庫中刪除表中所有數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
關(guān)于MySQL性能調(diào)優(yōu)你必須了解的15個重要變量(小結(jié))
MYSQL 應(yīng)該是比較流行的 WEB 后端數(shù)據(jù)庫。雖然 NOSQL 最近越來越多的被提到,但是相信大部分架構(gòu)師還是會選擇 MYSQL 來做數(shù)據(jù)存儲。本文作者總結(jié)梳理MySQL性能調(diào)優(yōu)的15個重要變量,感興趣的可以了解一下2019-07-07
MySQL數(shù)據(jù)庫InnoDB引擎主從復(fù)制同步經(jīng)驗(yàn)總結(jié)
這篇文章主要介紹了MySQL數(shù)據(jù)庫InnoDB引擎主從復(fù)制同步經(jīng)驗(yàn)總結(jié),本文總結(jié)了設(shè)置主從復(fù)制時遇到的一些錯誤和解決方法,需要的朋友可以參考下2015-01-01
MySQL 8.0統(tǒng)計(jì)信息不準(zhǔn)確的原因
這篇文章主要介紹了MySQL 8.0統(tǒng)計(jì)信息不準(zhǔn)確的原因,幫助大家更好的理解和學(xué)習(xí)MySQL8.0的相關(guān)內(nèi)容,感興趣的朋友可以了解下2020-08-08
MySQL索引底層數(shù)據(jù)結(jié)構(gòu)詳情
這篇文章主要介紹了MySQL索引底層數(shù)據(jù)結(jié)構(gòu)詳情,下面文章圍繞MySQL索引底層數(shù)據(jù)結(jié)構(gòu)的相關(guān)資料展開全篇文章,具有一定的參考價值,需要的小伙伴可以參考一下2021-12-12
Mysql數(shù)據(jù)庫之常用sql語句進(jìn)階與總結(jié)
這篇文章主要介紹了Mysql數(shù)據(jù)庫之常用sql語句,總結(jié)分析了MySQL數(shù)據(jù)庫常用的查詢、條件查詢、排序、連接查詢、子查詢等相關(guān)操作技巧,需要的朋友可以參考下2019-11-11
MySQL gh-ost DDL 變更工具的實(shí)現(xiàn)
本文主要介紹了MySQL gh-ost DDL變更工具的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02
MySQL中無GROUP BY情況下直接使用HAVING語句的問題探究
這篇文章主要介紹了MySQL中無GROUP BY情況下直接使用HAVING語句的問題探究,同時探究了該情況下MAX與MIN功能的使用情況,需要的朋友可以參考下2015-05-05

