亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

一、概述

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

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

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

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

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

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

4.1 UUID

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

UUID缺點(diǎn):

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

索引,B+Tree索引的分裂

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

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

單機(jī)

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

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

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

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

4.3 基于Redis生成全局id策略

在Redis集群情況下,同樣和MySql一樣需要設(shè)置不同的增長步長,同時(shí)key一定要設(shè)置有效期。可以使用Redis集群來獲取更高的吞吐量。

五、SnowFlake(雪花算法)

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

5.1 SnowFlake特點(diǎn)

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

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

5.3 雪花算法原理

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

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

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

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

5.4 算法實(shí)現(xiàn)

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

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

5.6 雪花算法缺點(diǎn)

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

六、總結(jié)

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

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

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

以上就是Java雪花算法的原理和實(shí)現(xiàn)方法的詳細(xì)內(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請求,本篇文章我們將詳細(xì)為大家介紹如何使用SpringBoot中的@RequestMapping注解,感興趣的同學(xué)跟著小編一起來學(xué)習(xí)吧
    2023-06-06
  • 關(guān)于maven本地倉庫的配置方式

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

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

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

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

    Java?獲取本機(jī)IP地址的實(shí)例代碼

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

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

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

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

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

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

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

    springmvc與mybatis集成配置實(shí)例詳解

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

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

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

最新評論