最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

redis實(shí)現(xiàn)分布式全局唯一id的示例代碼

 更新時(shí)間:2024年04月03日 10:02:55   作者:Hanson Huang  
在某些場(chǎng)景中,我們需要生成全局的唯一ID,本文主要介紹了redis實(shí)現(xiàn)分布式全局唯一id的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下

一、前言

在很多項(xiàng)目中生成類似訂單編號(hào)、用戶編號(hào)等有唯一性數(shù)據(jù)時(shí)還用的UUID工具,或者自己根據(jù)時(shí)間戳+隨機(jī)字符串等組合來生成,在并發(fā)小的時(shí)候很少出問題,當(dāng)并發(fā)上來時(shí)就很可能出現(xiàn)重復(fù)編號(hào)的問題了,單體項(xiàng)目和分布式項(xiàng)目都是如此,要想解決這個(gè)問題也有很多種方法,可以自己寫一個(gè)唯一ID生成規(guī)則,也可以通過數(shù)據(jù)庫來實(shí)現(xiàn)全局ID生成這個(gè)和使用Redis實(shí)現(xiàn)其實(shí)類似,還可以使用比較成熟的雪花算法工具實(shí)現(xiàn),每種方法都有各自的優(yōu)缺點(diǎn)這里不展開說明,這里詳細(xì)說明如何使用Redis實(shí)現(xiàn)生成分布式全局唯一ID。

還有一個(gè)問題為什么不能直接使用數(shù)據(jù)庫的自增ID,而是需要單獨(dú)生成一個(gè)分布式全局唯一ID,類似訂單IDON202311090001,在數(shù)據(jù)庫中有自增ID,對(duì)于當(dāng)前業(yè)務(wù)來說就是唯一的為什么不能用,還要去生成一個(gè)獨(dú)立的訂單ID,對(duì)于這個(gè)問題要從幾個(gè)方面分析:

1、數(shù)據(jù)庫自增ID是有序增長的很容易就被人猜到,比如我現(xiàn)在下一單看到的訂單ID為999那么就知道你的系統(tǒng)里最多只有999單,還有如果接口設(shè)計(jì)不合理,比如取消訂單接口只校驗(yàn)了用戶是否登錄沒有校驗(yàn)訂單是否屬于該用戶,接收一個(gè)訂單ID就能將訂單取消,那么這樣很容易就被人抓住漏洞,類似的情況有很多,也很多人寫接口是不會(huì)注意這個(gè)問題。
2、這種自增ID沒有意義,而且不同業(yè)務(wù)的自增ID是重合的,對(duì)于信息區(qū)分度很低,而且考慮到多業(yè)務(wù)交互和用戶端展示也都是不合適的,想想看要是你在某寶下單,訂單ID是999,或者在對(duì)接別人訂單系統(tǒng)時(shí),給你的訂單ID是999是不是很奇怪。
3、分庫分表時(shí)自增ID會(huì)重復(fù)

全局ID生成器:是一種在【分布式系統(tǒng)下】用來生成全局唯一ID的工具;
全局ID需要滿足的特性:
1.唯一性
2.高可用:集群、哨兵機(jī)制;
3.高性能
4.遞增性:Redis中的String數(shù)據(jù)類型的有自增特性!
5.安全性:將自增數(shù)值進(jìn)行拼接,不容易猜出來;

ID結(jié)構(gòu):符號(hào)位(1位) + 時(shí)間戳(31位) 序列號(hào)(32位)
時(shí)間戳為從起始時(shí)間到現(xiàn)在的時(shí)間差;
理論上支持1秒鐘2^32個(gè)訂單;

在這里插入圖片描述

二、如何通過Redis設(shè)計(jì)一個(gè)分布式全局唯一ID生成工具

用戶下單調(diào)用下單邏輯,先進(jìn)行業(yè)務(wù)邏輯處理,然后攜帶訂單ID標(biāo)識(shí)通過分布式全局唯一ID工具獲取一個(gè)唯一的訂單ID,這個(gè)訂單ID標(biāo)識(shí)就是用于區(qū)分業(yè)務(wù)的,獲取到訂單ID后將數(shù)據(jù)組裝入庫,分布式全局唯一ID工具可以做成一個(gè)內(nèi)嵌的utils,也可以封裝成一個(gè)獨(dú)立的jar,還可以做成一個(gè)分布式全局唯一ID生成服務(wù)供其它業(yè)務(wù)服務(wù)調(diào)用。

在這里插入圖片描述

2.1 使用 Redis 計(jì)數(shù)器實(shí)現(xiàn)

RedisString結(jié)構(gòu)提供了計(jì)數(shù)器自增功能,類似Java中的原子類,還要優(yōu)于Java的原子類,因?yàn)镽edis是單線程執(zhí)行的緩存讀寫本身就是線程安全的,也不用進(jìn)行原子類的樂觀鎖操作,每一次獲取分布式全局唯一ID時(shí)就將自增序列加1。

