上文主要包含的最基础的redis自增生成规则流水号。
但仔细分析会发现有几个问题:
1、流水号规则调整了怎么办?只能去改代码?
2、redis切换了怎么办?数据不迁移或者redis挂了从头生成流水号会导致重复怎么办?
3、redis异常怎么办?
设计方案:
1、建立流水号规则配置表:
CREATE TABLE `crl_serial_number_rule` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`rule_code` varchar(30) NOT NULL COMMENT '规则编码',
`rule_desc` varchar(60) NOT NULL COMMENT '规则描述',
`prefix` varchar(10) NULL COMMENT '前缀',
`date_formatter` varchar(20) NULL COMMENT '日期格式',
`random_mark` varchar(10) NOT NULL COMMENT '随机位',
`number_type` varchar(10) NOT NULL COMMENT '序号类型,numberTypeEnum:RANDOM 随机;ORDER 顺序',
`number_length` varchar(2) NOT NULL COMMENT '序号位数',
`number_start` varchar(10) DEFAULT NULL COMMENT '序号起始位',
`number_end` varchar(10) DEFAULT NULL COMMENT '序号结束位',
`current_number` varchar(10) DEFAULT NULL COMMENT '当前序号',
`created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modified_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE INDEX `uniq_idx_rule_code` (`rule_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='流水号规则表';
2、针对redis切换,在1中其实已经设计好数据库表,其中的随机位即可降低切换时的重复概率。
3、针对redis异常可以做出补充方案,确保可以生成一个流水号。
附代码(部分引入涉及敏感工程已去除):
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: xxx
* @date: 2018-06-13 下午3:17
*/
@Service
public class CrlSerialNumberServiceImpl implements CrlSerialNumberService {
private static final Logger logger = LoggerFactory.getLogger(CrlSerialNumberServiceImpl.class);
/*每次增长数值*/
private static int increase=100;
/*超时时间:代码中与TimeUnit.SECONDS共用*/
private static int expireTime=10;
/*重试次数*/
private static int retryCount=5;
@Resource
private CrlSerialNumberRuleService crlSerialNumberRuleService;
@Resource
private DistributedLocker distributedLocker;
@Resource
private CacheBizService cacheClient;
@Override
public String generateSerialNumberByRuleCode(String ruleCode){
String methodDesc = "按规则编码生成流水号";
StringBuilder crlSerialNumber = new StringBuilder();
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|查询流水号规则开始" + "|ruleCode:" + ruleCode);
CrlSerialNumberRule crlSerialNumberRule = crlSerialNumberRuleService.getSerialNumberRuleByRuleCode(ruleCode);
if (null == crlSerialNumberRule) {
logger.warn("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|查询流水号规则为空,请检查流水号规则配置" + "|ruleCode:" + ruleCode);
return crlSerialNumber.append("ERR").append(new SimpleDateFormat(DateFormatterEnum.yyMMddHHmmss.getCode()).format(new Date())).append(String.valueOf(new Random().nextInt(9))).append(new Random().nextInt(getMaxValueString("8"))).toString();
}
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|查询流水号规则结束" + "|ruleCode:" + ruleCode + "|crlSerialNumberRule:" + crlSerialNumberRule);
String dateFormatter = crlSerialNumberRule.getDateFormatter();
String dateString="";
if (StringUtils.isNotBlank(dateFormatter)) {
dateString = new SimpleDateFormat(dateFormatter).format(new Date());
}
if (NumberTypeEnum.RANDOM.getCode().equals(crlSerialNumberRule.getNumberType())) {
String randomMark="";
if(StatusEnum.VALID.getCode().equals(crlSerialNumberRule.getRandomMark())){
randomMark=String.valueOf(new Random().nextInt(9));
}
int random=new Random().nextInt(getMaxValueString(crlSerialNumberRule.getNumberLength()));
return crlSerialNumber.append(crlSerialNumberRule.getPrefix()).append(dateString).append(randomMark).append(getCurrentValueString(crlSerialNumberRule.getNumberLength(),String.valueOf(random))).toString();
} else if (NumberTypeEnum.ORDER.getCode().equals(crlSerialNumberRule.getNumberType())) {
try {
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|加分布式锁开始" + "|ruleCode:" + ruleCode);
CrlCommonResponse crlCommonResponse = distributedLocker.lockRetry(ruleCode, expireTime,TimeUnit.SECONDS,retryCount);
if(!crlCommonResponse.isSuccess()){
throw new Exception(methodDesc+"加分布式锁失败");
}
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|加分布式锁结束" + "|ruleCode:" + ruleCode + "|crlCommonResponse:"+crlCommonResponse);
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|获取当前key的value值开始" + "|ruleCode:" + ruleCode+"|dateFormatter:"+dateFormatter);
String cacheCurrentValue=cacheClient.rpop(crlSerialNumberRule.getPrefix() + dateFormatter + crlSerialNumberRule.getNumberLength());
if(StringUtils.isBlank(cacheCurrentValue)){
/*1 初始化*/
int numberStart;
int numberEnd;
if (StringUtils.isBlank(crlSerialNumberRule.getCurrentNumber())) {
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|数据库当前值不存在|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
/*1.1 赋初值*/
numberStart=1;
numberEnd=numberStart+increase;
/*1.2 起始值处理*/
if (StringUtils.isNotBlank(crlSerialNumberRule.getNumberStart())) {
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了起始值,设置为队列起始值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
numberStart=Integer.parseInt(crlSerialNumberRule.getNumberStart());
numberEnd=numberStart+increase;
}else{
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号未限制起始值,设置1为队列起始值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
}
/*1.3 结束值处理*/
if (StringUtils.isNotBlank(crlSerialNumberRule.getNumberEnd())) {
if(numberEnd>Integer.parseInt(crlSerialNumberRule.getNumberEnd())){
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了结束值,比队列结束值小,设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
numberEnd=Integer.parseInt(crlSerialNumberRule.getNumberEnd());
}else{
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号未限制结束值,比队列结束值大,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
}
}
}else{
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|数据库当前值存在|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
Integer currentNumber= Integer.valueOf(crlSerialNumberRule.getCurrentNumber());
/*2.1 赋初值*/
numberStart=1+currentNumber;
numberEnd=1+currentNumber+increase;
/*2.2.1 能够够获取值,左空右配置*/
if(StringUtils.isBlank(crlSerialNumberRule.getNumberStart())&&StringUtils.isNotBlank(crlSerialNumberRule.getNumberEnd())){
/*2.2.1.1 当前值与流水号结束值*/
if (currentNumber>=Integer.parseInt(crlSerialNumberRule.getNumberEnd())) {
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了结束值|数据库当前值达到位数最大值,设置1为队列起始值|ruleCode:" + ruleCode+"|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|currentNumber:" + currentNumber);
numberStart=1;
numberEnd=numberStart+increase;
}else{
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了最大值|数据库当前值未达到位数最大值,设置数据库当前值+1为队列起始值|ruleCode:" + ruleCode+"|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue);
}
/*2.2.1.2 队列结束值与流水号结束值*/
if(numberEnd>Integer.parseInt(crlSerialNumberRule.getNumberEnd())){
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了结束值,比队列结束值小,则设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
numberEnd=Integer.parseInt(crlSerialNumberRule.getNumberEnd());
}else{
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了结束值,比队列结束值大,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
}
}
/*2.2.2 能够够获取值,左配置右空*/
if(StringUtils.isNotBlank(crlSerialNumberRule.getNumberStart())&&StringUtils.isBlank(crlSerialNumberRule.getNumberEnd())){
/*2.2.2.1 当前值与流水号起始值*/
if(currentNumbergetMaxValueString(crlSerialNumberRule.getNumberLength())){
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号未限制结束值,长度最大值设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
numberEnd=getMaxValueString(crlSerialNumberRule.getNumberLength());
}else{
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号未限制结束值,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
}
}
/*2.2.3 能够够获取值,左右区间未配置*/
if(StringUtils.isBlank(crlSerialNumberRule.getNumberStart())&&StringUtils.isBlank(crlSerialNumberRule.getNumberEnd())){
/*2.2.3.1 当前值与长度最大值*/
if (currentNumber==getMaxValueString(crlSerialNumberRule.getNumberLength())) {
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|未配置流水号区间,数据库当前值达到位数最大值,从1自增|ruleCode:" + ruleCode+"|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|currentNumber:" + currentNumber);
numberStart=1;
numberEnd=numberStart+increase;
}
/*2.2.3.2 队列结束值与长度最大值*/
if(numberEnd>getMaxValueString(crlSerialNumberRule.getNumberLength())){
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|未配置流水号区间,长度最大值设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
numberEnd=getMaxValueString(crlSerialNumberRule.getNumberLength());
}else{
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|未配置流水号区间,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
}
}
/*2.2.4 能够获取值,左右区间配置*/
if(StringUtils.isNotBlank(crlSerialNumberRule.getNumberStart())&&StringUtils.isNotBlank(crlSerialNumberRule.getNumberEnd())){
/*2.2.4.1 当前值与流水号起始值*/
if(currentNumber=Integer.parseInt(crlSerialNumberRule.getNumberEnd())) {
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了结束值|数据库当前值达到位数最大值,设置1为队列起始值|ruleCode:" + ruleCode+"|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|currentNumber:" + currentNumber);
numberStart=Integer.parseInt(crlSerialNumberRule.getNumberStart());
numberEnd=numberStart+increase;
}
/*2.2.4.2 队列结束值与流水号结束值*/
if(numberEnd>Integer.parseInt(crlSerialNumberRule.getNumberEnd())){
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|配置了流水号区间,比队列结束值小,则设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
numberEnd=Integer.parseInt(crlSerialNumberRule.getNumberEnd());
}else{
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|配置了流水号区间,比队列结束值大,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber());
}
}
}
/*2 调用公共方法*/
cacheCurrentValue = updateAndPushAndPop(ruleCode,crlSerialNumberRule, dateFormatter, cacheCurrentValue, numberStart, numberEnd);
}
String randomMark="";
if(StatusEnum.VALID.getCode().equals(crlSerialNumberRule.getRandomMark())){
randomMark=String.valueOf(new Random().nextInt(9));
}
String currentValue = getCurrentValueString(crlSerialNumberRule.getNumberLength(), cacheCurrentValue);
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|释放分布式锁开始" + "|ruleCode:" + ruleCode + "|resData:"+crlCommonResponse.getResData());
distributedLocker.unlock(ruleCode, crlCommonResponse.getResData());
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|释放分布式锁结束" + "|ruleCode:" + ruleCode + "|resData:"+crlCommonResponse.getResData());
return crlSerialNumber.append(crlSerialNumberRule.getPrefix()).append(dateString).append(randomMark).append(currentValue).toString();
} catch (Exception e) {
logger.error("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "异常" + "|ruleCode:" + ruleCode + "|exception:" + e);
int i = new Random().nextInt(getMaxValueString(crlSerialNumberRule.getNumberLength()));
return crlSerialNumber.append(crlSerialNumberRule.getPrefix()).append(dateString).append(9).append(String.valueOf(i)).toString();
}
}
return crlSerialNumber.toString();
}
/**
* 更新数据库当前值、队列入队处理、队列出队获取
* @param ruleCode
* @param crlSerialNumberRule
* @param dateFormatter
* @param cacheCurrentValue
* @param numberStart
* @param numberEnd
* @return
* @throws Exception
*/
private String updateAndPushAndPop(String ruleCode,CrlSerialNumberRule crlSerialNumberRule, String dateFormatter, String cacheCurrentValue, Integer numberStart, Integer numberEnd) throws Exception {
String methodDesc = "按规则编码生成流水号";
/*2.1 更新数据库当前值为队列结束值*/
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|更新流水号规则中当前值开始" + "|ruleCode:" + ruleCode+"|numberEnd:"+numberEnd);
int updateNum = crlSerialNumberRuleService.updateCurrentNumberByRuleCode(String.valueOf(numberEnd), ruleCode);
if(updateNum<=0){
throw new Exception(methodDesc+"更新流水号规则中当前值失败");
}
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|更新流水号规则中当前值结束" + "|ruleCode:" + ruleCode+"|numberEnd:"+numberEnd+"|updateNum:"+updateNum);
/*2.2 队列入队开始*/
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了起始值,入队队列开始|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber()+"|numberStart:"+numberStart+"|numberEnd:"+numberEnd);
for(int i=numberStart;i<=numberEnd;i++){
cacheClient.lpush(crlSerialNumberRule.getPrefix() + dateFormatter + crlSerialNumberRule.getNumberLength(), String.valueOf(i));
}
logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了起始值,入队队列结束|ruleCode:" + ruleCode + "|prefix:" + crlSerialNumberRule.getPrefix() + "|length:" + crlSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue+"|currentNumber:"+crlSerialNumberRule.getCurrentNumber()+"|numberStart:"+numberStart+"|numberEnd:"+numberEnd);
/*2.3 二次出队获取当前缓存值*/
String str = cacheClient.rpop(crlSerialNumberRule.getPrefix() + dateFormatter + crlSerialNumberRule.getNumberLength());
if (StringUtils.isNotBlank(str)) {
cacheCurrentValue=str;
}else{
throw new Exception(methodDesc + "入队数据后rpop命令执行失败");
}
return cacheCurrentValue;
}
/**
* 字符串补足方法
* @param length
* @param currentValue
* @return
*/
private String getCurrentValueString(String length, String currentValue) {
String cValue=currentValue;
for(int i=cValue.length();i