全局序列号生成器-MAC00000106PCS

邓尼茨我今天去赶集 2020年03月05日 1,004次浏览

一定要有存储!@Mysql。

CREATE TABLE `starcloud_global_sequence`  (
  `sequence_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '序列号名字',
  `cache_seq_min` bigint(20) UNSIGNED NOT NULL COMMENT '缓存中的序列号最小值',
  `cache_seq_max` bigint(20) UNSIGNED NOT NULL COMMENT '缓存中的序列号最大值',
  `increment_by` int(11) UNSIGNED NOT NULL COMMENT '自增步长',
  `value_prefix` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '前缀',
  `value_suffix` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '后缀',
  `bit_width` int(11) NULL DEFAULT NULL COMMENT '数字位宽,前补零',
  `cache_size` int(11) UNSIGNED NULL DEFAULT NULL COMMENT 'Java进程中的缓存的序列号个数',
  `creation_date` datetime(0) NULL DEFAULT NULL COMMENT '创建者',
  `last_update_date` datetime(0) NULL DEFAULT NULL COMMENT '最后更新时间',
  PRIMARY KEY (`sequence_name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '全局序列号生成实体表' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

mybatis的映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fis.starcloud.component.gravity.IGlobalSequenceDao">

    <resultMap id="GlobalSequenceMap" type="fis.starcloud.component.gravity.GlobalSequence">
        <id column="sequence_name" property="sequenceName"/>
        <result column="cache_seq_min" property="cacheSeqMin"/>
        <result column="cache_seq_max" property="cacheSeqMax"/>
        <result column="increment_by" property="incrementBy"/>
        <result column="cache_size" property="cacheSize"/>
        <result column="value_prefix" property="valuePrefix"/>
        <result column="value_suffix" property="valueSuffix"/>
        <result column="bit_width" property="bitWidth"/>
        <result column="creation_date" property="creationDate" />
        <result column="last_update_date" property="lastUpdateDate" />
    </resultMap>
    <select id="get" timeout="60" parameterType="java.lang.String"  resultMap="GlobalSequenceMap">
        select * from starcloud_global_sequence where sequence_name = #{sequenceName} for update
    </select>

    <insert id="insert" timeout="60" keyProperty="globalSequence.sequenceName" keyColumn="sequence_name">
        insert into starcloud_global_sequence (sequence_name, cache_seq_min, cache_seq_max, increment_by, cache_size, value_prefix, value_suffix, bit_width, creation_date, last_update_date)
        values (
            #{globalSequence.sequenceName},
            #{globalSequence.cacheSeqMin},
            #{globalSequence.cacheSeqMax},
            #{globalSequence.incrementBy},
            #{globalSequence.cacheSize},
            #{globalSequence.valuePrefix},
            #{globalSequence.valueSuffix},
            #{globalSequence.bitWidth},
            #{globalSequence.creationDate},
            #{globalSequence.lastUpdateDate}
        )
        on duplicate key update cache_seq_min = cache_seq_max, cache_seq_max = cache_seq_min + increment_by * cache_size
    </insert>

    <update id="update" timeout="60">
        update starcloud_global_sequence set cache_seq_min=#{globalSequence.cacheSeqMin},cache_seq_max = #{globalSequence.cacheSeqMax},
            increment_by=#{globalSequence.incrementBy},cache_size = #{globalSequence.cacheSize},
            last_update_date=#{globalSequence.lastUpdateDate}
        where sequence_name = #{globalSequence.sequenceName}
    </update>
</mapper>

GlobalSequence对象

package fis.starcloud.component.gravity;

import lombok.Data;
import org.springframework.util.StringUtils;

import java.util.Date;
@Data
public class GlobalSequence {
    private String sequenceName;
    private Long cacheSeqMin =0l;
    private Long cacheSeqMax = 100l;
    private Integer incrementBy = 1;
    private Integer cacheSize = 100;
    private Date lastUpdateDate;
    private Date creationDate;
    private Integer bitWidth;
    private String valuePrefix;
    private String valueSuffix;

    public GlobalSequence(){}

    public GlobalSequence(String sequenceName){
        this.sequenceName=sequenceName;
    }

    private synchronized Long increment() {
        cacheSeqMin += incrementBy;
        return cacheSeqMin;
    }

    public String value() {
        StringBuffer stringBuffer=new StringBuffer();
        if(!StringUtils.isEmpty(valuePrefix)){
            stringBuffer.append(valuePrefix);
        }
        if(this.bitWidth!=null && this.bitWidth>0){
            stringBuffer.append(String.format("%0"+bitWidth+"d", this.increment()));
        }else {
            stringBuffer.append(this.increment());
        }
        if(!StringUtils.isEmpty(valueSuffix)) {
            stringBuffer.append(valueSuffix);
        }
        return stringBuffer.toString();
    }

    @Override
    public String toString() {
        return "GlobalSequence{" +
                "sequenceName='" + sequenceName + '\'' +
                ", cacheSeqMin=" + cacheSeqMin +
                ", cacheSeqMax=" + cacheSeqMax +
                ", incrementBy=" + incrementBy +
                ", cacheSize=" + cacheSize +
                '}';
    }
}

Dao方法

package fis.starcloud.component.gravity;

import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;

@Component
public interface IGlobalSequenceDao {
    GlobalSequence get(@Param("sequenceName") String sequenceName);

    Integer insert(@Param("globalSequence") GlobalSequence globalSequence);

    Integer update(@Param("globalSequence") GlobalSequence globalSequence);
}

Service方法

package fis.starcloud.component.gravity;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

@Component
@Slf4j
public class GlobalSequenceService {

    @Autowired
    private IGlobalSequenceDao globalSequenceDao;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE)
    public synchronized GlobalSequence acquireSequence(GlobalSequence globalSequenceInMem) {
        GlobalSequence globalSequence;
        try {
            globalSequence = globalSequenceDao.get(globalSequenceInMem.getSequenceName());
            validateAndModify(globalSequence);
            Date now = new Date();

            /**
             * 如果不存在该sequence,插入后返回;如果存在,则更新。
             */

            if (globalSequence == null) {
                globalSequence = new GlobalSequence(globalSequenceInMem.getSequenceName());
                globalSequence.setCreationDate(now);
                globalSequence.setLastUpdateDate(now);
                globalSequenceDao.insert(globalSequence);
                log.warn("GLOBAL_SEQUENCE : after insertion: " + globalSequence.toString());
            } else {
                //每次更新起始值,都以表记录中的起始值为准,而不是根据内存中的序列的起始值。
                globalSequence.setCacheSeqMin(globalSequence.getCacheSeqMax());
                globalSequence.setCacheSeqMax(globalSequence.getCacheSeqMax() + globalSequence.getCacheSize() * globalSequence.getIncrementBy());
                globalSequence.setLastUpdateDate(now);
                Integer ret = globalSequenceDao.update(globalSequence);
                log.warn("GLOBAL_SEQUENCE : after update: " + ret + ":" + globalSequence.toString());
            }


        } catch (Exception e) {
            log.error("GLOBAL_SEQUENCE : acquire sequence [{}] failed! In memory sequence is  " + e.getMessage(), globalSequenceInMem.toString());
            throw e;
        }
        return globalSequence;
    }

    /**
     * 基本校验,减小胡乱修改数据库表的中数据造成的序列号错乱的影响
     *
     * @param globalSequence
     */
    private void validateAndModify(GlobalSequence globalSequence) {
        if (globalSequence == null) return;

        if (globalSequence.getCacheSeqMin() < 0) {
            globalSequence.setCacheSeqMin(0L);
        }

        if (globalSequence.getCacheSize() <= 0) {
            globalSequence.setCacheSize(100);
        }

        if (globalSequence.getIncrementBy() < 1) {
            globalSequence.setIncrementBy(1);
        }

        Long max = globalSequence.getCacheSeqMin() + globalSequence.getCacheSize() * globalSequence.getIncrementBy();
        if (globalSequence.getCacheSeqMax() < max) {
            globalSequence.setCacheSeqMax(max);
        }
    }
}

