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

Java雪花算法的原理和實現(xiàn)方法

 更新時間:2023年10月01日 10:52:34   作者:goyeer  
這篇文章主要介紹了Java雪花算法的原理和實現(xiàn)方法,雪花算法是一種分布式唯一ID生成算法,可以生成全局唯一的ID標識符,就像自然界中雪花一般沒有相同的雪花,下面將詳細介紹,感興趣的可以學(xué)習(xí)一下

一、概述

分布式高并發(fā)的環(huán)境下,常見的就是12306節(jié)日訂票,在大量用戶同是搶購一個方向的票,毫秒級的時間下可能生成數(shù)萬個訂單,此時為確保生成訂單ID的唯一性變得至關(guān)重要。此時秒殺環(huán)境下,不僅要保障ID唯一性,還得確保ID生成的優(yōu)先度。

二、生成ID規(guī)則部分硬性要求

  • 全局唯一:不能出現(xiàn)重復(fù)的ID號,既然是唯一標識,這是最基本的要求。
  • 趨勢遞增:在MySQL的InnoDB引擎中適用的是聚集索引,由于多數(shù)RDBMS使用B+Tree的數(shù)據(jù)結(jié)構(gòu)來存儲索引數(shù)據(jù),在主鍵的選擇上我們盡量使用有序的主鍵保證寫入性能。
  • 單調(diào)遞增:保證下一個ID一定大于上一個ID,如事務(wù)版本號、排序等特殊需求。
  • 信息安全:如果ID是連續(xù)的,惡意用戶的抓取工作就非常容易,直接按照順序下載指定URL即可;如果是訂單號就危險。
  • 含有時間戳:生成的ID包含完整的時間戳信息。

三、ID號生成系統(tǒng)可用性要求

  • 高可用:發(fā)一個獲取分布式ID的請求,服務(wù)器就是保證99.9999%的情況下給我創(chuàng)建一個唯一分布式ID。
  • 低延遲:發(fā)一個獲取分布式ID的請求,服務(wù)器要快,極速。
  • 高QPS:如果一次請求10萬個分布式ID,服務(wù)器要頂住并成功創(chuàng)建10萬個分布式ID。

四、解決分布式ID通用方案

4.1 UUID

UUID(Universally Unique Identifier)的標準型式包含32個16進制數(shù)字,以連字號分為五段,形式為:8-4-4-4-12的36個字符,示例:1E785B2B-111C-752A-997B-3346E7495CE2;UUID性能非常高,不依賴網(wǎng)絡(luò),本地生成。

UUID缺點:

無序,無法預(yù)測它的生成順序,不能生成遞增有序的數(shù)字。在MySql官方推薦主鍵約短越好,UUID是一個32位的字符串,所以不推薦使用。

索引,B+Tree索引的分裂

分布式Id是主鍵,主鍵是聚簇索引。Mysql的索引是B+Tree來實現(xiàn)的,每次新的UUID數(shù)據(jù)的插入,為了新的UUID數(shù)據(jù)的插入,為了查詢的優(yōu)化,都會對索引底部的B+Tree進行修改;因為UUID數(shù)據(jù)是無序的,所以每一次UUID數(shù)據(jù)的插入都會對主鍵的聚簇索引做很大的修改,在做數(shù)據(jù)Insert時,會插入主鍵是無序的,會導(dǎo)致一些中間節(jié)點的產(chǎn)生分裂,會導(dǎo)致大量不飽和的節(jié)點。這樣大大降低了數(shù)據(jù)庫插入的性能。

4.2 數(shù)據(jù)庫自增主鍵

單機

在分布式里面,數(shù)據(jù)庫的自增ID機制的主要原理是:數(shù)據(jù)庫自增ID和MySql數(shù)據(jù)庫的replace into實現(xiàn)的。

Replace into的含義是插入一條紀錄,如果表中唯一索引的值遇到?jīng)_突,則替換老數(shù)據(jù)。

在單體應(yīng)用的時候,自增長ID使用,但是在集群分布式應(yīng)用中單體應(yīng)用就不適合。

  • 系統(tǒng)水平擴展比較困難,比如定義好了增長步長和機器臺數(shù)之后,在大量添加服務(wù)器時,需要重新設(shè)置初始值,這樣可操作性差,所以系統(tǒng)水平擴展方案復(fù)雜度高難以實現(xiàn)。
  • 數(shù)據(jù)庫壓力大,每次獲取ID都需要讀寫一次數(shù)據(jù)庫,非常影響性能,不符合分布式ID里面的延遲低和要高QPS的規(guī)則(在高并發(fā)下,如果都去數(shù)據(jù)庫里面獲取Id,非常影響性能的。)

