基于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):不依賴(lài)于數(shù)據(jù)庫(kù),靈活方便,且性能優(yōu)于數(shù)據(jù)庫(kù);數(shù)字ID天然排序,對(duì)分頁(yè)或者需要排序的結(jié)果很有幫助。
缺點(diǎn):如果系統(tǒng)中沒(méi)有Redis,還需要引入新的組件,增加系統(tǒng)復(fù)雜度;需要編碼和配置的工作量比較大。
考慮到單節(jié)點(diǎn)的性能瓶頸,可以使用 Redis 集群來(lái)獲取更高的吞吐量。
使用 Redis 集群也可以方式單點(diǎn)故障的問(wèn)題。
代碼實(shí)例
創(chuàng)建常量類(lèi)
/** * 單號(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ù)用,使用枚舉方式,可以自定義枚舉值來(lái)生成不同的單號(hào)
/** * 單號(hào)生成類(lèi)型枚舉 * * @author mq * 注:隨機(jī)號(hào)位于流水號(hào)之后,流水號(hào)使用redis計(jì)數(shù)據(jù),每天都是一個(gè)新的key,長(zhǎng)度不足時(shí)則自動(dòng)補(bǔ)0 * <p> * 生成規(guī)則 =固定前綴+當(dāng)天日期串+流水號(hào)(redis自增,不足長(zhǎng)度則補(bǔ)0)+隨機(jī)數(shù) */ public enum FormNoTypeEnum { /** * 應(yīng)付單單號(hào): * 固定前綴:YF * 時(shí)間格式:yyyyMMdd * 流水號(hào)長(zhǎng)度:7(當(dāng)單日單據(jù)較多時(shí)可根據(jù)業(yè)務(wù)適當(dāng)增加流水號(hào)長(zhǎng)度) * 隨機(jī)數(shù)長(zhǎng)度:3 * 總長(zhǎng)度:20 */ YF_ORDER("YF", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 7, 3, 20), /** * 付款單單號(hào): * 固定前綴:FK * 時(shí)間格式:yyyyMMdd * 流水號(hào)長(zhǎng)度:7 * 隨機(jī)數(shù)長(zhǎng)度:3 * 總長(zhǎng)度:20 */ FK_ORDER("FK", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 7, 3, 20), /** * 測(cè)試單單號(hào): * 固定前綴:"" * 時(shí)間格式:yyyyMMdd * 流水號(hào)長(zhǎng)度:10 * 隨機(jī)數(shù)長(zhǎng)度:0 * 總長(zhǎng)度: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)長(zhǎng)度 */ private Integer serialLength; /** * 隨機(jī)數(shù)長(zhǎng)度 */ private Integer randomLength; /** * 總長(zhǎng)度 */ 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)生成工具類(lèi)
/** * 單號(hào)生成工具類(lèi) * * @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的長(zhǎng)度=流水號(hào)長(zhǎng)度 -當(dāng)日自增計(jì)數(shù)長(zhǎng)度 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ù)長(zhǎng)度 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)類(lèi)型 生成單據(jù)編號(hào) * * @param formNoTypeEnum 單據(jù)編號(hào)類(lèi)型 * @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)目沒(méi)有加redis相關(guān),若有需要請(qǐng)參考,redis的博客 */ @Autowired private RedisCache redisCache; /** * 根據(jù)單據(jù)編號(hào)類(lèi)型 生成單據(jù)編號(hào) * * @param formNoTypeEnum 單據(jù)編號(hào)類(lèi)型 * @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); } }
使用測(cè)試
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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis客戶端連接錯(cuò)誤 NOAUTH Authentication required
本文主要介紹了redis客戶端連接錯(cuò)誤 NOAUTH Authentication required,詳細(xì)的介紹了解決方法,感興趣的可以了解一下2021-07-07Redis 哨兵機(jī)制及配置實(shí)現(xiàn)
本文主要介紹了Redis 哨兵機(jī)制及配置實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Redis高級(jí)數(shù)據(jù)類(lèi)型Hyperloglog、Bitmap的使用
很多小伙伴在面試中都會(huì)被問(wèn)道 Redis的常用數(shù)據(jù)結(jié)構(gòu)有哪些?可能很大一部分回答都是 string、hash、list、set、zset,但其實(shí)還有Hyperloglog和Bitmap,本文就來(lái)介紹一下2021-05-05Redis實(shí)現(xiàn)分布式Session管理的機(jī)制詳解
這篇文章主要介紹了Redis實(shí)現(xiàn)分布式Session管理的機(jī)制詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01詳解redis緩存與數(shù)據(jù)庫(kù)一致性問(wèn)題解決
這篇文章主要介紹了詳解redis緩存與數(shù)據(jù)庫(kù)一致性問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03詳解Redis單線程架構(gòu)的優(yōu)勢(shì)與不足
很多人都遇到過(guò)這么一道面試題:Redis是單線程還是多線程?這個(gè)問(wèn)題既簡(jiǎn)單又復(fù)雜,說(shuō)他簡(jiǎn)單是因?yàn)榇蠖鄶?shù)人都知道Redis是單線程,說(shuō)復(fù)雜是因?yàn)檫@個(gè)答案其實(shí)并不準(zhǔn)確,本文就給大家講講Redis單線程架構(gòu)的優(yōu)勢(shì)與不足,需要的朋友可以參考下2024-02-02