基於Redis實現分佈式單號,分佈式ID(自定義規則生成)

背景

一些業務背景下,業務要求單號須要有區分不一樣的前綴,那麼在分佈式的架構下如何自定義單號並且還能保證惟一呢?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

相關文章
相關標籤/搜索