1. ホーム
  2. データベース
  3. レディス

Redisによる分散シングルナンバーと分散ID(カスタムルール生成)

2022-01-15 21:36:51

背景

ビジネスの文脈によっては、異なる注文番号を区別するために接頭辞が必要になることがあります。分散アーキテクチャで注文番号をカスタマイズし、かつ一意性を確保するにはどうすればよいでしょうか。

参考:分散IDはこんな使い方もできます

Redis の実装

Redisのコマンド操作はすべてシングルスレッドで、incrやincrebyといった自己増強型のアトミックコマンドを提供しているので、生成されるIDは確実に一意的に並べられるようになっています。

長所 データベースに依存しない、柔軟、便利、データベースを凌駕する。数値IDは自然にソートされるので、ページングやソートが必要な結果には有効である。

短所:Redisを導入していない場合、新たなコンポーネントも導入する必要があり、システムが複雑になる、コーディングや設定作業が増える。

シングルノードの性能ボトルネックを考えると、Redisクラスターを利用することでより高いスループットを得ることができます。
また、Redisクラスタを使用することで、単一障害点の問題を解決することができます。

コード例

定数クラスの作成

/**
 * Single number generation constants
 *
 * @author mq
 */
public class FormNoConstants {
    /**
     * Single number cache Key prefix
     */
    public static final String SERIAL_CACHE_PREFIX = "FORM_NO_CACHE_";

    /**
     * Single number stream number yyMMdd prefix
     */
    public static final String SERIAL_YYMMDD_PREFIX = "yyMMdd";

    /**
     * single running number yyyyMMdd prefix
     */
    public static final String SERIAL_YYYYMMDD_PREFIX = "yyyyMMdd";
    
    /**
     * default cache days
     */
    public static final int DEFAULT_CACHE_DAYS = 7;
}


シングルナンバー生成列挙

注:拡張や再利用を容易にするために、列挙型のメソッドを使用し、列挙値をカスタマイズして異なる単一番号を生成することができます。

/**
 * Single number generation type enumeration
 *
 * @author mq
 * Note: The random number is located after the running number, running number using redis count data, each day is a new key, the length is insufficient to automatically fill 0
 * <p>
 * generation rules = fixed prefix + day date string + running number (redis self-incrementing, insufficient length to fill 0) + random number
 */
public enum FormNoTypeEnum {

    /**
     * Payable order number.
     * Fixed prefix: YF
     * Time format: yyyyMMdd
     * Flow number length: 7 (when a single day documents more can be increased according to business appropriate flow number length)
     * Random number length: 3
     * Total length: 20
     */
    YF_ORDER("YF", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 7, 3, 20),

    /**
     * Payment order number.
     * Fixed prefix: FK
     * Time format: yyyyMMdd
     * Flow number length: 7
     * Random number length: 3
     * Total length: 20
     */
    FK_ORDER("FK", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 7, 3, 20),

    /**
     * Test order number.
     * Fixed prefix: ""
     * Time format: yyyyMMdd
     * Flow number length: 10
     * Random number length: 0
     * Total length: 20
     */
    TEST_ORDER("te", FormNoConstants.SERIAL_YYYYMMDD_PREFIX, 10, 0, 20),
    ;

    /**
     * Single number prefix
     * Filled with "" when empty;
     */
    private String prefix;

    /**
     * Time format expressions
     * Example: yyyyMMdd
     */
    private String datePattern;

    /**
     * The length of the stream number
     */
    private Integer serialLength;
    /**
     * The length of the random number
     */
    private Integer randomLength;

    /**
     * Total length
     */
    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;
    }
    //Omit the get method
}

シングルナンバー生成ツールクラス

/**
 * Single number generation tool class
 *
 * @author mq
 */
public class FormNoSerialUtil {