# 給key為GENERATEID:NO的value自增1,如果這key不存在則會(huì)添加到Redis中并且設(shè)置value為1
## GENERATEID:key前綴
## NO:訂單ID標(biāo)識(shí)
127.0.0.1:6379> incr GENERATEID:NO
(integer) 1

2.2 使用 Redis Hash結(jié)構(gòu)實(shí)現(xiàn)

Redis Hash結(jié)構(gòu)中的每一個(gè)field也可以進(jìn)行自增操作,可以用一個(gè)Hash結(jié)構(gòu)存儲(chǔ)所有的標(biāo)識(shí)信息和自增序列,方便管理,比較適合并發(fā)不高的小項(xiàng)目所有服務(wù)都是用的一個(gè)Redis,如果并發(fā)較高就不合適了,畢竟Redis操作普通String結(jié)構(gòu)肯定比操作Hash結(jié)構(gòu)快。

# 給key為GENERATEID,field為no的value自增1,如果這key不存在則會(huì)添加到Redis中并且設(shè)置value為1
## GENERATEID:分布式全局唯一ID Hash key
## NO:Hash結(jié)構(gòu)中的field
127.0.0.1:6379> hincrby GENERATEID NO 1
(integer) 1

三、通過代碼實(shí)現(xiàn)分布式全局唯一ID工具

這里使用Redis 計(jì)數(shù)器實(shí)現(xiàn),自增序列以天為單位存儲(chǔ),在實(shí)際業(yè)務(wù)中,比如生成訂單編號(hào)組成規(guī)則都類似NO1699631999000-1(業(yè)務(wù)標(biāo)識(shí)key+當(dāng)前時(shí)間戳+自增序列),這個(gè)規(guī)則可以自己定義,保證最終生成的訂單編號(hào)不重復(fù)即可,不建議直接一個(gè)自增序列干到底,訂單編號(hào)這類型的數(shù)據(jù)都是有長度限制的,或者是要求生成20字符的訂單編號(hào),如果增長的過長反而不好處理。

3.1 導(dǎo)入依賴配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.1</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

3.2 配置yml文件

spring:
  #redis配置信息
  redis:
    ## Redis數(shù)據(jù)庫索引(默認(rèn)為0)
    database: 0
    ## Redis服務(wù)器地址
    host: 127.0.0.1
    ## Redis服務(wù)器連接端口
    port: 6379
    ## Redis服務(wù)器連接密碼(默認(rèn)為空)
    password:
    ## 連接超時(shí)時(shí)間(毫秒)
    timeout: 1200
    lettuce:
      pool:
        ## 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
        max-active: 8
        ## 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
        max-wait: -1
        ## 連接池中的最大空閑連接
        max-idle: 8
        ## 連接池中的最小空閑連接
        min-idle: 1

3.3 序列化配置

@Configuration
public class RedisConfig {

    //編寫我們自己的配置redisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // JSON序列化配置
        Jackson2JsonRedisSerializer jsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper=new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jsonRedisSerializer.setObjectMapper(objectMapper);

        // String的序列化
        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();

        //key和hash的key都采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);

        //value和hash的value都采用jackson的序列化方式
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);

        template.afterPropertiesSet();

        return template;
    }
}

3.4 編寫獲取工具

@Component
public class RedisGenerateIDUtils {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    // key前綴
    private String PREFIX = "GENERATEID:";

    /**
     * 獲取全局唯一ID
     * @param key 業(yè)務(wù)標(biāo)識(shí)key
     */
    public String generateId(String key) {
        // 獲取對(duì)應(yīng)業(yè)務(wù)自增序列
        Long incr = getIncr(key);
        // 組裝最后的結(jié)果,這里可以根據(jù)需要自己定義,這里是按照業(yè)務(wù)標(biāo)識(shí)key+當(dāng)前時(shí)間戳+自增序列進(jìn)行組裝
        String resultID = key + System.currentTimeMillis() + "-" + incr;
        return resultID;
    }

    /**
     * 獲取對(duì)應(yīng)業(yè)務(wù)自增序列
     */
    private Long getIncr(String key) {
        String cacheKey = getCacheKey(key);
        Long increment = 0L;
        // 判斷Redis中是否存在這個(gè)自增序列,如果不存在添加一個(gè)序列并且設(shè)置一個(gè)過期時(shí)間
        if (!redisTemplate.hasKey(cacheKey)) {
            // 這里存在線程安全問題,需要加分布式鎖,這里做簡(jiǎn)單實(shí)現(xiàn)
            String lockKey = cacheKey + "_LOCK";
            // 設(shè)置分布式鎖
            boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 30, TimeUnit.SECONDS);
            if (!lock) {
                // 如果沒有拿到鎖進(jìn)行自旋
                return getIncr(key);
            }
            increment = redisTemplate.opsForValue().increment(cacheKey);
            // 我這里設(shè)置24小時(shí),可以根據(jù)實(shí)際情況設(shè)置當(dāng)前時(shí)間到當(dāng)天結(jié)束時(shí)間的插值
            redisTemplate.expire(cacheKey, 24, TimeUnit.HOURS);

            // 釋放鎖
            redisTemplate.delete(lockKey);
        } else {
            increment = redisTemplate.opsForValue().increment(cacheKey);
        }

