redis實現分布式全局唯一id

目錄

  • 一、前言
  • 二、如何通過Redis設計一個分布式全局唯一ID生成工具
    • 2.1 使用 Redis 計數器實現
    • 2.2 使用 Redis Hash結構實現
  • 三、通過代碼實現分布式全局唯一ID工具
    • 3.1 導入依賴配置
    • 3.2 配置yml文件
    • 3.3 序列化配置
    • 3.4 編寫獲取工具
    • 3.5 測試獲取工具
  • 四、運行結果

一、前言

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

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

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

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

ID結構:
符號位(1位) + 時間戳(31位) + 序列號(32位)
時間戳為從起始時間到現在的時間差;
理論上支持1秒鐘2^32個訂單;

在這里插入圖片描述

二、如何通過Redis設計一個分布式全局唯一ID生成工具

用戶下單調用下單邏輯,先進行業務邏輯處理,然后攜帶訂單ID標識通過分布式全局唯一ID工具獲取一個唯一的訂單ID,這個訂單ID標識就是用于區分業務的,獲取到訂單ID后將數據組裝入庫,分布式全局唯一ID工具可以做成一個內嵌的utils,也可以封裝成一個獨立的jar,還可以做成一個分布式全局唯一ID生成服務供其它業務服務調用。

在這里插入圖片描述

2.1 使用 Redis 計數器實現

RedisString結構提供了計數器自增功能,類似Java中的原子類,還要優于Java的原子類,因為Redis是單線程執行的緩存讀寫本身就是線程安全的,也不用進行原子類的樂觀鎖操作,每一次獲取分布式全局唯一ID時就將自增序列加1

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

2.2 使用 Redis Hash結構實現

Redis Hash結構中的每一個field也可以進行自增操作,可以用一個Hash結構存儲所有的標識信息和自增序列,方便管理,比較適合并發不高的小項目所有服務都是用的一個Redis,如果并發較高就不合適了,畢竟Redis操作普通String結構肯定比操作Hash結構快。

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

三、通過代碼實現分布式全局唯一ID工具

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

3.1 導入依賴配置

<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數據庫索引(默認為0)database: 0## Redis服務器地址host: 127.0.0.1## Redis服務器連接端口port: 6379## Redis服務器連接密碼(默認為空)password:## 連接超時時間(毫秒)timeout: 1200lettuce:pool:## 連接池最大連接數(使用負值表示沒有限制)max-active: 8## 連接池最大阻塞等待時間(使用負值表示沒有限制)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 {@Resourceprivate RedisTemplate<String, Object> redisTemplate;// key前綴private String PREFIX = "GENERATEID:";/*** 獲取全局唯一ID* @param key 業務標識key*/public String generateId(String key) {// 獲取對應業務自增序列Long incr = getIncr(key);// 組裝最后的結果,這里可以根據需要自己定義,這里是按照業務標識key+當前時間戳+自增序列進行組裝String resultID = key + System.currentTimeMillis() + "-" + incr;return resultID;}/*** 獲取對應業務自增序列*/private Long getIncr(String key) {String cacheKey = getCacheKey(key);Long increment = 0L;// 判斷Redis中是否存在這個自增序列,如果不存在添加一個序列并且設置一個過期時間if (!redisTemplate.hasKey(cacheKey)) {// 這里存在線程安全問題,需要加分布式鎖,這里做簡單實現String lockKey = cacheKey + "_LOCK";// 設置分布式鎖boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 30, TimeUnit.SECONDS);if (!lock) {// 如果沒有拿到鎖進行自旋return getIncr(key);}increment = redisTemplate.opsForValue().increment(cacheKey);// 我這里設置24小時,可以根據實際情況設置當前時間到當天結束時間的插值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();}/*** 獲取當前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 測試獲取工具

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedisUniqueIdDemoApplication.class)
class RedisUniqueIdDemoApplicationTests {@Resourceprivate RedisGenerateIDUtils redisGenerateIDUtils;@Testpublic void test() throws InterruptedException {// 定義一個線程池 設置核心線程數和最大線程數都為100,隊列根據需要設置ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 100, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10000));CountDownLatch countDownLatch = new CountDownLatch(10000);long beginTime = System.currentTimeMillis();// 獲取10000個全局唯一ID 看看是否有重復CopyOnWriteArraySet<String> ids = new CopyOnWriteArraySet<>();for (int i = 0; i < 10000; i++) {executor.execute(() -> {// 獲取全局唯一IDlong beginTime02 = System.currentTimeMillis();String orderNo = redisGenerateIDUtils.generateId("NO");System.out.println(orderNo);System.out.println("獲取單個ID耗時 time=" + (System.currentTimeMillis() - beginTime02));if (ids.contains(orderNo)) {System.out.println("重復ID=" + orderNo);} else {ids.add(orderNo);}countDownLatch.countDown();});}countDownLatch.await();// 打印獲取到的全局唯一ID集合數量System.out.println("獲取到全局唯一ID count=" + ids.size());System.out.println("耗時毫秒 time=" + (System.currentTimeMillis() - beginTime));}
}

知識小貼士:關于countdownlatch

countdownlatch名為信號槍:主要的作用是同步協調在多線程的等待于喚醒問題

我們如果沒有CountDownLatch ,那么由于程序是異步的,當異步程序沒有執行完時,主線程就已經執行完了,然后我們期望的是分線程全部走完之后,主線程再走,所以我們此時需要使用到CountDownLatch

CountDownLatch 中有兩個最重要的方法