    /**
     * Generate a single number prefix
     */
    public static String getFormNoPrefix(FormNoTypeEnum formNoTypeEnum) {
        // format the time
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formNoTypeEnum.getDatePattern());
        StringBuffer sb = new StringBuffer();
        sb.append(formNoTypeEnum.getPrefix());
        sb.append(formatter.format(LocalDateTime.now()));
        return sb.toString();
    }
    
    /**
     * Construct the running number cache Key
     *
     * @param serialPrefix stream number prefix
     * @return stream number cache Key
     */
    public static String getCacheKey(String serialPrefix) {
        return FormNoConstants.SERIAL_CACHE_PREFIX.concat(serialPrefix);
    }
    
    /**
     * Complete the running number
     *
     * @param serialPrefix single number prefix
     * @param incrementalSerial The current day's self-adding serial number
     * @author mengqiang
     * @date 2019/1/1
     */
    public static String completionSerial(String serialPrefix, Long incrementalSerial,
                                          FormNoTypeEnum formNoTypeEnum) {
        StringBuffer sb = new StringBuffer(serialPrefix);

        // the length to be filled with zeros = the length of the serial number - the length of the day's incremental count
        int length = formNoTypeEnum.getSerialLength() - String.valueOf(incrementalSerial).length();
        //fill zero
        for (int i = 0; i < length; i++) {
            sb.append("0");
        }
        //redis self-incrementing number for the day
        sb.append(incrementalSerial);
        return sb.toString();
    }


    /**
     * Completes the random number
     *
     * @param serialWithPrefix current single number
     * @param formNoTypeEnum Single number generation enum
     * @author mengqiang
     * @date 2019/1/1
     */
    public static String completionRandom(String serialWithPrefix, FormNoTypeEnum formNoTypeEnum) {
        StringBuffer sb = new StringBuffer(serialWithPrefix);
        //the length of the random number
        int length = formNoTypeEnum.getRandomLength();
        if (length > 0) {
            Random random = new Random();
            for (int i = 0; i < length; i++) {
                //complete random numbers within ten
                sb.append(random.nextInt(10));
            }
        }
        return sb.toString();
    }
}


単一番号生成インターフェース

/**
 * Single number generation interface
 *
 * @author mq
 */
public interface FormNoGenerateService {

    /**
     * Generate document number based on document number type
     *
     * @param formNoTypeEnum document number type
     * @author mengqiang
     * @date 2019/1/1
     */
    String generateFormNo(FormNoTypeEnum formNoTypeEnum);
}


単一番号生成インターフェース実装

/**
 * Single number generation interface implementation
 *
 * @author mengqiang
 * @version FormNoGenerateServiceImpl.java, v 1.0 2019-01-01 18:10
 */
@Service
public class FormNoGenerateServiceImpl implements FormNoGenerateService {
    /**
     * redis service
     * demo project does not add redis-related, if necessary, please refer to the redis blog
     */
    @Autowired
    private RedisCache redisCache;
    /*
     * Generate a document number based on the document number type
     *
     * @param formNoTypeEnum Document number type
     * @author mengqiang
     * @date 2019/1/1
     */
    @Override
    public String generateFormNo(FormNoTypeEnum formNoTypeEnum) {
        //Get the single number prefix
        //format Fixed prefix + time prefix Example : YF20190101
        String formNoPrefix = FormNoSerialUtil.getFormNoPrefix(formNoTypeEnum);
        //Get the cache key
        String cacheKey = FormNoSerialUtil.getCacheKey(formNoPrefix);
        //Get the current day's self-incremental number
        Long incrementalSerial = redisCache.incr(cacheKey);
        //set expiration time 7 days
        redisCache.expire(cacheKey, FormNoConstants.DEFAULT_CACHE_DAYS, TimeUnit.DAYS);
        //combine single number and complete the serial number
        String serialWithPrefix = FormNoSerialUtil
                .completionSerial(formNoPrefix, incrementalSerial, formNoTypeEnum);
        //completion of random number
        return FormNoSerialUtil.completionRandom(serialWithPrefix, formNoTypeEnum);
    }
}


テストを使用する

redisのスクリーンショット


概要

上記は最もエレガントな方法ではなく、ユニバーサルなサービスにするために、ジャーパッケージ方式にするのが良いだろう

Redisベースの分散シングル・分散ID(カスタムルール生成)については、この記事がすべてです。Redisベースの分散シングルと分散ID(カスタムルール生成)については、Script Houseの過去記事を検索するか、以下の記事を引き続きご覧ください。