欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java UUID&雪花算法生成和使用場景詳解

 更新時間:2025年01月11日 11:06:53   作者:CC大煊  
UUID和雪花算法都是用于生成唯一標識符的有效工具,它們各有優(yōu)勢:UUID簡單易用,但長度較長,適用于分布式系統(tǒng);雪花算法生成的ID較短且有序,適用于需要保證順序的場景,在選擇算法時,需要考慮系統(tǒng)架構(gòu)、性能需求和順序需求等因素

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位中包含了以下幾個部分:

  1. 時間戳 - 占用41位,精確到毫秒級,可以使用約69年。
  2. 數(shù)據(jù)中心ID - 占用5位,可以有最多32個數(shù)據(jù)中心。
  3. 機器ID - 占用5位,每個數(shù)據(jù)中心可以有最多32臺機器。
  4. 序列號 - 占用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)文章

最新評論