  • countDown

  • await

await 方法 是阻塞方法,我們擔心分線程沒有執行完時,main線程就先執行,所以使用await可以讓main線程阻塞,那么什么時候main線程不再阻塞呢?當CountDownLatch 內部維護的 變量變為0時,就不再阻塞,直接放行,那么什么時候CountDownLatch 維護的變量變為0 呢,我們只需要調用一次countDown ,內部變量就減少1,我們讓分線程和變量綁定, 執行完一個分線程就減少一個變量,當分線程全部走完,CountDownLatch 維護的變量就是0,此時await就不再阻塞,統計出來的時間也就是所有分線程執行完后的時間。

四、運行結果

redis結果
在這里插入圖片描述

代碼運行結果,id沒有出現重復:

在這里插入圖片描述

代碼地址:Github

覺得有用的話還請來個三連!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/713619.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/713619.shtml
英文地址,請注明出處:http://en.pswp.cn/news/713619.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux運維_Bash腳本_部署安裝DocBook-XML-4.5(XML-DTD)

Linux運維_Bash腳本_部署安裝DocBook-XML-4.5(XML-DTD) Bash (Bourne Again Shell) 是一個解釋器&#xff0c;負責處理 Unix 系統命令行上的命令。它是由 Brian Fox 編寫的免費軟件&#xff0c;并于 1989 年發布的免費軟件&#xff0c;作為 Sh (Bourne Shell) 的替代品。 您可…

leetcode 熱題 100_最長連續序列

題解一&#xff1a; 哈希表&#xff1a;找連續最長的數字序列&#xff0c;很容易聯想到排序&#xff0c;但排序的時間復雜度O(nlogN)過大&#xff0c;判題容易超時。因此我們需要使用哈希表來快速查找&#xff0c;序列中是否存在與某個數相鄰的數。用HashSet建立哈希表并去重&a…

【Javascript編程實操02】1、判斷一個年份是閏年還是平年 2、找到三個數中最小的數

目錄 前言 1、判斷一個年份是閏年還是平年 原理&#xff1a; 代碼&#xff1a; 實現效果&#xff1a; 2、找到三個數中最小的數 流程圖&#xff1a; 代碼&#xff1a; 實現效果&#xff1a; 總結 前言 本次繼續針對Javascript階段的if...else...的實操練習&#xff0…

IDEA 配置股票插件

IDEA配置股票基金實時查看插件&#xff0c;步驟如下&#xff1a; 打開Settings&#xff0c;找到Plugins&#xff0c;在Marketplace中搜索&#xff1a;Money Never Sleeps&#xff0c;如下圖所示&#xff1a; Money Never Sleeps是IntelliJ IDEA平臺插件. 支持查看股票實時行情…

three.js 叉乘判斷物體在人前左,前右,后左、后右

效果&#xff1a; 代碼&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs"></div><div style"padding: 10px;text-align: left;">叉乘判斷物體…

sshd啟動太慢,導致首次登錄困難的問題(未解決)

開始以為是無法開機啟動。長時間后&#xff08;3-4分鐘&#xff09;&#xff0c;又可以登錄了。 解決辦法1&#xff08;無效&#xff09; 編輯sshd_config UseDNS no GSSAPIAuthentication no IgnoreRhosts yes UsePAM no 解決辦法2&#xff08;無效&#xff09; 在/etc/h…

加密與安全_探索對稱加密算法

文章目錄 概述常用的對稱加密算法AESECB模式CBC模式 (推薦)ECB VS CBC 附&#xff1a;AES工具類總結 概述 對稱加密算法是一種加密技術&#xff0c;使用相同的密鑰來進行加密和解密數據。在這種算法中&#xff0c;發送方使用密鑰將明文&#xff08;未加密的數據&#xff09;轉…

14:00面試,14:07就出來了,問的問題過于變態了。。。

我從一家小公司轉投到另一家公司&#xff0c;期待著新的工作環境和機會。然而&#xff0c;新公司的加班文化讓我有些始料未及。雖然薪資相對較高&#xff0c;但長時間的工作和缺乏休息使我身心俱疲。 就在我逐漸適應這種高強度的工作節奏時&#xff0c;公司突然宣布了一則令人…

Android提供了多種方式來打開特定文件夾中的視頻

使用 MediaStore獲取指定文件夾的視頻&#xff0c;更優化方法&#xff1a; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.net.Uri; import android.os.Build; import android.os.Environme…

鴻蒙操作系統特點

鴻蒙&#xff08;HarmonyOS&#xff09;是華為公司開發的一種面向全場景的分布式操作系統。下面是對鴻蒙操作系統的詳細介紹&#xff1a; 1. 多設備支持&#xff1a;鴻蒙是一種面向多種設備的操作系統&#xff0c;支持手機、平板電腦、智能手表、智能屏、車載設備和物聯網設備…

kafka學習筆記三

目錄 第二篇 外部系統集成 第三篇 生產調優手冊 第1章 kafka硬件配置選擇 第2章 生產者調優 2.1 生產者核心參數配置 2.2 生產者如何提高吞吐量 2.3 數據可靠性 2.4 數據去重 2.5 數據有序 2.6 數據亂序 第3章 Kafka Broker調優 3.1 Broker核心參數配置 3.2 其他 …

禪道:提bug、管理case 7.0

一、禪道的介紹 &#xff08;1&#xff09;定義禪道是一個項目管理工具&#xff0c;也是一個bug管理工具&#xff0c;還是一個用例管理工具。 &#xff08;2&#xff09;作用&#xff1a;為了解決眾多企業在管理中出現混亂&#xff0c;無序的現象&#xff0c;開發出來 &…

ppt中調整某條表格框線的格式

1、先設置好邊框線的屬性&#xff1a; 2、選擇要調整的邊框線所在的單元格&#xff08;第二列的右邊框加粗&#xff0c;體現分欄的效果&#xff09; 3、設計--邊框--中選擇要調整的邊框線位置&#xff08;假設要調整右框線&#xff09;

精讀服務器默認rsyslog的配置文件

rsyslog的配置文件 rsyslog.conf #### MODULES ####$ModLoad imuxsock # provides support for local system logging (e.g. via logger command) $ModLoad imjournal # provides access to the systemd journal #$ModLoad imklog # reads kernel messages (the same are read…

JavaScript解決生日倒計時的問題

創建一個文本框&#xff0c;在一個文本框中&#xff0c;讓用戶輸入他的出生月份&#xff0c;第二個文本框中輸入月份中的日期&#xff0c;編寫一個JavaScript程序&#xff0c;在第三個文本框中打印距離用戶生日還有多少天。 <div id"box"><label for"&…

libvirt命名空間xmlns:qemu的使用

示例xml <domain type{domain_type} xmlns:qemuhttp://libvirt.org/schemas/domain/qemu/1.0><qemu:commandline><qemu:commandline><qemu:arg value-newarg/><qemu:env nameQEMU_ENV valueVAL/></qemu:commandline></domain>"…

13、輸入捕獲實驗

目錄 一、通用定時器輸入捕獲概述 二、常用寄存器和庫函數配置 三、輸入捕獲實驗講解 一、通用定時器輸入捕獲概述 STM32輸入捕獲工作過程&#xff08;通道1為例&#xff09; 一句話總結工作過程&#xff1a; 通過檢測TIMx_CHx上的邊沿信號&#xff0c;在邊沿信號發生跳變…

28.HarmonyOS App(JAVA)多頁簽的實現(Tab)

HarmonyOS App(JAVA)多頁簽的實現&#xff08;Tab&#xff09; 頁面可左右滑動&#xff0c;點擊界面1,2,3切換到對應界面 PageSlider的創建和使用 在layout目錄下的xml文件中創建PageSlider。 <PageSlider ohos:id"$id:page_slider" ohos:height"300vp&…

2D割草/吸血鬼游戲 性能優化——GPU Spine動畫

視頻中萬人同屏方案(gpu動畫、渲染、索敵、避障等功能)&#xff0c;可某寶搜店鋪&#xff1a;【游戲開發資源商店】獲取整套方案源碼。 在過去的幾年里&#xff0c;割草、類吸血鬼玩法的游戲頻出爆款&#xff0c;其豐富的技能、滿屏特效、刷怪清屏的解壓暢快是此類游戲的核心&…

【MySQL】復合查詢(重點)-- 詳解

一、基本查詢練習回顧 1、查詢工資高于 500 或崗位為 MANAGER 的雇員&#xff0c;同時還要滿足他們的姓名首字母為大寫的 J 2、按照部門號升序而雇員的工資降序排序 3、使用年薪進行降序排序 4、顯示工資最高的員工的名字和工作崗位 5、顯示工資高于平均工資的員工信息 6、顯…