4.3 基于Redis生成全局id策略

在Redis集群情況下,同樣和MySql一樣需要設(shè)置不同的增長步長,同時key一定要設(shè)置有效期??梢允褂肦edis集群來獲取更高的吞吐量。

五、SnowFlake(雪花算法)

而Twitter的SnowFlake解決了這種需求,最初Twitter把存儲系統(tǒng)從MySQL遷移到Cassandra(由Facebook開發(fā)一套開源分布式NoSQL數(shù)據(jù)庫系統(tǒng)) 因為Cassandra沒有順序ID生成機制,所以開發(fā)了這樣一套全局唯一ID生成服務(wù)。SnowFlake每秒能產(chǎn)生26萬個自增可排序的ID。

5.1 SnowFlake特點

  • Twitter的SnowFlake生成ID能夠按照時間有序生成。
  • SnowFlake算法生成Id的結(jié)果是一個64bit大小的整數(shù),為一個Long型(轉(zhuǎn)換成字符串后長度最多19)。
  • 分布式系統(tǒng)內(nèi)不會產(chǎn)生ID碰撞(由datacenter和workerid作為區(qū)分)并且效率較高。

5.2 SnowFlake結(jié)構(gòu)

5.3 雪花算法原理

雪花算法的原理就是生成一個的64位比特位的long類型的唯一id

  • 最高1位固定值0,因為生成的id是正整數(shù),如果是1就是負值。
  • 緊接著是41位存儲毫秒級時間戳,2^41/(1000 * 60 * 24 * 365) = 69 ,大概可以使用69年。
  • 接下來10位存儲機器碼,包括5位DataCenterId和5位WorkerId,最多可以部署2^10=1024臺機器。
  • 最后12位存儲序列號,同一毫秒時間戳?xí)r,通過這個遞增的序列號來區(qū)分,即對于同一臺機器而言,同一毫秒級時間戳下,可以生成2^12=4096個不重復(fù)id。

可以將雪花算法作為一個單獨的服務(wù)進行部署,然后需要全局唯一id的系統(tǒng),請求雪花算法服務(wù)獲取id即可。

對于每一個雪花算法服務(wù),需要先指定10位的機器碼,這個根據(jù)自身業(yè)務(wù)進行設(shè)定即可。例如機房號+機器號,機器號+服務(wù)號,或者時其他區(qū)別標識的10位比特位的整數(shù)都行。

5.4 算法實現(xiàn)

