Mysql中雪花算法(Snowflake)的使用
一、基本概念
雪花算法(Snowflake)是一種生成全局唯一ID的分布式算法。它的主要功能是在分布式系統(tǒng)中生成一個(gè)全局唯一的ID,且ID是按照時(shí)間有序遞增的。
Snowflake 中文的意思為雪花,所以 Snowflake算法 常被稱(chēng)為 雪花算法,是 Twitter(現(xiàn)“X”)開(kāi)源的分布式 ID 生成算法,是一種分布式主鍵ID生成的解決方案。雪花算法
生成后是一個(gè) 64bit 的 long 型的數(shù)值,組成部分引入了時(shí)間戳,基本保持了自增。
互聯(lián)網(wǎng)大廠實(shí)現(xiàn)的雪花開(kāi)源項(xiàng)目:
美團(tuán) Leaf:https://github.com/Meituan-Dianping/Leaf
百度 Uid:https://github.com/baidu/uid-generator
二、核心思想
Snowflake算法使用一個(gè)64位的二進(jìn)制數(shù)字作為ID。這64位long型ID被分割成四個(gè)部分:符號(hào)位、時(shí)間戳、工作機(jī)器ID、序列號(hào)。通過(guò)這幾部分來(lái)表示不同的信息,將數(shù)據(jù)映射到具有特定結(jié)構(gòu)的分布式系統(tǒng)中,實(shí)現(xiàn)數(shù)據(jù)的存儲(chǔ)和查詢(xún)。
該算法由一系列節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)負(fù)責(zé)存儲(chǔ)數(shù)據(jù)的一部分。這些節(jié)點(diǎn)通過(guò)哈希函數(shù)將數(shù)據(jù)映射到特定的位置,形成類(lèi)似于雪花結(jié)構(gòu)的分布式系統(tǒng)。通過(guò)這種方式,雪花算法能夠在分布式系統(tǒng)中保證ID的唯一性和有序性。
因?yàn)?/strong>雪花算法
有序自增,保障了 MySQL 中 B+ Tree 索引結(jié)構(gòu)插入高性能。所以,日常業(yè)務(wù)使用中,雪花算法
更多是被應(yīng)用在數(shù)據(jù)庫(kù)的主鍵 ID 和業(yè)務(wù)關(guān)聯(lián)主鍵。
上面有說(shuō)過(guò)雪花算法
會(huì)生成 64bit 的 long 型的數(shù)值,而這64bit 可以分為四個(gè)組成部分:
固定值:
1bit,最高位是符號(hào)位,0 表示正,1 表示負(fù),固定為 0,如果是 1 就是負(fù)數(shù)了。
第一位為什么不使用
在雪花算法中,第一位是符號(hào)位,0表示整數(shù),第一位如果是1則表示負(fù)數(shù),我們用的ID默認(rèn)就是正數(shù),所以默認(rèn)就是0,那么這一位默認(rèn)就沒(méi)有意義。
時(shí)間戳:
41bit,存儲(chǔ)毫秒級(jí)時(shí)間戳(41 位的長(zhǎng)度可以使用 69 年)。
標(biāo)識(shí)位(存儲(chǔ)機(jī)器碼):
10bit,上面中的 機(jī)器id(5bit)和 服務(wù)id(5bit)統(tǒng)一叫作“標(biāo)識(shí)位”,兩個(gè)標(biāo)識(shí)位組合起來(lái)最多可以支持部署 1024 個(gè)節(jié)點(diǎn)。
標(biāo)識(shí)位怎么用
標(biāo)識(shí)位一共10 bit,如果全部表示機(jī)器,那么可以表示1024臺(tái)機(jī)器,如果拆分,5 bit 表示機(jī)房,5bit表示機(jī)房里面的機(jī)器,那么可以有32個(gè)機(jī)房,每個(gè)機(jī)房可以用32臺(tái)機(jī)器。
序列號(hào):
12bit,用于表示在同一毫秒內(nèi)生成的多個(gè)ID的序號(hào)。如果在同一毫秒內(nèi)生成的ID超過(guò)了4096個(gè)(2的12次方),則需要等到下一毫秒再生成ID。
默認(rèn)的雪花算法
是 64 bit,具體的長(zhǎng)度可以自行配置。
如果希望運(yùn)行更久,增加時(shí)間戳的位數(shù);如果需要支持更多節(jié)點(diǎn)部署,增加標(biāo)識(shí)位長(zhǎng)度;如果并發(fā)很高,增加序列號(hào)位數(shù)。
總結(jié):雪花算法
并不是一成不變的,可以根據(jù)系統(tǒng)內(nèi)具體場(chǎng)景進(jìn)行定制。
三、為何要使用雪花算法
?現(xiàn)在的服務(wù)基本是分布式、微服務(wù)形式的,而且大數(shù)據(jù)量也導(dǎo)致分庫(kù)分表的產(chǎn)生,對(duì)于水平分表就需要保證表中 id 的全局唯一性。 對(duì)于 MySQL 而言,一個(gè)表中的主鍵 id 一般使用自增的方式,但是如果進(jìn)行水平分表之后,多個(gè)表中會(huì)生成重復(fù)的 id 值。那么如何保證水平分表后的多張表中的 id 是全局唯一性的呢?。
1,數(shù)據(jù)庫(kù)主鍵自增:
可以讓不同表初始化一個(gè)不同的初始值,然后按指定的步長(zhǎng)進(jìn)行自增。
例如有3張拆分表,初始主鍵值為1,2,3,自增步長(zhǎng)為3。
2,UUID:
用 UUID 來(lái)作為主鍵,但是 UUID 生成的是一個(gè)無(wú)序的字符串,
對(duì)于 MySQL 推薦使用增長(zhǎng)的數(shù)值類(lèi)型值作為主鍵來(lái)說(shuō)不適合。
3,Redis:
使用 Redis 的自增原子性來(lái)生成唯一 id,但是這種方式業(yè)內(nèi)比較少用。
當(dāng)然還有其他解決方案,不同互聯(lián)網(wǎng)公司也有自己內(nèi)部的實(shí)現(xiàn)方案。雪花算法廣泛應(yīng)用于分布式系統(tǒng)中的唯一ID生成。它可以保證在分布式環(huán)境中生成的ID是唯一且有序的。常見(jiàn)的應(yīng)用場(chǎng)合包括訂單號(hào)生成、分布式數(shù)據(jù)庫(kù)中的數(shù)據(jù)主鍵、分布式鎖等。通過(guò)使用雪花算法生成全局唯一ID,可以方便地進(jìn)行分布式系統(tǒng)的數(shù)據(jù)管理和查詢(xún)。
優(yōu)點(diǎn):
缺點(diǎn):
依賴(lài)服務(wù)器時(shí)間,服務(wù)器時(shí)間回?fù)軙r(shí)可能會(huì)生成重復(fù) id。
小小的解決方案:算法中可通過(guò)記錄最后一個(gè)生成 id 時(shí)的時(shí)間戳來(lái)解決,每次生成 id 之前比較當(dāng)前服務(wù)器時(shí)鐘是否被回?fù)?,避免生成重?fù) id。
由于時(shí)間回?fù)軐?dǎo)致的生產(chǎn)重復(fù)的ID的問(wèn)題,其實(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{ /** 開(kāi)始時(shí)間截 (2015-01-01) */ private final long twepoch = 1288834974657L; /** 機(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; /** * 構(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; } /** * 獲得下一個(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(); } //測(cè)試方法 public static void main(String[] args) { // 假設(shè)我們有一個(gè)工作機(jī)器ID為1,數(shù)據(jù)中心ID為1的環(huán)境 long workerId = 1L; long datacenterId = 1L; // 創(chuàng)建一個(gè)SnowflakeIdWorker實(shí)例 SnowflakeIdWorker idWorker = new SnowflakeIdWorker(workerId, datacenterId); // 生成并打印10個(gè)ID作為示例 for (int i = 0; i < 10; i++) { long id = idWorker.nextId(); System.out.println(id); } } }
五、總結(jié)
雪花算法
依賴(lài)于時(shí)間的一致性,如果發(fā)生時(shí)間回?fù)?,可能?huì)導(dǎo)致問(wèn)題。為了解決這個(gè)問(wèn)題,通常會(huì)使用拓展位來(lái)擴(kuò)展時(shí)間戳的位數(shù)。原本雪花算法
只能支持69年的時(shí)間范圍,但根據(jù)實(shí)際需求,可以增加時(shí)間戳的位數(shù)來(lái)延長(zhǎng)可使用的年限,比如使用42位可以支持139年的時(shí)間范圍。然而,對(duì)于很多公司來(lái)說(shuō),首要任務(wù)是生存下來(lái),因此可能會(huì)權(quán)衡取舍,不過(guò)度追求時(shí)間戳位數(shù)的增加。
需要注意的是,雪花算法
也有一些缺點(diǎn)。在單機(jī)上,生成的ID是遞增的,但在多臺(tái)機(jī)器上,只能大致保持遞增趨勢(shì),并不能?chē)?yán)格保證遞增。這是因?yàn)槎嗯_(tái)機(jī)器之間的時(shí)鐘不一定完全同步。因此,在多機(jī)器環(huán)境下,對(duì)于嚴(yán)格的遞增需求,需要考慮其他解決方案。
總而言之,雪花算法
是一種常用的分布式唯一ID生成算法,但并非完美解決方案。在使用時(shí),需要根據(jù)實(shí)際需求和限制條件進(jìn)行權(quán)衡和選擇,以尋找適合自己情況的解決方案。
到此這篇關(guān)于Mysql中雪花算法(Snowflake)的使用的文章就介紹到這了,更多相關(guān)Mysql 雪花算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Linux自動(dòng)備份MySQL數(shù)據(jù)庫(kù)腳本代碼
下面這段Linux的Shell腳本用于每日自動(dòng)備份MySQL數(shù)據(jù)庫(kù),可通過(guò)Linux的crontab每天定時(shí)執(zhí)行2013-11-11通過(guò)SqlCmd執(zhí)行超大SQL文件的方法
這篇文章主要介紹了sql?server?與?mysql?中常用的SQL語(yǔ)句區(qū)別,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12IPv6設(shè)置后如何解決MySQL無(wú)法連接localhost的問(wèn)題
這篇文章主要介紹了IPv6設(shè)置后如何解決MySQL無(wú)法連接localhost的問(wèn)題,需要的朋友可以參考下2016-04-04Unity連接MySQL并讀取表格數(shù)據(jù)的實(shí)現(xiàn)代碼
本文給大家介紹Unity連接MySQL并讀取表格數(shù)據(jù)的實(shí)現(xiàn)代碼,實(shí)例化的同時(shí)調(diào)用MySqlConnection,傳入?yún)?shù),這里的傳入?yún)?shù)個(gè)人認(rèn)為是CMD里面的直接輸入了,string格式直接類(lèi)似手敲到cmd里面,完整代碼參考下本文2021-06-06兩個(gè)windows服務(wù)器使用canal實(shí)現(xiàn)mysql實(shí)時(shí)同步
canal是阿里基于java寫(xiě)的一個(gè)組件,他的作用是canal.deployer讀取mysql數(shù)據(jù)的binlog日志,然后canal.adapter將其轉(zhuǎn)換為對(duì)應(yīng)的數(shù)據(jù)(數(shù)據(jù)的變化或者變化后的數(shù)據(jù),跟配置有關(guān)),并且同步到相關(guān)中間件,本文實(shí)現(xiàn)兩個(gè)windows服務(wù)器使用canal實(shí)現(xiàn)mysql主從復(fù)制實(shí)時(shí)同步2025-03-03