Generator类

package fis.starcloud.component.gravity;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class GlobalSequenceGenerator {
    private static Map<String, GlobalSequence> sequenceMap = new ConcurrentHashMap<>();

    @Autowired
    private GlobalSequenceService globalSequenceService;

    /**
     * 取内存里的序列号
     * 根据序列号名字取下一个序列号
     *
     * @param sequenceName 序列号名字
     * @return
     */
    public String next(String sequenceName) throws Exception {
        if (StringUtils.isEmpty(sequenceName)) {
            throw new IllegalArgumentException("sequenceName should not be empty!");
        }

        GlobalSequence sequence = sequenceMap.get(sequenceName.toUpperCase());

        if (isRunOut(sequence)) {
            try {
                if (sequence == null) {
                    sequence = new GlobalSequence(sequenceName);
                }
                sequence = globalSequenceService.acquireSequence(sequence);
            } catch (Exception e) {
                throw e;
            }
            sequenceMap.put(sequence.getSequenceName().toUpperCase(), sequence);
        }

        return sequence.value();
    }

    /**
     * JVM中无改序列号或者序列号已经用光
     *
     * @param sequence
     * @return
     */
    private boolean isRunOut(GlobalSequence sequence) {
        return sequence == null || sequence.getCacheSeqMin() > sequence.getCacheSeqMax() - sequence.getIncrementBy();
    }
}

调用GlobalSequenceGenerator类的next方法即可,参数为序列号的名称,默认为无前缀,无后缀和无等位宽。
也可先插入数据,比如前缀为value_prefix为MAC,bit_width为8,value_suffix为PCS。

能够支持的序列号格式:
1
0001
ABC0000001
MAC0000001PCS
这四种基本形式又可以随意组合,比如调用两次next方法拼接成一个新的序列号。
如ABC0510EFG7759Z