        return increment;
    }

    /**
     * 組裝緩存key
     */
    private String getCacheKey(String key) {
        return PREFIX + key + ":" + getYYYYMMDD();
    }

    /**
     * 獲取當(dāng)前YYYYMMDD格式年月日
     */
    private String getYYYYMMDD() {
        LocalDate currentDate = LocalDate.now();
        int year = currentDate.getYear();
        int month = currentDate.getMonthValue();
        int day = currentDate.getDayOfMonth();
        return "" + year + month + day;
    }
}

3.5 測(cè)試獲取工具

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedisUniqueIdDemoApplication.class)
class RedisUniqueIdDemoApplicationTests {

    @Resource
    private RedisGenerateIDUtils redisGenerateIDUtils;
    

    @Test
    public void test() throws InterruptedException {
        // 定義一個(gè)線程池 設(shè)置核心線程數(shù)和最大線程數(shù)都為100,隊(duì)列根據(jù)需要設(shè)置
        ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 100, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10000));
        CountDownLatch countDownLatch = new CountDownLatch(10000);

        long beginTime = System.currentTimeMillis();
        // 獲取10000個(gè)全局唯一ID 看看是否有重復(fù)
        CopyOnWriteArraySet<String> ids = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 10000; i++) {
            executor.execute(() -> {
                // 獲取全局唯一ID
                long beginTime02 = System.currentTimeMillis();
                String orderNo = redisGenerateIDUtils.generateId("NO");
                System.out.println(orderNo);
                System.out.println("獲取單個(gè)ID耗時(shí) time=" + (System.currentTimeMillis() - beginTime02));
                if (ids.contains(orderNo)) {
                    System.out.println("重復(fù)ID=" + orderNo);
                } else {
                    ids.add(orderNo);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        // 打印獲取到的全局唯一ID集合數(shù)量
        System.out.println("獲取到全局唯一ID count=" + ids.size());
        System.out.println("耗時(shí)毫秒 time=" + (System.currentTimeMillis() - beginTime));
    }
}

知識(shí)小貼士:關(guān)于countdownlatch

countdownlatch名為信號(hào)槍:主要的作用是同步協(xié)調(diào)在多線程的等待于喚醒問題

我們?nèi)绻麤]有CountDownLatch ,那么由于程序是異步的,當(dāng)異步程序沒有執(zhí)行完時(shí),主線程就已經(jīng)執(zhí)行完了,然后我們期望的是分線程全部走完之后,主線程再走,所以我們此時(shí)需要使用到CountDownLatch

CountDownLatch 中有兩個(gè)最重要的方法

  • countDown

  • await

await 方法 是阻塞方法,我們擔(dān)心分線程沒有執(zhí)行完時(shí),main線程就先執(zhí)行,所以使用await可以讓main線程阻塞,那么什么時(shí)候main線程不再阻塞呢?當(dāng)CountDownLatch 內(nèi)部維護(hù)的 變量變?yōu)?時(shí),就不再阻塞,直接放行,那么什么時(shí)候CountDownLatch 維護(hù)的變量變?yōu)? 呢,我們只需要調(diào)用一次countDown ,內(nèi)部變量就減少1,我們讓分線程和變量綁定, 執(zhí)行完一個(gè)分線程就減少一個(gè)變量,當(dāng)分線程全部走完,CountDownLatch 維護(hù)的變量就是0,此時(shí)await就不再阻塞,統(tǒng)計(jì)出來的時(shí)間也就是所有分線程執(zhí)行完后的時(shí)間。

四、運(yùn)行結(jié)果

redis結(jié)果

在這里插入圖片描述

代碼運(yùn)行結(jié)果,id沒有出現(xiàn)重復(fù):

在這里插入圖片描述

代碼地址:Github

