java UUID&雪花算法生成和使用場景詳解
1. 引言
簡介:為什么需要唯一標識符
在現(xiàn)代軟件架構(gòu)和數(shù)據(jù)管理中,能夠唯一標識信息資源是至關(guān)重要的。唯一標識符(Unique Identifier,簡稱UID)允許系統(tǒng)在全局范圍內(nèi)區(qū)分每一個獨立的元素,無論是用戶、訂單、消息還是任何數(shù)據(jù)記錄。這種標識的唯一性保證了數(shù)據(jù)的一致性和完整性,避免了數(shù)據(jù)處理過程中的混淆和錯誤。
在分布式系統(tǒng)中,例如互聯(lián)網(wǎng)服務、云基礎設施和大規(guī)模計算環(huán)境,需要跨多個節(jié)點、位置和時間區(qū)間追蹤和管理數(shù)據(jù)。在這些環(huán)境中,生成全局唯一的標識符尤為重要,因為傳統(tǒng)的基于單一數(shù)據(jù)庫的自增ID生成策略在這些環(huán)境中可能會導致ID沖突。
UUID和雪花算法的重要性
UUID(Universally Unique Identifier)和雪花算法(Snowflake Algorithm)是生成唯一標識符的兩種流行方法,它們各有優(yōu)勢,并適用于不同的應用場景。
- UUID:UUID是一個16字節(jié)的數(shù)字,通常以32個十六進制數(shù)字表示,并通過特定的版本算法生成。它的主要優(yōu)點是簡單易用,能夠在本地生成,無需通過網(wǎng)絡交互,從而避免了網(wǎng)絡延遲和中斷的問題。UUID的生成不依賴于中心數(shù)據(jù)庫或其他外部系統(tǒng),這使得它非常適合需要高度解耦的系統(tǒng)架構(gòu)。
- 雪花算法:雪花算法是由Twitter開發(fā)的,用于生成64位的長整型數(shù)字作為唯一ID。它結(jié)合了機器ID、數(shù)據(jù)中心ID和時間戳信息,可以在分布式系統(tǒng)中快速生成具有時間順序性的ID。雪花算法的主要優(yōu)點是生成ID時考慮了時間順序,這對于需要維護記錄順序的系統(tǒng)特別有用。
這兩種算法的存在和發(fā)展,顯著提升了現(xiàn)代系統(tǒng)中數(shù)據(jù)處理的效率和可靠性,使得開發(fā)者可以更專注于業(yè)務邏輯的實現(xiàn),而不必擔心數(shù)據(jù)標識和沖突的問題。
2. UUID生成方案
UUID的定義和標準
UUID(Universally Unique Identifier)是一種軟件建構(gòu)的標準,也被稱為GUID(Globally Unique Identifier)。UUID的主要目的是讓分布式系統(tǒng)中的所有元素,都能有唯一的識別信息,而不需要通過中央控制端來做標識符的指定。如此一來,每個人都可以創(chuàng)建不與其它人沖突的UUID。在這個方面,UUID的目標與主鍵的目標是相符合的。
UUID是由一個十六位的數(shù)字組成,通過特定的算法進行生成,形如:550e8400-e29b-41d4-a716-446655440000
。
UUID的標準型式包含32個16進制數(shù)字,以連字號分為五段,形式為8-4-4-4-12的32個字符。
Java中生成UUID的方法
在Java中,可以使用java.util.UUID
類來生成UUID。以下是一個簡單的示例:
import java.util.UUID; public class Main { public static void main(String[] args) { UUID uuid = UUID.randomUUID(); System.out.println(uuid.toString()); } }
在這個示例中,UUID.randomUUID()
方法被用來生成一個隨機UUID。
UUID的版本差異
UUID標準定義了五種不同的生成方法,或者說五個版本。每個版本的UUID都包含一個4位的版本號,以便我們可以區(qū)分生成的UUID使用了哪種方法。
- 版本1:基于時間的UUID:這種UUID使用了發(fā)起UUID生成請求的計算機的MAC地址和當前的時間戳(精確到100納秒)來生成UUID。由于MAC地址是全球唯一的,所以生成的UUID也是全球唯一的。不過,這種方法會因暴露MAC地址而帶來一定的安全風險。
- 版本4:隨機生成的UUID:這種UUID完全由隨機數(shù)生成,沒有時間和硬件的限制,也沒有安全性問題。Java的
UUID.randomUUID()
方法就是生成這種UUID。不過,由于是隨機生成,所以理論上存在生成的UUID重復的可能,但實際上這種可能性非常非常小。
3. UUID的使用場景
UUIDs 提供了一種高度可靠的方式來生成唯一標識符,這在許多不同的技術(shù)場景中都非常有用。
以下是一些典型的使用UUID的場景:
網(wǎng)絡系統(tǒng)中的唯一性需求
在網(wǎng)絡環(huán)境中,尤其是在互聯(lián)網(wǎng)應用和服務中,需要追蹤和區(qū)分成千上萬的請求和事務。UUID可以為每一個請求或事務生成一個唯一的標識符,確保即使在高并發(fā)的情況下也不會產(chǎn)生沖突。例如,Web API可以為每個請求生成一個UUID,用于日志記錄、監(jiān)控和追蹤問題,從而提高服務的可靠性和可追溯性。
數(shù)據(jù)庫主鍵
在數(shù)據(jù)庫設計中,使用UUID作為主鍵是一種常見的做法,尤其是在分布式數(shù)據(jù)庫系統(tǒng)中。與傳統(tǒng)的遞增整數(shù)主鍵相比,UUID可以避免跨數(shù)據(jù)庫的同步和沖突問題,使得數(shù)據(jù)庫的擴展更為靈活和可靠。此外,使用UUID作為主鍵可以減少數(shù)據(jù)庫遷移和合并時的復雜性,因為它保證了即使在不同的數(shù)據(jù)庫間也不會出現(xiàn)主鍵的重復。
分布式系統(tǒng)中的身份標識
在分布式系統(tǒng)中,尤其是那些涉及多個服務和組件的大型系統(tǒng)中,需要一種機制來唯一標識每個組件或節(jié)點。UUID為這些系統(tǒng)提供了一種簡單而有效的方式來生成這種唯一標識符。例如,微服務架構(gòu)中的每個服務實例可以使用UUID來標識,這有助于在服務發(fā)現(xiàn)和負載均衡等過程中確保正確的資源分配和管理。
總的來說,UUID的使用可以極大地增強系統(tǒng)的健壯性、可擴展性和安全性。其能夠在不依賴中心化控制的情況下生成全局唯一的標識符,使得它成為現(xiàn)代軟件和系統(tǒng)設計中不可或缺的一個工具。
4. 雪花算法(Snowflake Algorithm)
雪花算法的介紹
雪花算法(Snowflake Algorithm)是由Twitter開發(fā)的一種用于生成唯一ID的算法,特別適用于分布式系統(tǒng)中。
這種算法可以在不需要中央數(shù)據(jù)庫的情況下生成全局唯一的ID,非常適合需要處理大量數(shù)據(jù)和高并發(fā)請求的應用。
結(jié)構(gòu)解析
雪花算法生成的ID是一個64位的整數(shù),這64位中包含了以下幾個部分:
- 時間戳 - 占用41位,精確到毫秒級,可以使用約69年。
- 數(shù)據(jù)中心ID - 占用5位,可以有最多32個數(shù)據(jù)中心。
- 機器ID - 占用5位,每個數(shù)據(jù)中心可以有最多32臺機器。
- 序列號 - 占用12位,每個節(jié)點每毫秒可以生成最多4096個ID。
這種結(jié)構(gòu)確保了即使在同一時間同一數(shù)據(jù)中心的同一機器上發(fā)生多個請求,生成的ID也是唯一的。
Java實現(xiàn)雪花算法
要在Java中實現(xiàn)雪花算法,我們需要定義一個類來處理ID生成的邏輯。
下面是一個簡單的實現(xiàn)示例:
import cn.hutool.core.net.NetUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.Random; /** * Twitter_Snowflake<br> * SnowFlake的結(jié)構(gòu)如下(每部分用-分開):<br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - * 000000000000 <br> * 1位標識,由于long基本類型在Java中是帶符號的,最高位是符號位,正數(shù)是0,負數(shù)是1,所以id一般是正數(shù),最高位是0<br> * 41位時間截(毫秒級),注意,41位時間截不是存儲當前時間的時間截,而是存儲時間截的差值(當前時間截 - 開始時間截) * 得到的值),這里的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程序來指定的(如下下面程序IdWorker類的startTime屬性)。41位的時間截,可以使用69年,年T * = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br> * 10位的數(shù)據(jù)機器位,可以部署在1024個節(jié)點,包括5位datacenterId和5位workerId<br> * 12位序列,毫秒內(nèi)的計數(shù),12位的計數(shù)順序號支持每個節(jié)點每毫秒(同一機器,同一時間截)產(chǎn)生4096個ID序號<br> * 加起來剛好64位,為一個Long型。<br> * SnowFlake的優(yōu)點是,整體上按照時間自增排序,并且整個分布式系統(tǒng)內(nèi)不會產(chǎn)生ID碰撞(由數(shù)據(jù)中心ID和機器ID作區(qū)分),并且效率較高,經(jīng)測試,SnowFlake每秒能夠產(chǎn)生26萬ID左右。 */ @Component public class SnowflakeIdWorker { /** * 開始時間截 (2019-06-21) */ private final long twepoch = 1561104939733L; /** * 機器id所占的位數(shù) */ private final long workerIdBits = 5L; /** * 數(shù)據(jù)標識id所占的位數(shù) */ private final long datacenterIdBits = 5L; /** * 支持的最大機器id,結(jié)果是31 (這個移位算法可以很快的計算出幾位二進制數(shù)所能表示的最大十進制數(shù)) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** * 支持的最大數(shù)據(jù)標識id,結(jié)果是31 */ private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); /** * 序列在id中占的位數(shù) */ private final long sequenceBits = 12L; /** * 機器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** * 數(shù)據(jù)標識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); /** * 工作機器ID(0~31) */ private long workerId = getWorkId(); /** * 數(shù)據(jù)中心ID(0~31) */ private long datacenterId = getDataId(); /** * 毫秒內(nèi)序列(0~4095) */ private long sequence = 0L; /** * 上次生成ID的時間截 */ private long lastTimestamp = -1L; /** * 機器隨機獲取數(shù)據(jù)中中心id的參數(shù) 32 */ private final long DATA_RANDOM = maxDatacenterId + 1; /** * 隨機獲取的機器id的參數(shù) */ private final long WORK_RANDOM = maxWorkerId + 1; private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdWorker.class); @PostConstruct public void init() { logger.debug("snowflake-work-id:{}", getWorkId()); logger.debug("snowflake-data-id:{}", getDataId()); } public SnowflakeIdWorker() { // this(0, 0); } /** * 構(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(); // 如果當前時間小于上一次ID生成的時間戳,說明系統(tǒng)時鐘回退過這個時候應當拋出異常 if (timestamp < lastTimestamp) { if (lastTimestamp - timestamp < 2000) { // 容忍2秒內(nèi)的回撥,避免NTP校時造成的異常 timestamp = lastTimestamp; } else { // 如果服務器時間有問題(時鐘后退) 報錯。 throw new RuntimeException(String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } } // 如果是同一時間生成的,則進行毫秒內(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; // 移位并通過或運算拼到一起組成64位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (datacenterId << datacenterIdShift) // | (workerId << workerIdShift) // | sequence; } /** * 阻塞到下一個毫秒,直到獲得新的時間戳 * * @param lastTimestamp 上次生成ID的時間截 * @return 當前時間戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒為單位的當前時間 * * @return 當前時間(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } /** * 根據(jù)host name 取余,發(fā)生異常就獲取0到31之間的隨機數(shù) * * @return */ public long getWorkId() { try { String ip = NetUtil.getLocalhost().getHostAddress(); logger.info("服務器IP:{}", ip); return getHostId(ip, maxWorkerId); } catch (Exception e) { return new Random().nextInt((int) WORK_RANDOM); } } /** * 根據(jù)host name 取余,發(fā)生異常就獲取0到31之間的隨機數(shù) * * @return */ public long getDataId() { try { String ip = NetUtil.getLocalhost().getHostAddress(); logger.info("服務器IP:{}", ip); return getHostId(ip, maxDatacenterId); } catch (Exception e) { return new Random().nextInt((int) DATA_RANDOM); } } /** * 根據(jù)host name 取余 * * @return */ private long getHostId(String s, long max) { byte[] bytes = s.getBytes(); long sums = 0; for (byte b : bytes) { sums += b; } return sums % (max + 1); } /** * 測試 */ public static void main(String[] args) { SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); System.out.println(idWorker.nextId()); } }
在這個實現(xiàn)中,我們創(chuàng)建了一個SnowflakeIdWorker
類,該類可以在構(gòu)造時接受數(shù)據(jù)中心ID和機器ID,并提供了一個nextId()
方法來生成新的ID。
這個方法確保生成的ID是按時間順序遞增的,并且在多線程環(huán)境中是安全的。
5. 雪花算法的使用場景
雪花算法由于其獨特的結(jié)構(gòu)和高效的性能特點,非常適用于特定的技術(shù)場景。以下是雪花算法的一些主要使用場景:
大規(guī)模分布式系統(tǒng)中的ID生成
在大規(guī)模分布式系統(tǒng)中,需要確保在多個節(jié)點和服務之間生成的ID是唯一的,同時又不能依賴于中央數(shù)據(jù)庫或服務來維護ID的唯一性。雪花算法通過結(jié)合時間戳、數(shù)據(jù)中心ID、機器ID和序列號生成唯一的ID,從而無需進行網(wǎng)絡交互即可在各個節(jié)點獨立生成ID。這種方法非常適合用于電商平臺、社交網(wǎng)絡、在線游戲等業(yè)務,其中需要處理大量數(shù)據(jù)并且對ID生成的性能要求極高。
性能考量和優(yōu)勢
雪花算法的一個顯著優(yōu)勢是其生成ID的速度非常快,可以在毫秒級別生成數(shù)百萬個ID,這對于高并發(fā)的應用場景尤為重要。此外,由于ID是基于時間戳生成的,這自然地保證了生成的ID的順序性(在同一毫秒內(nèi)通過序列號保證順序),這對于需要保持事件順序的應用場景(如日志記錄、消息隊列等)非常有用。
雪花算法的另一個優(yōu)勢是其擴展性好。通過調(diào)整數(shù)據(jù)中心ID和機器ID的位數(shù),可以靈活適應不同規(guī)模的系統(tǒng)擴展需要。這使得雪花算法不僅適用于大型系統(tǒng),也適用于中小型系統(tǒng),甚至是動態(tài)擴展的云環(huán)境。
總之,雪花算法是解決分布式系統(tǒng)中ID生成問題的一個高效、可靠的解決方案,它通過獨特的設計滿足了高性能、高可用性和可擴展性的需求。
6. UUID與雪花算法的比較
UUID和雪花算法都是在特定場景下生成唯一ID的有效工具。然而,它們在性能、應用場景和選擇依據(jù)方面有著顯著的差異。
性能比較
- UUID:UUID的生成過程非常簡單,只需要調(diào)用函數(shù)即可立即生成。因此,UUID的生成性能很高。然而,UUID的長度較長(32位十六進制),在存儲和傳輸上會占用更多的資源。此外,如果在數(shù)據(jù)庫中使用UUID作為主鍵,可能會導致索引性能下降。
- 雪花算法:雪花算法生成的ID長度較短(64位整數(shù)),在存儲和傳輸上更加高效。而且,由于雪花算法生成的ID是有序的,因此在數(shù)據(jù)庫中使用雪花算法生成的ID作為主鍵可以提高索引性能。然而,雪花算法的生成過程比UUID更復雜,需要維護時間戳、數(shù)據(jù)中心ID、機器ID和序列號等信息。
應用場景差異
- UUID:UUID最大的優(yōu)點是可以在任何地方生成,不需要考慮系統(tǒng)的分布式架構(gòu)。因此,UUID非常適合在分布式系統(tǒng)中作為全局唯一標識符使用。
- 雪花算法:雪花算法最大的優(yōu)點是生成的ID是有序的,非常適合在需要保證順序的場景中使用。例如,如果需要按照ID的生成順序進行數(shù)據(jù)處理,那么雪花算法會是一個更好的選擇。
選擇依據(jù)
在選擇UUID和雪花算法時,需要考慮以下幾個因素:
- 系統(tǒng)架構(gòu):如果系統(tǒng)是分布式的,并且需要在多個節(jié)點上生成唯一ID,那么雪花算法可能是一個更好的選擇。如果系統(tǒng)架構(gòu)較簡單,或者不需要在多個節(jié)點上生成唯一ID,那么UUID可能是一個更好的選擇。
- 性能需求:如果系統(tǒng)對存儲和傳輸效率有較高的要求,那么應該選擇生成長度較短的雪花算法ID。如果系統(tǒng)對生成ID的速度有較高的要求,那么應該選擇生成速度較快的UUID。
- 順序需求:如果系統(tǒng)需要按照ID的生成順序進行操作,那么應該選擇雪花算法。如果系統(tǒng)不需要保證ID的順序,那么可以選擇UUID。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringSecurity實現(xiàn)動態(tài)加載權(quán)限信息的方法
這篇文章主要介紹了SpringSecurity實現(xiàn)動態(tài)加載權(quán)限信息,本文給大家介紹的非常詳細,對大家的學習或工作具有一定需要的朋友可以參考下2022-01-01Java求一個分數(shù)數(shù)列的前20項之和的實現(xiàn)代碼
這篇文章主要介紹了Java求一個分數(shù)數(shù)列的前20項之和的實現(xiàn)代碼,需要的朋友可以參考下2017-02-02Spring Boot 配置文件(application.yml、application-dev.y
本文主要介紹了Spring Boot 配置文件,主要包含application.yml、application-dev.yml、application-test.yml,具有一定的參考價值,感興趣的可以了解一下2024-03-03Java POI實現(xiàn)將導入Excel文件的示例代碼
這篇文章主要介紹了Java POI實現(xiàn)將導入Excel文件的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-02-02Mybatis操作多數(shù)據(jù)源的實現(xiàn)
本文主要介紹了Mybatis操作多數(shù)據(jù)源,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-05-05基于spring如何實現(xiàn)事件驅(qū)動實例代碼
這篇文章主要給大家介紹了關(guān)于基于spring如何實現(xiàn)事件驅(qū)動的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用spring具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-04-04