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

