基于Redis實(shí)現(xiàn)分布式單號(hào)及分布式ID(自定義規(guī)則生成)
背景
一些業(yè)務(wù)背景下,業(yè)務(wù)要求單號(hào)需要有區(qū)分不同的前綴,那么在分布式的架構(gòu)下如何自定義單號(hào)而且還能保證唯一呢?
注:分布式ID也可以此方式
Redis實(shí)現(xiàn)方式
Redis的所有命令操作都是單線程的,本身提供像 incr 和 increby 這樣的自增原子命令,所以能保證生成的 ID 肯定是唯一有序的。
優(yōu)點(diǎn):不依賴于數(shù)據(jù)庫,靈活方便,且性能優(yōu)于數(shù)據(jù)庫;數(shù)字ID天然排序,對分頁或者需要排序的結(jié)果很有幫助。
缺點(diǎn):如果系統(tǒng)中沒有Redis,還需要引入新的組件,增加系統(tǒng)復(fù)雜度;需要編碼和配置的工作量比較大。
考慮到單節(jié)點(diǎn)的性能瓶頸,可以使用 Redis 集群來獲取更高的吞吐量。
使用 Redis 集群也可以方式單點(diǎn)故障的問題。
代碼實(shí)例
創(chuàng)建常量類
/**
* 單號(hào)生成常量
*
* @author mq
*/
public class FormNoConstants {
/**
* 單號(hào)流水號(hào)緩存Key前綴
*/
public static final String SERIAL_CACHE_PREFIX = "FORM_NO_CACHE_";
/**
* 單號(hào)流水號(hào)yyMMdd前綴
*/
public static final String SERIAL_YYMMDD_PREFIX = "yyMMdd";
/**
* 單號(hào)流水號(hào)yyyyMMdd前綴
*/
public static final String SERIAL_YYYYMMDD_PREFIX = "yyyyMMdd";
/**
* 默認(rèn)緩存天數(shù)
*/
public static final int DEFAULT_CACHE_DAYS = 7;
}
單號(hào)生成枚舉
注:為了方便擴(kuò)展,方便復(fù)用,使用枚舉方式,可以自定義枚舉值來生成不同的單號(hào)
/**
* 單號(hào)生成類型枚舉
*
* @author mq
* 注:隨機(jī)號(hào)位于流水號(hào)之后,流水號(hào)使用redis計(jì)數(shù)據(jù),每天都是一個(gè)新的key,長度不足時(shí)則自動(dòng)補(bǔ)0
* <p>
* 生成規(guī)則 =固定前綴+當(dāng)天日期串+流水號(hào)(redis自增,不足長度則補(bǔ)0)+隨機(jī)數(shù)
*/
public enum FormNoTypeEnum {
/**
* 應(yīng)付單單號(hào):
* 固定前綴:YF
* 時(shí)間格式:yyyyMMdd
* 流水號(hào)長度:7(當(dāng)單日單據(jù)較多時(shí)可根據(jù)業(yè)務(wù)適當(dāng)增加流水號(hào)長度)
* 隨機(jī)數(shù)長度:3
* 總長度:20
*/
YF_ORDER("YF", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 7, 3, 20),
/**
* 付款單單號(hào):
* 固定前綴:FK
* 時(shí)間格式:yyyyMMdd
* 流水號(hào)長度:7
* 隨機(jī)數(shù)長度:3
* 總長度:20
*/
FK_ORDER("FK", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 7, 3, 20),
/**
* 測試單單號(hào):
* 固定前綴:""
* 時(shí)間格式:yyyyMMdd
* 流水號(hào)長度:10
* 隨機(jī)數(shù)長度:0
* 總長度:20
*/
TEST_ORDER("te", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 10, 0, 20),
;
/**
* 單號(hào)前綴
* 為空時(shí)填""
*/
private String prefix;
/**
* 時(shí)間格式表達(dá)式
* 例如:yyyyMMdd
*/
private String datePattern;
/**
* 流水號(hào)長度
*/
private Integer serialLength;
/**
* 隨機(jī)數(shù)長度
*/
private Integer randomLength;
/**
* 總長度
*/
private Integer totalLength;
FormNoTypeEnum(String prefix, String datePattern, Integer serialLength, Integer randomLength, Integer totalLength) {
this.prefix = prefix;
this.datePattern = datePattern;
this.serialLength = serialLength;
this.randomLength = randomLength;
this.totalLength = totalLength;
}
//省略 get 方法
}
單號(hào)生成工具類
/**
* 單號(hào)生成工具類
*
* @author mq
*/
public class FormNoSerialUtil {
/**
* 生成單號(hào)前綴
*/
public static String getFormNoPrefix(FormNoTypeEnum formNoTypeEnum) {
//格式化時(shí)間
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formNoTypeEnum.getDatePattern());
StringBuffer sb = new StringBuffer();
sb.append(formNoTypeEnum.getPrefix());
sb.append(formatter.format(LocalDateTime.now()));
return sb.toString();
}
/**
* 構(gòu)建流水號(hào)緩存Key
*
* @param serialPrefix 流水號(hào)前綴
* @return 流水號(hào)緩存Key
*/
public static String getCacheKey(String serialPrefix) {
return FormNoConstants.SERIAL_CACHE_PREFIX.concat(serialPrefix);
}
/**
* 補(bǔ)全流水號(hào)
*
* @param serialPrefix 單號(hào)前綴
* @param incrementalSerial 當(dāng)天自增流水號(hào)
* @author mengqiang
* @date 2019/1/1
*/
public static String completionSerial(String serialPrefix, Long incrementalSerial,
FormNoTypeEnum formNoTypeEnum) {
StringBuffer sb = new StringBuffer(serialPrefix);
//需要補(bǔ)0的長度=流水號(hào)長度 -當(dāng)日自增計(jì)數(shù)長度
int length = formNoTypeEnum.getSerialLength() - String.valueOf(incrementalSerial).length();
//補(bǔ)零
for (int i = 0; i < length; i++) {
sb.append("0");
}
//redis當(dāng)日自增數(shù)
sb.append(incrementalSerial);
return sb.toString();
}
/**
* 補(bǔ)全隨機(jī)數(shù)
*
* @param serialWithPrefix 當(dāng)前單號(hào)
* @param formNoTypeEnum 單號(hào)生成枚舉
* @author mengqiang
* @date 2019/1/1
*/
public static String completionRandom(String serialWithPrefix, FormNoTypeEnum formNoTypeEnum) {
StringBuffer sb = new StringBuffer(serialWithPrefix);
//隨機(jī)數(shù)長度
int length = formNoTypeEnum.getRandomLength();
if (length > 0) {
Random random = new Random();
for (int i = 0; i < length; i++) {
//十以內(nèi)隨機(jī)數(shù)補(bǔ)全
sb.append(random.nextInt(10));
}
}
return sb.toString();
}
}
單號(hào)生成接口
/**
* 單號(hào)生成接口
*
* @author mq
*/
public interface FormNoGenerateService {
/**
* 根據(jù)單據(jù)編號(hào)類型 生成單據(jù)編號(hào)
*
* @param formNoTypeEnum 單據(jù)編號(hào)類型
* @author mengqiang
* @date 2019/1/1
*/
String generateFormNo(FormNoTypeEnum formNoTypeEnum);
}
單號(hào)生成接口實(shí)現(xiàn)
/**
* 單號(hào)生成接口實(shí)現(xiàn)
*
* @author mengqiang
* @version FormNoGenerateServiceImpl.java, v 1.0 2019-01-01 18:10
*/
@Service
public class FormNoGenerateServiceImpl implements FormNoGenerateService {
/**
* redis 服務(wù)
* demo 項(xiàng)目沒有加redis相關(guān),若有需要請參考,redis的博客
*/
@Autowired
private RedisCache redisCache;
/**
* 根據(jù)單據(jù)編號(hào)類型 生成單據(jù)編號(hào)
*
* @param formNoTypeEnum 單據(jù)編號(hào)類型
* @author mengqiang
* @date 2019/1/1
*/
@Override
public String generateFormNo(FormNoTypeEnum formNoTypeEnum) {
//獲得單號(hào)前綴
//格式 固定前綴 +時(shí)間前綴 示例 :YF20190101
String formNoPrefix = FormNoSerialUtil.getFormNoPrefix(formNoTypeEnum);
//獲得緩存key
String cacheKey = FormNoSerialUtil.getCacheKey(formNoPrefix);
//獲得當(dāng)日自增數(shù)
Long incrementalSerial = redisCache.incr(cacheKey);
//設(shè)置失效時(shí)間 7天
redisCache.expire(cacheKey, FormNoConstants.DEFAULT_CACHE_DAYS, TimeUnit.DAYS);
//組合單號(hào)并補(bǔ)全流水號(hào)
String serialWithPrefix = FormNoSerialUtil
.completionSerial(formNoPrefix, incrementalSerial, formNoTypeEnum);
//補(bǔ)全隨機(jī)數(shù)
return FormNoSerialUtil.completionRandom(serialWithPrefix, formNoTypeEnum);
}
}
使用測試