到此這篇關(guān)于redis實(shí)現(xiàn)分布式全局唯一id的示例代碼的文章就介紹到這了,更多相關(guān)redis 分布式全局唯一id內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Redis動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)緩存策略設(shè)計(jì)

    Redis動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)緩存策略設(shè)計(jì)

    本文主要介紹了Redis動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)緩存策略設(shè)計(jì),包括熱點(diǎn)數(shù)據(jù)識(shí)別、動(dòng)態(tài)緩存、多級(jí)緩存、預(yù)加載機(jī)制、更新策略以及監(jiān)控告警等,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-01-01
  • 深入理解Redis BigKey

    深入理解Redis BigKey

    本文主要介紹了Redis中的BigKey問題及其處理方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-02-02
  • Redis分布式限流組件設(shè)計(jì)與使用實(shí)例

    Redis分布式限流組件設(shè)計(jì)與使用實(shí)例

    本文主要講解基于 自定義注解+Aop+反射+Redis+Lua表達(dá)式 實(shí)現(xiàn)的限流設(shè)計(jì)方案。實(shí)現(xiàn)的限流設(shè)計(jì)與實(shí)際使用。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • redis如何清理緩存

    redis如何清理緩存

    本文主要介紹了redis如何清理緩存,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Redis哨兵主備切換的數(shù)據(jù)丟失問題及解決

    Redis哨兵主備切換的數(shù)據(jù)丟失問題及解決

    主備切換過程中可能會(huì)導(dǎo)致數(shù)據(jù)丟失,異步復(fù)制和腦裂是兩種主要原因,異步復(fù)制可能導(dǎo)致部分?jǐn)?shù)據(jù)未復(fù)制到slave而master宕機(jī),腦裂則可能導(dǎo)致多個(gè)master存在,舊master恢復(fù)后數(shù)據(jù)被清空,從而丟失數(shù)據(jù)
    2024-12-12
  • gem install redis報(bào)錯(cuò)的解決方案

    gem install redis報(bào)錯(cuò)的解決方案

    今天小編就為大家分享一篇關(guān)于gem install redis報(bào)錯(cuò)的解決方案,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • 詳解Redis實(shí)現(xiàn)分布式鎖的原理

    詳解Redis實(shí)現(xiàn)分布式鎖的原理

    分布式鎖,即分布式系統(tǒng)中的鎖,在單體應(yīng)用中我們通過鎖解決的是控制共享資源訪問的問題,而分布式鎖,就是解決了分布式系統(tǒng)中控制共享資源訪問的問題,本文講給大家詳細(xì)介紹一下Redis實(shí)現(xiàn)分布式鎖的原理,需要的朋友可以參考下
    2023-09-09
  • Redis集群的槽分配的幾種實(shí)現(xiàn)方法

    Redis集群的槽分配的幾種實(shí)現(xiàn)方法

    Redis集群中的槽用來管理和分配數(shù)據(jù)到不同的節(jié)點(diǎn),Redis集群總共有 16384個(gè)槽,所有的鍵根據(jù)哈希值被分配到這些槽中,下面將詳細(xì)介紹 Redis 集群的槽分配機(jī)制,感興趣的可以了解一下
    2025-09-09
  • Redis字符串String操作詳解從基礎(chǔ)到高級(jí)應(yīng)用小結(jié)

    Redis字符串String操作詳解從基礎(chǔ)到高級(jí)應(yīng)用小結(jié)

    本文全面解析Redis字符串類型,涵蓋設(shè)置、修改、數(shù)字運(yùn)算、過期處理及分布式鎖、計(jì)數(shù)器等高級(jí)應(yīng)用,提供基礎(chǔ)命令與性能優(yōu)化策略,助開發(fā)者高效利用這一核心數(shù)據(jù)類型實(shí)現(xiàn)緩存、統(tǒng)計(jì)等功能,感興趣的朋友一起看看吧
    2025-07-07
  • Redis+aop實(shí)現(xiàn)接口防刷(冪等)的解決方案

    Redis+aop實(shí)現(xiàn)接口防刷(冪等)的解決方案

    在高并發(fā)場(chǎng)景下,可能會(huì)因?yàn)榫W(wǎng)絡(luò)或者服務(wù)器原因,造成延遲,同時(shí)就是有可能會(huì)有人用腳本大量訪問你的接口,造成資源崩潰,所以本文給大家介紹了Redis+aop實(shí)現(xiàn)接口防刷(冪等)的解決方案,需要的朋友可以參考下
    2024-03-03

最新評(píng)論

海晏县| 平原县| 盐城市| 于田县| 三门峡市| 盐亭县| 沈丘县| 井陉县| 恭城| 大竹县| 泰安市| 阿图什市| 东方市| 衡山县| 连山| 新安县| 瑞昌市| 公安县| 河东区| 南丰县| 隆尧县| 固阳县| 勐海县| 夏津县| 密云县| 阿鲁科尔沁旗| 五大连池市| 香港 | 安达市| 宣化县| 亳州市| 威远县| 乌恰县| 定结县| 保康县| 牡丹江市| 成武县| 玛纳斯县| 水富县| 九龙坡区| 嘉荫县|