Redis generates distributed serial numbers

“This is the second day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021”

The introduction

For recent projects, document numbers need to be made in a fixed format: fixed prefix of document type + time stamp of year month day + 4-digit serial number. It is required that the serial number of each document type be unique to facilitate subsequent business use. In the previous project, UUID was used as the document number of other businesses. After communicating with the team leader, Redis was used in the project, so it happened to be more convenient to use Redis to solve the problem of unique distributed deployment order number.

Distributed ids can be handled in the same way

Train of thought

Example: Take the special document as an example, today is November 4, 2021, the document number of the first document today should be: If S202111040001 is implemented by Redis, my-test:no:S20211104 is used as the Redis cache key, 1 is used as the value for storage, and each time a value is obtained, it will increment its count, and the expiration time will be updated each time the key is fetched. Ensure that there is no large accumulation of keys in Redis if keys fail automatically.

Redis implementation

All command operations are single-threaded, and Redis itself provides auto-increment atomic commands like INCr and Increby, so the generated ids are guaranteed to be uniquely ordered.

  • Advantages: Independent of database, flexible and convenient, and better performance than database.

According to the business characteristics, it is necessary to encapsulate the key and document type of Redis. For subsequent expansion, the total length of document number and serial number are also encapsulated. Here is the code implementation:

Constant class

The Redis cache key prefix is stored in the constant, as well as the prefix format of the year month day timestamp

/ * * *@className: FormNoConstant
 * @description: Order number generates constant *@author: hanHang
 * @create: 2021/9/28 9:41
 **/
public class FormNoConstant {
    /** * Serial number Cache Key prefix */
    public static final String SERIAL_CACHE_PREFIX = "my-test:no:";
    /** * Serial number yyyyMMdd prefix */
    public static final String SERIAL_YYYY_MM_DD_PREFIX = "yyyyMMdd";
}
Copy the code

The enumeration

Enumeration stores document type prefix, date-time stamp format expression, serial number length, total length of document number and other information

/ * * *@className: FormNoTypeEnum
 * @description: Number Generation type Enumeration *@author: hanHang
 * @create: 2021/9/28 9:40 * * /
@Getter
public enum FormNoTypeEnum {
    /** * Special document no. : */
    SPECIAL_ORDER("S", FormNoConstants.SERIAL_YYYY_MM_DD_PREFIX, 3 , 12),

    /** * Normal document number: */
    NORMAL_ORDER("N", FormNoConstants.SERIAL_YYYY_MM_DD_PREFIX, 3 , 12);

    /** * fill in "*/" if the number prefix is empty
    private final String prefix;

    /** * Time format * e.g. yyyyMMdd */
    private final String datePattern;

    /** ** length */
    private final Integer serialLength;

    /** * Total length */
    private final Integer totalLength;


    FormNoTypeEnum(String prefix, String datePattern, Integer serialLength, Integer totalLength) {
        this.prefix = prefix;
        this.datePattern = datePattern;
        this.serialLength = serialLength;
        this.totalLength = totalLength; }}Copy the code

Utility class

The utility class contains methods to obtain the prefix of the order number, obtain the cache key according to the prefix of the order number, and generate the complete sequence number by completing the sequence number.

GetCacheKey (String serialPrefix) : This method obtains the key of the corresponding document type-date from Redis

/ * * *@className: FormNoSerialUtil
 * @description: Document Number Tools *@author: hanHang
 * @create: 2021/9/28 * * / a quarter to ten
public class FormNoSerialUtil {
    /** * get the prefix * based on the document type@param formNoTypeEnum
	 * @returnThe single prefix */
    public static String getFormNoPrefix(FormNoTypeEnum formNoTypeEnum) {
        // Format the time
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formNoTypeEnum.getDatePattern());
        return formNoTypeEnum.getPrefix() +
                formatter.format(LocalDateTime.now());
    }

    /** * get the serial number cache Key *@paramSerialPrefix Serial number prefix *@returnSerial number Cache Key */
    public static String getCacheKey(String serialPrefix) {
        return FormNoConstants.SERIAL_CACHE_PREFIX.concat(serialPrefix);
    }

    /** **@paramSerialPrefix Indicates the serial number prefix *@paramIncrementalSerial Increments the current day serial number *@paramFormNoTypeEnum Document type Enumeration *@returnComplete serial number */
    public static String completionSerial(String serialPrefix, Long incrementalSerial, FormNoTypeEnum formNoTypeEnum) {
        StringBuffer sb = new StringBuffer(serialPrefix);

        // Need to add 0 length = serial number length - day increment count length
        int length = formNoTypeEnum.getSerialLength() - String.valueOf(incrementalSerial).length();
        / / zero padding
        for (int i = 0; i < length; i++) {
            sb.append("0");
        }
        //redis increments on the day
        sb.append(incrementalSerial);
        returnsb.toString(); }}Copy the code

Component

Pass this class to the Spring proxy using the Component annotation, expose the generateFormNo method, get the cache key based on the document type enumeration, and get the value of the key in Redis, incrementing each time.

Note: if it is called for the first time that day, value returns 0, which needs to be incremented again because it needs to count from 1.

/ * * *@className: FormNoGenerateComponent
 * @description: Generates a single number *@author: hanHang
 * @create: 2021/9/28 9:52 * * /
@Component
public class FormNoGenerateComponent {
    @Resource
    RedisTemplate<String, Object> redisTemplate;

    /** * Generate document number * based on document number type@paramFormNoTypeEnum Document number type *@returnDocument no. */
    public String generateFormNo(FormNoTypeEnum formNoTypeEnum) {
        // Get single prefix format Fixed prefix + time prefix example: S20211104
        String formNoPrefix = FormNoSerialUtil.getFormNoPrefix(formNoTypeEnum);
        // Get the cache key
        String cacheKey = FormNoSerialUtil.getCacheKey(formNoPrefix);

        Long incrementalSerial = getIncr(cacheKey,getCurrent2TodayEndMillisTime());
        if (incrementalSerial==0){
            incrementalSerial = getIncr(cacheKey,getCurrent2TodayEndMillisTime());
        }

        // combine the order number and complete the sequence number
        return FormNoSerialUtil
                .completionSerial(formNoPrefix, incrementalSerial, formNoTypeEnum);
    }

    /** * get the increment of the day *@param key key
     * @paramLiveTime indicates the liveTime *@returnSelf-increment of the day */
    private Long getIncr(String key, long liveTime) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        long increment = entityIdCounter.getAndIncrement();

        // Set the expiration time initially
        if (increment == 0 && liveTime > 0) {
            entityIdCounter.expire(liveTime, TimeUnit.MILLISECONDS);
        }
        return increment;
    }

    / * * *@returnThe number of milliseconds between now and the end of today */
    private Long getCurrent2TodayEndMillisTime(a) {
        Calendar todayEnd = Calendar.getInstance();
        todayEnd.set(Calendar.HOUR_OF_DAY, 23);
        todayEnd.set(Calendar.MINUTE, 59);
        todayEnd.set(Calendar.SECOND, 59);
        todayEnd.set(Calendar.MILLISECOND, 999);
        returntodayEnd.getTimeInMillis() - System.currentTimeMillis(); }}Copy the code

conclusion

Using Redis to do distributed numbering is currently used in the work of the scene is more, can also use this method to do distributed unique primary key, but if the business processing failure, this number will be occupied, there will be a day of data number broken scene, I did not think of a good solution, need to boss advice. In the interview, I was asked to use the snowflake algorithm to deal with distributed unique primary keys. I haven’t studied it yet, and I will write another article after I have studied it.