一定要有存储!@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