redis截圖

總結(jié)
以上還不是最優(yōu)雅的方式,最好是能做成jar包方式,做成通用的服務(wù)
到此這篇關(guān)于基于Redis實(shí)現(xiàn)分布式單號(hào)及分布式ID(自定義規(guī)則生成)的文章就介紹到這了,更多相關(guān)基于Redis實(shí)現(xiàn)分布式單號(hào)及分布式ID(自定義規(guī)則生成)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis過期Key刪除策略和內(nèi)存淘汰策略的實(shí)現(xiàn)
當(dāng)內(nèi)存使用達(dá)到上限,就無法存儲(chǔ)更多數(shù)據(jù)了,為了解決這個(gè)問題,Redis內(nèi)部會(huì)有兩套內(nèi)存回收的策略,過期Key刪除策略和內(nèi)存淘汰策略,本文就來詳細(xì)的介紹一下這兩種方法,感興趣的可以了解一下2024-02-02
基于Redis實(shí)現(xiàn)雙加密Token的示例代碼
在現(xiàn)代分布式系統(tǒng)中,Token管理是身份驗(yàn)證和授權(quán)的核心部分,本文將深入分析一個(gè)基于Redis的Token管理實(shí)現(xiàn),探討其設(shè)計(jì)思路、關(guān)鍵代碼邏輯以及實(shí)現(xiàn)細(xì)節(jié),通過對源碼的逐層剖析,幫助讀者更好地理解Token管理的實(shí)現(xiàn)原理,需要的朋友可以參考下2025-01-01
深度剖析Redis字符串操作指南從入門到實(shí)戰(zhàn)應(yīng)用
Redis字符串類型二進(jìn)制安全,支持文本、數(shù)字、二進(jìn)制等數(shù)據(jù),涵蓋基礎(chǔ)操作、數(shù)字計(jì)算、過期管理及分布式鎖等應(yīng)用,結(jié)合優(yōu)化策略提升系統(tǒng)性能,本文給大家介紹Redis字符串操作指南,感興趣的朋友一起看看吧2025-07-07
使用redis實(shí)現(xiàn)延遲通知功能(Redis過期鍵通知)
這篇文章主要介紹了使用redis實(shí)現(xiàn)延遲通知功能(Redis過期鍵通知)的相關(guān)知識(shí),本文通過實(shí)例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-09-09
redis集群搭建_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了redis集群搭建,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
淺析PHP分布式中Redis實(shí)現(xiàn)Session的方法
這篇文章主要介紹了PHP分布式中Redis實(shí)現(xiàn)Session的方法,文中詳細(xì)介紹了兩種方法的使用方法,并給出了測試的示例代碼,有需要的朋友可以參考借鑒,下面來一起看看吧,2016-12-12
使用SpringBoot?+?Redis?實(shí)現(xiàn)接口限流的方式
這篇文章主要介紹了SpringBoot?+?Redis?實(shí)現(xiàn)接口限流,Redis?除了做緩存,還能干很多很多事情:分布式鎖、限流、處理請求接口冪等,文中給大家提到了限流注解的創(chuàng)建方式,需要的朋友可以參考下2022-05-05
Redis實(shí)現(xiàn)排行榜及相同積分按時(shí)間排序功能的實(shí)現(xiàn)
這篇文章主要介紹了Redis實(shí)現(xiàn)排行榜及相同積分按時(shí)間排序,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08