package com.goyeer;
import java.util.Date;
public class SnowFlakeUtil {
    private static SnowFlakeUtil snowFlakeUtil;
    static {
        snowFlakeUtil = new SnowFlakeUtil();
    }
    // 初始時間戳(紀年),可用雪花算法服務(wù)上線時間戳的值
    //
    private static final long INIT_EPOCH = 1694263918335L;
    // 時間位取&
    private static final long TIME_BIT = 0b1111111111111111111111111111111111111111110000000000000000000000L;
    // 記錄最后使用的毫秒時間戳,主要用于判斷是否同一毫秒,以及用于服務(wù)器時鐘回撥判斷
    private long lastTimeMillis = -1L;
    // dataCenterId占用的位數(shù)
    private static final long DATA_CENTER_ID_BITS = 5L;
    // dataCenterId占用5個比特位,最大值31
    // 0000000000000000000000000000000000000000000000000000000000011111
    private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
    // dataCenterId
    private long dataCenterId;
    // workId占用的位數(shù)
    private static final long WORKER_ID_BITS = 5L;
    // workId占用5個比特位,最大值31
    // 0000000000000000000000000000000000000000000000000000000000011111
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    // workId
    private long workerId;
    // 最后12位,代表每毫秒內(nèi)可產(chǎn)生最大序列號,即 2^12 - 1 = 4095
    private static final long SEQUENCE_BITS = 12L;
    // 掩碼(最低12位為1,高位都為0),主要用于與自增后的序列號進行位與,如果值為0,則代表自增后的序列號超過了4095
    // 0000000000000000000000000000000000000000000000000000111111111111
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
    // 同一毫秒內(nèi)的最新序號,最大值可為 2^12 - 1 = 4095
    private long sequence;
    // workId位需要左移的位數(shù) 12
    private static final long WORK_ID_SHIFT = SEQUENCE_BITS;
    // dataCenterId位需要左移的位數(shù) 12+5
    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    // 時間戳需要左移的位數(shù) 12+5+5
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
    /**
     * 無參構(gòu)造
     */
    public SnowFlakeUtil() {
        this(1, 1);
    }
    /**
     * 有參構(gòu)造
     * @param dataCenterId
     * @param workerId
     */
    public SnowFlakeUtil(long dataCenterId, long workerId) {
        // 檢查dataCenterId的合法值
        if (dataCenterId < 0 || dataCenterId > MAX_DATA_CENTER_ID) {
            throw new IllegalArgumentException(
                    String.format("dataCenterId 值必須大于 0 并且小于 %d", MAX_DATA_CENTER_ID));
        }
        // 檢查workId的合法值
        if (workerId < 0 || workerId > MAX_WORKER_ID) {
            throw new IllegalArgumentException(String.format("workId 值必須大于 0 并且小于 %d", MAX_WORKER_ID));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }
    /**
     * 獲取唯一ID
     * @return
     */
    public static Long getSnowFlakeId() {
        return snowFlakeUtil.nextId();
    }
    /**
     * 通過雪花算法生成下一個id,注意這里使用synchronized同步
     * @return 唯一id
     */
    public synchronized long nextId() {
        long currentTimeMillis = System.currentTimeMillis();
        System.out.println(currentTimeMillis);
        // 當前時間小于上一次生成id使用的時間,可能出現(xiàn)服務(wù)器時鐘回撥問題
        if (currentTimeMillis < lastTimeMillis) {
            throw new RuntimeException(
                    String.format("可能出現(xiàn)服務(wù)器時鐘回撥問題,請檢查服務(wù)器時間。當前服務(wù)器時間戳:%d,上一次使用時間戳:%d", currentTimeMillis,
                            lastTimeMillis));
        }
        if (currentTimeMillis == lastTimeMillis) {
            // 還是在同一毫秒內(nèi),則將序列號遞增1,序列號最大值為4095
            // 序列號的最大值是4095,使用掩碼(最低12位為1,高位都為0)進行位與運行后如果值為0,則自增后的序列號超過了4095
            // 那么就使用新的時間戳
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                currentTimeMillis = getNextMillis(lastTimeMillis);
            }
        } else { // 不在同一毫秒內(nèi),則序列號重新從0開始,序列號最大值為4095
            sequence = 0;
        }
        // 記錄最后一次使用的毫秒時間戳
        lastTimeMillis = currentTimeMillis;
        // 核心算法,將不同部分的數(shù)值移動到指定的位置,然后進行或運行
        // <<:左移運算符, 1 << 2 即將二進制的 1 擴大 2^2 倍
        // |:位或運算符, 是把某兩個數(shù)中, 只要其中一個的某一位為1, 則結(jié)果的該位就為1
        // 優(yōu)先級:<< > |
        return
                // 時間戳部分
                ((currentTimeMillis - INIT_EPOCH) << TIMESTAMP_SHIFT)
                        // 數(shù)據(jù)中心部分
                        | (dataCenterId << DATA_CENTER_ID_SHIFT)
                        // 機器表示部分
                        | (workerId << WORK_ID_SHIFT)
                        // 序列號部分
                        | sequence;
    }
    /**
     * 獲取指定時間戳的接下來的時間戳,也可以說是下一毫秒
     * @param lastTimeMillis 指定毫秒時間戳
     * @return 時間戳
     */
    private long getNextMillis(long lastTimeMillis) {
        long currentTimeMillis = System.currentTimeMillis();
        while (currentTimeMillis <= lastTimeMillis) {
            currentTimeMillis = System.currentTimeMillis();
        }
        return currentTimeMillis;
    }
    /**
     * 獲取隨機字符串,length=13
     * @return
     */
    public static String getRandomStr() {
        return Long.toString(getSnowFlakeId());
    }
    /**
     * 從ID中獲取時間
     * @param id 由此類生成的ID
     * @return
     */
    public static Date getTimeBySnowFlakeId(long id) {
        return new Date(((TIME_BIT & id) >> 22) + INIT_EPOCH);
    }
    public static void main(String[] args) {
        SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil();
        long id = snowFlakeUtil.nextId();
        System.out.println(id);
        Date date = SnowFlakeUtil.getTimeBySnowFlakeId(id);
        System.out.println(date);
        long time = date.getTime();
        System.out.println(time);
        System.out.println(getRandomStr());
    }
}

