背景
一些業務背景下,業務要求單號須要有區分不一樣的前綴,那麼在分佈式的架構下如何自定義單號並且還能保證惟一呢?java
注:分佈式ID也能夠此方式redis
Redis實現方式
Redis的全部命令操做都是單線程的,自己提供像 incr 和 increby 這樣的自增原子命令,因此能保證生成的 ID 確定是惟一有序的。數據庫
優勢:不依賴於數據庫,靈活方便,且性能優於數據庫;數字ID自然排序,對分頁或者須要排序的結果頗有幫助。緩存
缺點:若是系統中沒有Redis,還須要引入新的組件,增長系統複雜度;須要編碼和配置的工做量比較大。架構
考慮到單節點的性能瓶頸,可使用 Redis 集羣來獲取更高的吞吐量。
使用 Redis 集羣也能夠方式單點故障的問題。app
代碼實例
建立常量類dom
/** * 單號生成常量 * * @author mq */ public class FormNoConstants { /** * 單號流水號緩存Key前綴 */ public static final String SERIAL_CACHE_PREFIX = "FORM_NO_CACHE_"; /** * 單號流水號yyMMdd前綴 */ public static final String SERIAL_YYMMDD_PREFIX = "yyMMdd"; /** * 單號流水號yyyyMMdd前綴 */ public static final String SERIAL_YYYYMMDD_PREFIX = "yyyyMMdd"; /** * 默認緩存天數 */ public static final int DEFAULT_CACHE_DAYS = 7; }
單號生成枚舉
注:爲了方便擴展,方便複用,使用枚舉方式,能夠自定義枚舉值來生成不一樣的單號分佈式
/** * 單號生成類型枚舉 * * @author mq * 注:隨機號位於流水號以後,流水號使用redis計數據,天天都是一個新的key,長度不足時則自動補0 * <p> * 生成規則 =固定前綴+當天日期串+流水號(redis自增,不足長度則補0)+隨機數 */ public enum FormNoTypeEnum { /** * 應付單單號: * 固定前綴:YF * 時間格式:yyyyMMdd * 流水號長度:7(當單日單據較多時可根據業務適當增長流水號長度) * 隨機數長度:3 * 總長度:20 */ YF_ORDER("YF", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 7, 3, 20), /** * 付款單單號: * 固定前綴:FK * 時間格式:yyyyMMdd * 流水號長度:7 * 隨機數長度:3 * 總長度:20 */ FK_ORDER("FK", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 7, 3, 20), /** * 測試單單號: * 固定前綴:"" * 時間格式:yyyyMMdd * 流水號長度:10 * 隨機數長度:0 * 總長度:20 */ TEST_ORDER("te", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 10, 0, 20), ; /** * 單號前綴 * 爲空時填"" */ private String prefix; /** * 時間格式表達式 * 例如:yyyyMMdd */ private String datePattern; /** * 流水號長度 */ private Integer serialLength; /** * 隨機數長度 */ 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 方法 }
單號生成工具類
/** * 單號生成工具類 * * @author mq */ public class FormNoSerialUtil { /** * 生成單號前綴 */ public static String getFormNoPrefix(FormNoTypeEnum formNoTypeEnum) { //格式化時間 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formNoTypeEnum.getDatePattern()); StringBuffer sb = new StringBuffer(); sb.append(formNoTypeEnum.getPrefix()); sb.append(formatter.format(LocalDateTime.now())); return sb.toString(); } /** * 構建流水號緩存Key * * @param serialPrefix 流水號前綴 * @return 流水號緩存Key */ public static String getCacheKey(String serialPrefix) { return FormNoConstants.SERIAL_CACHE_PREFIX.concat(serialPrefix); } /** * 補全流水號 * * @param serialPrefix 單號前綴 * @param incrementalSerial 當天自增流水號 * @author mengqiang * @date 2019/1/1 */ public static String completionSerial(String serialPrefix, Long incrementalSerial, FormNoTypeEnum formNoTypeEnum) { StringBuffer sb = new StringBuffer(serialPrefix); //須要補0的長度=流水號長度 -當日自增計數長度 int length = formNoTypeEnum.getSerialLength() - String.valueOf(incrementalSerial).length(); //補零 for (int i = 0; i < length; i++) { sb.append("0"); } //redis當日自增數 sb.append(incrementalSerial); return sb.toString(); } /** * 補全隨機數 * * @param serialWithPrefix 當前單號 * @param formNoTypeEnum 單號生成枚舉 * @author mengqiang * @date 2019/1/1 */ public static String completionRandom(String serialWithPrefix, FormNoTypeEnum formNoTypeEnum) { StringBuffer sb = new StringBuffer(serialWithPrefix); //隨機數長度 int length = formNoTypeEnum.getRandomLength(); if (length > 0) { Random random = new Random(); for (int i = 0; i < length; i++) { //十之內隨機數補全 sb.append(random.nextInt(10)); } } return sb.toString(); } }
單號生成接口
/** * 單號生成接口 * * @author mq */ public interface FormNoGenerateService { /** * 根據單據編號類型 生成單據編號 * * @param formNoTypeEnum 單據編號類型 * @author mengqiang * @date 2019/1/1 */ String generateFormNo(FormNoTypeEnum formNoTypeEnum); }
單號生成接口實現
/** * 單號生成接口實現 * * @author mengqiang * @version FormNoGenerateServiceImpl.java, v 1.0 2019-01-01 18:10 */ @Service public class FormNoGenerateServiceImpl implements FormNoGenerateService { /** * redis 服務 * demo 項目沒有加redis相關,如有須要請參考,redis的博客 */ @Autowired private RedisCache redisCache; /** * 根據單據編號類型 生成單據編號 * * @param formNoTypeEnum 單據編號類型 * @author mengqiang * @date 2019/1/1 */ @Override public String generateFormNo(FormNoTypeEnum formNoTypeEnum) { //得到單號前綴 //格式 固定前綴 +時間前綴 示例 :YF20190101 String formNoPrefix = FormNoSerialUtil.getFormNoPrefix(formNoTypeEnum); //得到緩存key String cacheKey = FormNoSerialUtil.getCacheKey(formNoPrefix); //得到當日自增數 Long incrementalSerial = redisCache.incr(cacheKey); //設置失效時間 7天 redisCache.expire(cacheKey, FormNoConstants.DEFAULT_CACHE_DAYS, TimeUnit.DAYS); //組合單號並補全流水號 String serialWithPrefix = FormNoSerialUtil .completionSerial(formNoPrefix, incrementalSerial, formNoTypeEnum); //補全隨機數 return FormNoSerialUtil.completionRandom(serialWithPrefix, formNoTypeEnum); } }
使用測試
redis截圖
總結
以上還不是最優雅的方式,最好是能作成jar包方式,作成通用的服務ide