分布式全局sequence生成貌似一直都没有一个完美的解决方案,结合之前在‘架构师之路’看到的五种方式,自己基于数据库,用java实现了一个全局sequence生成的工具。其他的方案,数据库自增太局限,数据库分布式不适用;数据库函数的不便于迁移、数据库压力大;单机生成的又太长;uuid识别度太差;所以用了其中一种方案,我觉得还可以接受的。
备份保存最后的id,用于应用重启等
DROP TABLE IF EXISTS `prd_base_sequence`;
CREATE TABLE `prd_base_sequence` (
`seq_key` tinyint(4) NOT NULL DEFAULT '0' COMMENT '业务类型,0:product;1:line;2:user',
`start_id` int(11) NOT NULL DEFAULT '1000000' COMMENT '起始id',
`step_by` int(11) NOT NULL DEFAULT '10' COMMENT '步长',
PRIMARY KEY (`seq_key`)
) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='全局sequence生成表';
对外提供的Util,可以根据业务Key来获取对应的Sequence
package com.arlen.common.sequence;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SequenceUtil {
private final Logger logger = LoggerFactory.getLogger(SequenceUtil.class);
private final static Map<Byte, BaseSequence> sequenceMap = new ConcurrentHashMap<Byte, BaseSequence>();
private SequenceUtil() {}
/**
* 根据业务key获取对应的自增序列
* @param key
* @return 序列号
* @since CodingExample Ver(编码范例查看) 1.1
* @author arlen
* @throws SequenceGenerateException
*/
public synchronized static int getNextId(Byte key) {
try {
if (!sequenceMap.containsKey(key)) {
sequenceMap.put(key, new BaseSequence(key));
}
return sequenceMap.get(key).getNextId();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Sequence产生实体
package com.arlen.common.sequence;
import org.springframework.context.ApplicationContext;
import com.arlen.common.sequence.dao.IBaseSequenceDao;
import com.arlen.common.spring.SpringContextUtil;
public class BaseSequence {
private final static int START = 1000000;
private final static int STEP = 10;
/**
* 业务类型,0:product;1:line;2:user
*/
private Byte seqKey;
/**
* 起始id
*/
private Integer startId;
/**
* 步长
*/
private Integer stepBy;
/**
* 下个id
*/
private int nextId;
private IBaseSequenceDao dao;
public BaseSequence() {
}
public BaseSequence(byte seqKey) {
this.seqKey = seqKey;
checkAndReload();
}
public int getNextId() {
if (this.nextId == this.startId) {
this.startId += this.stepBy;
getDao().updateByPrimaryKeySelective(this);
}
return nextId++;
}
private void checkAndReload() {
BaseSequence sequence = getDao().selectByPrimaryKey(this.seqKey);
if (sequence == null) {
this.nextId = START;
this.startId = START + STEP;
this.stepBy = STEP;
getDao().insert(this);
} else {
this.nextId = sequence.getStartId();
this.stepBy = sequence.getStepBy();
this.startId = sequence.getStartId() + this.stepBy;
getDao().updateByPrimaryKeySelective(this);
}
}
private IBaseSequenceDao getDao() {
if (dao == null) {
// 如果在Web环境下,可以用ContextLoader.getCurrentWebApplicationContext();,我这里是写了一个简单的工具类,见文章最后
ApplicationContext applicationContext = SpringContextUtil.getApplicationContext();
if (applicationContext == null) {
throw new RuntimeException("Init sequence faild, not in web environment. Get WebApplication failed");
}
dao = applicationContext.getBean(IBaseSequenceDao.class);
if (dao == null) {
throw new RuntimeException("Init sequence faild, IBaseSequenceDao dose not init");
}
}
return dao;
}
public Byte getSeqKey() {
return seqKey;
}
public void setSeqKey(Byte seqKey) {
this.seqKey = seqKey;
}
public Integer getStepBy() {
return stepBy;
}
public void setStepBy(Integer stepBy) {
this.stepBy = stepBy;
}
public Integer getStartId() {
return startId;
}
public void setStartId(Integer startId) {
this.startId = startId;
}
}
Mybatis SqlMap文件必须得在自己的Spring配置中扫描到
<?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="com.arlen.common.sequence.dao.IBaseSequenceDao">
<resultMap id="BaseResultMap" type="com.arlen.common.sequence.BaseSequence">
<id column="t_seq_key" jdbcType="TINYINT" property="seqKey" />
<result column="t_start_id" jdbcType="INTEGER" property="startId" />
<result column="t_step_by" jdbcType="INTEGER" property="stepBy" />
</resultMap>
<sql id="Base_Column_List">
t.seq_key as t_seq_key, t.start_id as t_start_id, t.step_by as t_step_by
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Byte" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from prd_base_sequence t
where t.seq_key = #{seqKey,jdbcType=TINYINT}
</select>
<insert id="insert" parameterType="com.arlen.common.sequence.BaseSequence">
insert into prd_base_sequence (seq_key, start_id, step_by
)
values (#{seqKey,jdbcType=TINYINT}, #{startId,jdbcType=INTEGER}, #{stepBy,jdbcType=INTEGER}
)
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.arlen.common.sequence.BaseSequence">
update prd_base_sequence
<set>
<if test="startId != null">
start_id = #{startId,jdbcType=INTEGER},
</if>
<if test="stepBy != null">
step_by = #{stepBy,jdbcType=INTEGER},
</if>
</set>
where seq_key = #{seqKey,jdbcType=TINYINT}
</update>
</mapper>
使用JUnit测试,注意是基于Spring环境。100个线程并发获取。
public class OrderExtraFeeTest extends BaseTest {
@Test
public void sequence() {
System.out.println("========================================");
//System.out.println(SequenceUtil.getNextId((byte)3));
for (int i = 0; i < 100; i ++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "loop in");
try {
System.out.println(SequenceUtil.getNextId((byte)4));
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("========================================");
}
}
我的做法是定义了一个枚举类,然后各自业务系统用自己的即可,当然还有其他方式,就不一一列举了
public enum SequenceEnum {
PRODUCT((byte)1),LINE((byte)1),UC((byte)1);
private byte value;
private SequenceEnum(byte value) {
this.value = value;
}
public int getID() {
return SequenceUtil.getNextId(this.value);
}
}
还有很多方式,可以参照网上,如何获取ApplicationContext。注意下面的类需要被Spring扫描到
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
applicationContext = ac;
}
/**
* 获取applicationContext
*
* @return applicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 根据id获取Bean
*
* @param id
* @return Bean
*/
public static Object getBeanById(String id) {
return applicationContext.getBean(id);
}
/**
* 根据class获取Bean
*
* @param clazz
* @return Bean
*/
public static Object getBeanByClass(Class<?> clazz) {
return applicationContext.getBean(clazz);
}
/**
* 根据class获取所有的Bean
*
* @param clazz
* @return Map
*/
public static Map<String, ?> getBeansByClass(Class<?> clazz) {
return applicationContext.getBeansOfType(clazz);
}
}