基于Redis實(shí)現(xiàn)的分布式唯一編號(hào)生成工具類(lèi)
首先,直接上代碼:
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import com.baomidou.mybatisplus.core.toolkit.DateUtils;
import com.xxxxx.blade.redis.BladeRedis; // 根據(jù)實(shí)際包路徑調(diào)整
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import com.xxxxx.common.exception.ServiceException; // 根據(jù)實(shí)際包路徑調(diào)整
/**
* 通用編號(hào)生成工具類(lèi)
* 功能:生成格式為【業(yè)務(wù)編碼+日期+3位自增序號(hào)】的唯一編號(hào)(例如:JJ20250826001)
* 特性:基于Redis實(shí)現(xiàn)分布式自增,通過(guò)Redisson分布式鎖保證并發(fā)安全,序號(hào)按日期重置
*/
@Slf4j // Lombok注解,自動(dòng)注入日志對(duì)象log
@Component // Spring組件注解,將該類(lèi)注冊(cè)為Bean,交由Spring容器管理
public class CommonNumber {
/**
* Redis操作客戶(hù)端(靜態(tài)變量)
* 用于執(zhí)行自增、過(guò)期時(shí)間設(shè)置等Redis命令
*/
private static BladeRedis BLADE_REDIS;
/**
* Redisson客戶(hù)端(靜態(tài)變量)
* 用于獲取分布式鎖,保證多實(shí)例環(huán)境下的并發(fā)安全
*/
private static RedissonClient REDISSSON_CLIENT;
/**
* Redisson客戶(hù)端(實(shí)例變量)
* 由Spring容器注入,通過(guò)@Resource注解按名稱(chēng)匹配
*/
@Resource
private RedissonClient RedissonClient;
/**
* Redis操作客戶(hù)端(實(shí)例變量)
* 由Spring容器注入,Blade框架封裝的Redis客戶(hù)端
*/
@Resource
private BladeRedis bladeRedis;
/**
* 初始化方法(PostConstruct注解)
* 作用:在Spring Bean初始化完成后,將實(shí)例變量賦值給靜態(tài)變量
* 原因:工具方法為static,無(wú)法直接注入Spring Bean,通過(guò)該方式間接獲取容器中的Bean實(shí)例
*/
@PostConstruct
public void init() {
BLADE_REDIS = bladeRedis;
REDISSSON_CLIENT = RedissonClient;
}
/**
* 生成通用唯一編號(hào)
* 格式:業(yè)務(wù)編碼(code) + 年月日(yyyyMMdd) + 3位自增序號(hào)(不足補(bǔ)0)
* 示例:code=JJ → JJ20250826001、JJ20250826002...
*
* @param code 業(yè)務(wù)編碼(區(qū)分不同業(yè)務(wù)場(chǎng)景的編號(hào)前綴)
* @return 格式化后的唯一編號(hào)
* @throws ServiceException 當(dāng)獲取分布式鎖失敗或Redis操作異常時(shí)拋出
*/
public static String getCommonNumber(String code) {
// 1. 定義分布式鎖key:按業(yè)務(wù)編碼區(qū)分,避免不同業(yè)務(wù)鎖競(jìng)爭(zhēng)
RLock lock = REDISSSON_CLIENT.getLock("common-number-lock:" + code);
try {
// 2. 嘗試獲取分布式鎖:最多等待3秒,持有鎖5秒(防止死鎖)
// tryLock返回false表示獲取鎖失?。úl(fā)過(guò)高)
if (!lock.tryLock(3, 5, TimeUnit.SECONDS)) {
throw new ServiceException("系統(tǒng)繁忙,請(qǐng)稍后重試");
}
// 3. 格式化當(dāng)前日期為yyyyMMdd格式(用于序號(hào)按日期重置)
String currentTime = DateUtils.format(new Date(), "yyyyMMdd");
// 4. 定義Redis自增key:業(yè)務(wù)編碼+日期,確保每天的序號(hào)從1開(kāi)始
String companyNumberKey = "common_number_key:" + code + currentTime;
// 5. Redis自增操作:原子性遞增,保證序號(hào)唯一(初始值為1,每次+1)
Long incr = BLADE_REDIS.incr(companyNumberKey);
// 6. 拼接最終編號(hào):業(yè)務(wù)編碼 + 日期 + 3位補(bǔ)0序號(hào)(例如:1→001,10→010,100→100)
String companyNumber = code + currentTime + String.format("%03d", incr);
// 7. 設(shè)置Rediskey過(guò)期時(shí)間:48小時(shí)(確保過(guò)期數(shù)據(jù)自動(dòng)清理,節(jié)省Redis空間)
BLADE_REDIS.expire(companyNumberKey, 60 * 60 * 48L);
// 8. 返回生成的編號(hào)
return companyNumber;
} catch (InterruptedException e) {
// 捕獲線程中斷異常(獲取鎖過(guò)程中線程被中斷)
log.error("獲取編號(hào)時(shí)線程被中斷,code:{}", code, e);
throw new ServiceException("獲取編號(hào)失敗");
} catch (ServiceException e) {
// 拋出獲取鎖失敗的自定義異常(無(wú)需額外日志,已在拋出時(shí)明確)
throw e;
} catch (Exception e) {
// 捕獲其他異常(Redis操作失敗等)
log.error("獲取編號(hào)失敗,code:{}", code, e);
throw new ServiceException("獲取編號(hào)失敗");
} finally {
// 9. 釋放分布式鎖:必須在finally中執(zhí)行,確保鎖一定會(huì)釋放
// 先判斷當(dāng)前線程是否持有鎖,避免釋放其他線程的鎖
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
```java
這是一個(gè) 基于Redis實(shí)現(xiàn)的分布式唯一編號(hào)生成工具類(lèi),核心功能是生成格式為 業(yè)務(wù)編碼+日期+3位自增序號(hào)(如 JJ20250826001)的全局唯一編號(hào),適用于分布式系統(tǒng)中需要有序、不重復(fù)編號(hào)的場(chǎng)景(如訂單號(hào)、單據(jù)號(hào)等)。以下是詳細(xì)解析:
一、類(lèi)結(jié)構(gòu)與依賴(lài)說(shuō)明
1. 核心注解
@Slf4j:Lombok注解,自動(dòng)生成日志對(duì)象log,用于打印異常日志。@Component:Spring注解,將該類(lèi)注冊(cè)為Spring容器中的Bean,支持依賴(lài)注入。
2. 依賴(lài)組件
BladeRedis:bladex框架封裝的Redis操作工具(類(lèi)似Spring Data Redis),用于執(zhí)行incr(自增)、expire(設(shè)置過(guò)期時(shí)間)等Redis命令。RedissonClient:Redisson框架的客戶(hù)端,用于操作Redis分布式鎖(解決分布式環(huán)境下的并發(fā)沖突)。@Resource:Spring依賴(lài)注入注解,用于注入RedissonClient和BladeRedis實(shí)例。@PostConstruct:Spring生命周期注解,在Bean初始化完成后執(zhí)行init方法,將注入的實(shí)例賦值給靜態(tài)變量(因?yàn)?code>getCommonNumber是靜態(tài)方法,無(wú)法直接使用非靜態(tài)成員變量)。
二、核心邏輯:編號(hào)生成流程
1. 方法定義
public static String getCommonNumber(String code)
- 入?yún)?
code:業(yè)務(wù)編碼(如JJ代表某種單據(jù)類(lèi)型),用于區(qū)分不同業(yè)務(wù)場(chǎng)景的編號(hào)。 - 出參:格式為
code + 日期(yyyyMMdd) + 3位自增序號(hào)的唯一編號(hào)。
2. 關(guān)鍵步驟(帶并發(fā)安全保障)
(1)分布式鎖獲取
RLock lock = REDISSSON_CLIENT.getLock("common-number-lock:"+code);
if (!lock.tryLock(3, 5, TimeUnit.SECONDS)) {
throw new ServiceException("系統(tǒng)繁忙,請(qǐng)稍后重試");
}
- 鎖key設(shè)計(jì):
common-number-lock:+ 業(yè)務(wù)編碼code,確保不同業(yè)務(wù)的鎖相互隔離,避免鎖競(jìng)爭(zhēng)加劇。 - 鎖參數(shù):
- 最多等待3秒(
waitTime=3):線程獲取鎖時(shí),最多等待3秒,超過(guò)則認(rèn)為獲取失敗。 - 鎖持有時(shí)間5秒(
leaseTime=5):即使線程未主動(dòng)釋放鎖,5秒后Redis也會(huì)自動(dòng)釋放,避免死鎖。
- 最多等待3秒(
- 作用:解決分布式環(huán)境下的并發(fā)沖突,確保同一業(yè)務(wù)編碼的自增序號(hào)不會(huì)重復(fù)。
(2)Redis自增生成序號(hào)
// 1. 生成當(dāng)天日期(格式:yyyyMMdd)
String currentTime = DateUtil.format(DateUtil.date(), "yyyyMMdd");
// 2. 構(gòu)建Redis自增key(業(yè)務(wù)編碼+日期,確保每天的序號(hào)獨(dú)立重置)
String companyNumberKey = "common_number_key:" + code + currentTime;
// 3. Redis自增(原子操作,確保序號(hào)唯一)
Long incr = BLADE_REDIS.incr(companyNumberKey);
// 4. 序號(hào)補(bǔ)零(3位,不足3位前面補(bǔ)0,如1→001,10→010)
String seq = String.format("%03d", incr);
- Redis key設(shè)計(jì):
common_number_key:+ 業(yè)務(wù)編碼code+ 日期currentTime,確保:- 不同業(yè)務(wù)編碼的序號(hào)相互獨(dú)立;
- 同一業(yè)務(wù)編碼每天的序號(hào)從1開(kāi)始重置(因?yàn)槿掌谧兓髃ey會(huì)變化)。
- 原子性保障:Redis的
incr命令是原子操作,即使多個(gè)線程同時(shí)調(diào)用,也能保證自增結(jié)果唯一,無(wú)需額外加鎖(但此處仍加分布式鎖是為了避免極端情況下的并發(fā)問(wèn)題,或配合序號(hào)補(bǔ)零等邏輯)。
(3)設(shè)置Redis key過(guò)期時(shí)間
BLADE_REDIS.expire(companyNumberKey, 60 * 60 * 48L); // 48小時(shí)過(guò)期
- 自增key的過(guò)期時(shí)間設(shè)置為48小時(shí),原因:
- 確保當(dāng)天的序號(hào)能正常自增(當(dāng)天內(nèi)key未過(guò)期);
- 避免Redis中存儲(chǔ)大量過(guò)期的自增key,節(jié)省存儲(chǔ)空間;
- 48小時(shí)覆蓋跨天場(chǎng)景(如凌晨23:59生成的key,到次日仍可正常使用,避免提前過(guò)期)。
(4)組裝最終編號(hào)
String companyNumber = code + currentTime + seq;
- 例如:業(yè)務(wù)編碼
JJ+ 日期20250826+ 序號(hào)001→ 最終編號(hào)JJ20250826001。
3. 異常處理與鎖釋放
try {
// 編號(hào)生成邏輯...
} catch (Exception e) {
log.error("獲取編號(hào)失敗", e); // 打印異常堆棧,便于排查
throw new ServiceException("獲取編號(hào)失敗"); // 拋出自定義業(yè)務(wù)異常,上層處理
} finally {
// 確保鎖一定釋放(避免死鎖)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
- finally塊釋放鎖:無(wú)論生成編號(hào)成功與否,都要釋放分布式鎖,避免鎖資源泄露。
- 鎖持有判斷:
lock.isHeldByCurrentThread()確保當(dāng)前線程確實(shí)持有鎖時(shí)才釋放,避免釋放其他線程的鎖(如線程等待鎖超時(shí)后,未持有鎖卻執(zhí)行解鎖操作)。
三、核心設(shè)計(jì)亮點(diǎn)
1. 分布式并發(fā)安全
- 雙重保障:Redis
incr原子操作 + Redisson分布式鎖,確保序號(hào)唯一,無(wú)并發(fā)沖突。 - 鎖自動(dòng)過(guò)期:避免因線程異常導(dǎo)致的死鎖問(wèn)題。
2. 序號(hào)合理性
- 按天重置:每天的序號(hào)從1開(kāi)始,編號(hào)可讀性強(qiáng)(通過(guò)編號(hào)可直接看出日期)。
- 固定長(zhǎng)度:3位序號(hào)補(bǔ)零,確保編號(hào)長(zhǎng)度一致(便于存儲(chǔ)和展示)。
3. 資源優(yōu)化
- Redis key過(guò)期:48小時(shí)自動(dòng)清理,避免Redis存儲(chǔ)冗余數(shù)據(jù)。
- 鎖粒度細(xì):按業(yè)務(wù)編碼分鎖,減少鎖競(jìng)爭(zhēng),提高并發(fā)效率。
四、潛在問(wèn)題與優(yōu)化建議
1. 潛在問(wèn)題
- 鎖競(jìng)爭(zhēng)風(fēng)險(xiǎn):如果同一業(yè)務(wù)編碼的并發(fā)請(qǐng)求極高,分布式鎖可能成為性能瓶頸(線程需等待3秒)。
- Redis依賴(lài)風(fēng)險(xiǎn):Redis服務(wù)不可用時(shí),編號(hào)生成會(huì)失?。o(wú)降級(jí)方案)。
- 序號(hào)溢出:3位序號(hào)最大支持999,如果單日同一業(yè)務(wù)編碼的編號(hào)超過(guò)999,會(huì)生成
code+日期+1000(如JJ202508261000),破壞3位固定長(zhǎng)度格式。
2. 優(yōu)化建議
- 優(yōu)化鎖策略:
- 去掉分布式鎖(Redis
incr已保證原子性),僅在需要序號(hào)補(bǔ)零、特殊邏輯時(shí)加鎖,提高并發(fā)效率。 - 調(diào)整鎖等待時(shí)間和持有時(shí)間(根據(jù)業(yè)務(wù)并發(fā)量動(dòng)態(tài)調(diào)整)。
- 去掉分布式鎖(Redis
- 降級(jí)方案:
- 當(dāng)Redis不可用時(shí),可臨時(shí)使用本地緩存(如
AtomicLong)+ 機(jī)器標(biāo)識(shí)生成編號(hào),避免服務(wù)不可用。
- 當(dāng)Redis不可用時(shí),可臨時(shí)使用本地緩存(如
- 序號(hào)擴(kuò)容:
- 將3位序號(hào)改為4位(
%04d),支持單日9999個(gè)編號(hào),滿(mǎn)足更高并發(fā)場(chǎng)景。
- 將3位序號(hào)改為4位(
- 防止重復(fù)生成:
- 可將生成的編號(hào)存入Redis或數(shù)據(jù)庫(kù),做最終去重校驗(yàn)(極端情況下Redis自增失敗時(shí)兜底)。
- 靜態(tài)成員變量?jī)?yōu)化:
- 目前通過(guò)
@PostConstruct給靜態(tài)變量賦值,依賴(lài)Spring初始化順序,可改為使用@Autowired+ 非靜態(tài)方法(去掉static),更符合Spring依賴(lài)注入規(guī)范(需將工具類(lèi)注入使用,而非直接調(diào)用靜態(tài)方法)。
- 目前通過(guò)
五、使用場(chǎng)景
適用于分布式系統(tǒng)中需要生成 有序、唯一、可讀 編號(hào)的場(chǎng)景,例如:
- 訂單編號(hào)、支付單號(hào)、物流單號(hào);
- 單據(jù)編號(hào)(如入庫(kù)單、出庫(kù)單);
- 業(yè)務(wù)流水號(hào)等。
總結(jié)
該工具類(lèi)基于Redis的原子自增和Redisson的分布式鎖,實(shí)現(xiàn)了分布式環(huán)境下的唯一編號(hào)生成,設(shè)計(jì)簡(jiǎn)潔、實(shí)用性強(qiáng),同時(shí)也存在一些可優(yōu)化的細(xì)節(jié)(如鎖策略、降級(jí)方案),可根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景調(diào)整。
以上就是基于Redis實(shí)現(xiàn)的分布式唯一編號(hào)生成工具類(lèi)的詳細(xì)內(nèi)容,更多關(guān)于Redis分布式唯一編號(hào)生成工具類(lèi)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
redis requires ruby version2.2.2的解決方案
本文主要介紹了redis requires ruby version2.2.2的解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
Redis實(shí)現(xiàn)查看服務(wù)狀態(tài)、關(guān)閉和啟動(dòng)方式
本文介紹了如何在Linux系統(tǒng)中查看、關(guān)閉和啟動(dòng)Redis服務(wù),包括使用命令行工具和配置文件進(jìn)行相關(guān)操作2025-11-11
Redis模擬延時(shí)隊(duì)列實(shí)現(xiàn)日程提醒的方法
文章介紹了如何使用Redis實(shí)現(xiàn)一個(gè)簡(jiǎn)單的延時(shí)任務(wù)隊(duì)列,通過(guò)Redis的有序集合特性來(lái)存儲(chǔ)和管理延時(shí)任務(wù),通過(guò)定期檢查集合中小于等于當(dāng)前時(shí)間的任務(wù)并執(zhí)行,可以實(shí)現(xiàn)延時(shí)任務(wù)的管理,感興趣的朋友跟隨小編一起看看吧2024-11-11
使用 Redis 流實(shí)現(xiàn)消息隊(duì)列的代碼
這篇文章主要介紹了使用 Redis 流實(shí)現(xiàn)消息隊(duì)列,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11
Redis與MySQL數(shù)據(jù)一致性問(wèn)題的策略模式及解決方案
開(kāi)發(fā)中,一般會(huì)使用Redis緩存一些常用的熱點(diǎn)數(shù)據(jù)用來(lái)減少數(shù)據(jù)庫(kù)IO,提高系統(tǒng)的吞吐量,本文將給大家介紹了Redis與MySQL數(shù)據(jù)一致性問(wèn)題的策略模式及解決方案,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07
Redis數(shù)據(jù)結(jié)構(gòu)之鏈表與字典的使用
這篇文章主要介紹了Redis數(shù)據(jù)結(jié)構(gòu)之鏈表與字典的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05