5.5 雪花算法優(yōu)點

  • 高并發(fā)分布式環(huán)境下生成不重復(fù) id,每秒可生成百萬個不重復(fù) id。
  • 基于時間戳,以及同一時間戳下序列號自增,基本保證 id 有序遞增。
  • 不依賴第三方庫或者中間件。
  • 算法簡單,在內(nèi)存中進行,效率高。

5.6 雪花算法缺點

依賴服務(wù)器時間,服務(wù)器時鐘回撥時可能會生成重復(fù) id。算法中可通過記錄最后一個生成 id 時的時間戳來解決,每次生成 id 之前比較當前服務(wù)器時鐘是否被回撥,避免生成重復(fù) id。

六、總結(jié)

其實雪花算法每一部分占用的比特位數(shù)量并不是固定死的。例如你的業(yè)務(wù)可能達不到 69 年之久,那么可用減少時間戳占用的位數(shù),雪花算法服務(wù)需要部署的節(jié)點超過1024 臺,那么可將減少的位數(shù)補充給機器碼用。

注意,雪花算法中 41 位比特位不是直接用來存儲當前服務(wù)器毫秒時間戳的,而是需要當前服務(wù)器時間戳減去某一個初始時間戳值,一般可以使用服務(wù)上線時間作為初始時間戳值。

對于機器碼,可根據(jù)自身情況做調(diào)整,例如機房號,服務(wù)器號,業(yè)務(wù)號,機器 IP 等都是可使用的。對于部署的不同雪花算法服務(wù)中,最后計算出來的機器碼能區(qū)分開來即可。

以上就是Java雪花算法的原理和實現(xiàn)方法的詳細內(nèi)容,更多關(guān)于Java雪花算法的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • springboot整合mybatis中的問題及出現(xiàn)的一些問題小結(jié)

    springboot整合mybatis中的問題及出現(xiàn)的一些問題小結(jié)

    這篇文章主要介紹了springboot整合mybatis中的問題及出現(xiàn)的一些問題小結(jié),本文給出了解決方案,需要的朋友可以參考下
    2018-11-11
  • SpringBoot中的@RequestMapping注解的用法示例

    SpringBoot中的@RequestMapping注解的用法示例

    @RequestMapping注解是SpringBoot中最常用的注解之一,它可以幫助開發(fā)者定義和處理HTTP請求,本篇文章我們將詳細為大家介紹如何使用SpringBoot中的@RequestMapping注解,感興趣的同學(xué)跟著小編一起來學(xué)習(xí)吧
    2023-06-06
  • 關(guān)于maven本地倉庫的配置方式

    關(guān)于maven本地倉庫的配置方式

    這篇文章主要介紹了關(guān)于maven本地倉庫的配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java多線程之線程安全問題詳解

    Java多線程之線程安全問題詳解

    這篇文章主要為大家詳細介紹了Java多線程之線程安全問題,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • Java?獲取本機IP地址的實例代碼

    Java?獲取本機IP地址的實例代碼

    這篇文章主要介紹了Java?獲取本機IP地址,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-05-05
  • Java 處理超大數(shù)類型之BigInteger案例詳解

    Java 處理超大數(shù)類型之BigInteger案例詳解

    這篇文章主要介紹了Java 處理超大數(shù)類型之BigInteger案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-09-09
  • BloomFilter如何快速檢查用戶名重復(fù)

    BloomFilter如何快速檢查用戶名重復(fù)

    這篇文章主要介紹了BloomFilter如何快速檢查用戶名重復(fù)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • java 獲取HttpRequest Header的幾種方法(必看篇)

    java 獲取HttpRequest Header的幾種方法(必看篇)

    下面小編就為大家?guī)硪黄猨ava 獲取HttpRequest Header的幾種方法(必看篇)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • springmvc與mybatis集成配置實例詳解

    springmvc與mybatis集成配置實例詳解

    這篇文章主要介紹了springmvc與mybatis集成配置實例詳解的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-09-09
  • 關(guān)于Spring Boot對jdbc的支持問題

    關(guān)于Spring Boot對jdbc的支持問題

    這篇文章主要介紹了關(guān)于Spring Boot對jdbc的支持問題,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04

最新評論