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='流水号规则表';
1、首先获取一次缓存中的序列号,不要直接加分布式锁,毕竟遇到查不到的情况约1000分之一
2、流水号大约1秒多生成,建议超时时长设置为2-3秒,重试次数可以多次,每次间隔500ms即可。间隔过大,会导致撞锁。间隔太小会导致无用的重试次数太多。
3、一次操作时间《加锁的重试次数间隔时间<两次操作时间。理由是:若重试的次数间隔时间比一次操作时间短,则会导致加锁失败。若大于两次操作时间,则造成浪费。
4、入队的方式,除了执行一个个命令外,rpush是支持把数组一次性存入缓存的
import org.apache.commons.lang3.StringUtils;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: wzh* @date: 2019-01-21 上午10:44*/@Service("dubheSerialNumberCenterServiceImpl")public class DubheSerialNumberCenterServiceImpl implements DubheSerialNumberCenterService {private static final Logger logger = LoggerFactory.getLogger(DubheSerialNumberCenterServiceImpl.class);/*超时时间:代码中与TimeUnit.SECONDS共用*/private static long expireTime = 3;/*重试次数*/private static int retryCount = 10;@Resourceprivate DubheSerialNumberRuleService dubheSerialNumberRuleService;@Resourceprivate DistributedLocker distributedLocker;@Resourceprivate R2mClusterClient r2mClusterClient;@Overridepublic CommonResponse<String> generateSerialNumber(CommonRequest<SerialNumberRequest> req) {String methodDesc = "DubheSerialNumberCenterServiceImpl|generateSerialNumber|按规则编码生成流水号|";SerialNumberRequest serialNumberRequest = req.getRequestData();String ruleCode = serialNumberRequest.getRuleCode();StringBuilder crlSerialNumber = new StringBuilder();logger.info(methodDesc + "查询流水号规则开始" + "|ruleCode:" + ruleCode);DubheSerialNumberRule dubheSerialNumberRule = dubheSerialNumberRuleService.getSerialNumberRuleByParams(serialNumberRequest.getRuleCode());if (null == dubheSerialNumberRule) {logger.warn(methodDesc + "查询流水号规则为空,请检查流水号规则配置" + "|ruleCode:" + ruleCode);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();return CommonResponse.buildResponse(req, RespCodeEnum.SERIAL_NO_RULE_NOT_EXIST, crlSerialNumber.toString());}logger.info(methodDesc +"查询流水号规则结束" + "|ruleCode:" + ruleCode + "|dubheSerialNumberRule:" + dubheSerialNumberRule);String dateFormatter = dubheSerialNumberRule.getDateFormatter();String dateString = "";if (StringUtils.isNotBlank(dateFormatter)) {dateString = new SimpleDateFormat(dateFormatter).format(new Date());}String randomMark = "";if (0<dubheSerialNumberRule.getRandomMark()) {int maxNumber=getMaxValueString(dubheSerialNumberRule.getRandomMark());randomMark = format(dubheSerialNumberRule.getRandomMark(),new Random().nextInt(maxNumber));}int random = new Random().nextInt(getMaxValueString(dubheSerialNumberRule.getNumberLength()));if (NumberTypeEnum.RANDOM.getCode().equals(dubheSerialNumberRule.getNumberType())) {crlSerialNumber.append(dubheSerialNumberRule.getPrefix()).append(dateString).append(randomMark).append(format(dubheSerialNumberRule.getNumberLength(), random)).toString();return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());} else if (NumberTypeEnum.ORDER.getCode().equals(dubheSerialNumberRule.getNumberType())) {/*拼接前缀+时间长度+随机位数*/crlSerialNumber.append(dubheSerialNumberRule.getPrefix()).append(dateString).append(randomMark);int increase = dubheSerialNumberRule.getQueueNumber();String cacheKey=dubheSerialNumberRule.getPrefix() + dateFormatter + dubheSerialNumberRule.getNumberLength();String redisKey="";try {logger.info(methodDesc + "|获取当前key的value值开始" + "|ruleCode:" + ruleCode + "|cacheKey:" + cacheKey);String cacheCurrentValue = r2mClusterClient.rpop(cacheKey);if(StringUtils.isNotBlank(cacheCurrentValue)){logger.info(methodDesc + "|获取当前key的value值|出参|cacheCurrentValue:",cacheCurrentValue,"|入参|cacheKey:" + cacheKey + "|ruleCode:" + ruleCode);crlSerialNumber.append(format(dubheSerialNumberRule.getNumberLength(), Integer.parseInt(cacheCurrentValue))).toString();logger.info(methodDesc + "|返回流水号|出参|crlSerialNumber:",crlSerialNumber.toString(),"|入参|ruleCode:" + ruleCode);return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());}logger.info(methodDesc + "|获取当前key的value值不存在|出参|cacheCurrentValue:",cacheCurrentValue,"|入参|cacheKey:" + cacheKey + "|ruleCode:" + ruleCode);/*1 未能从缓存查询,先加锁*/logger.info(methodDesc + "|加分布式锁开始" + "|ruleCode:" + ruleCode);CommonResponse<String> crlCommonResponse = distributedLocker.lockRetry(ruleCode, expireTime, TimeUnit.SECONDS, retryCount);if (!crlCommonResponse.isSuccess()) {throw new BizException(RespCodeEnum.SERIAL_NO_LOCK_FAIL);}redisKey=crlCommonResponse.getResponseData();logger.info(methodDesc + "|加分布式锁结束" + "|ruleCode:" + ruleCode + "|crlCommonResponse:" + crlCommonResponse);/*2 再获取一次缓存*/logger.info(methodDesc + "|获取当前key的value值开始" + "|ruleCode:" + ruleCode + "|cacheKey:" + cacheKey);cacheCurrentValue = r2mClusterClient.rpop(cacheKey);if (StringUtils.isNotBlank(cacheCurrentValue)) {logger.info(methodDesc + "|获取当前key的value值|出参|cacheCurrentValue:", cacheCurrentValue, "|入参|cacheKey:" + cacheKey + "|ruleCode:" + ruleCode);crlSerialNumber.append(format(dubheSerialNumberRule.getNumberLength(), Integer.parseInt(cacheCurrentValue))).toString();logger.info(methodDesc + "|返回流水号|出参|crlSerialNumber:", crlSerialNumber.toString(), "|入参|ruleCode:" + ruleCode);return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());}logger.info(methodDesc + "|获取当前key的value值不存在|出参|cacheCurrentValue:",cacheCurrentValue,"|入参|cacheKey:" + cacheKey + "|ruleCode:" + ruleCode);/*3 处理起始值与结束值*/int numberStart;int numberEnd;if (StringUtils.isBlank(dubheSerialNumberRule.getCurrentNumber())) {logger.info(methodDesc + "|当前缓存值不存在|数据库当前值不存在|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());/*3.1 数据库当前值不存在处理*/numberStart = dubheSerialNumberRule.getNumberStart();numberEnd = numberStart + increase;/*3.1.1 结束值处理*/if (numberEnd > dubheSerialNumberRule.getNumberEnd()) {logger.info(methodDesc + "|当前缓存值不存在|流水号限制了结束值,比队列结束值小,设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());numberEnd = dubheSerialNumberRule.getNumberEnd();} else {logger.info(methodDesc + "|当前缓存值不存在|流水号未限制结束值,比队列结束值大,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());}} else {logger.info(methodDesc + "|当前缓存值不存在|数据库当前值存在|ruleCode:" + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());Integer currentNumber = Integer.valueOf(dubheSerialNumberRule.getCurrentNumber());/*3.2 数据库当前值存在处理*/numberStart = 1 + currentNumber;numberEnd = 1 + currentNumber + increase;/*3.2.1 当前值与流水号起始值*/if (currentNumber < dubheSerialNumberRule.getNumberStart()) {logger.info(methodDesc + "|当前缓存值不存在|配置了流水号区间|可能由于修改了规则导致数据库当前值比左区间小,缓存按起始值set|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|currentNumber:" + currentNumber + "|numberStart:" + dubheSerialNumberRule.getNumberStart());numberStart = dubheSerialNumberRule.getNumberStart();numberEnd = numberStart + increase;}/*3.2.2 当前值与流水号结束值*/else if (currentNumber >= dubheSerialNumberRule.getNumberEnd()) {logger.info(methodDesc + "|当前缓存值不存在|流水号限制了结束值|数据库当前值达到位数最大值,设置1为队列起始值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|currentNumber:" + currentNumber);numberStart = dubheSerialNumberRule.getNumberStart();numberEnd = numberStart + increase;}/*3.2.3 队列结束值与流水号结束值*/if (numberEnd > dubheSerialNumberRule.getNumberEnd()) {logger.info(methodDesc + "|当前缓存值不存在|配置了流水号区间,比队列结束值小,则设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());numberEnd = dubheSerialNumberRule.getNumberEnd();} else {logger.info(methodDesc + "|当前缓存值不存在|配置了流水号区间,比队列结束值大,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());}}/*4 入队列并更新数据库*/cacheCurrentValue = updateAndPushAndPop(dubheSerialNumberRule, cacheKey, cacheCurrentValue, numberStart, numberEnd);crlSerialNumber.append(format(dubheSerialNumberRule.getNumberLength(), Integer.parseInt(cacheCurrentValue))).toString();/*5 返回流水号*/logger.info(methodDesc + "|返回流水号|出参|crlSerialNumber:", crlSerialNumber.toString(), "|入参|ruleCode:" + ruleCode);return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());} catch (Exception e) {logger.error(methodDesc + "异常|入参|ruleCode:" + ruleCode,"|exception:",e);crlSerialNumber.append(dubheSerialNumberRule.getPrefix()).append(dateString).append(9).append(format(dubheSerialNumberRule.getNumberLength(),random)).toString();return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());}finally {if(StringUtils.isNotBlank(redisKey)){logger.info(methodDesc + "|释放分布式锁开始" + "|ruleCode:" + ruleCode + "|redisKey:" + redisKey);distributedLocker.unlock(ruleCode, redisKey);logger.info(methodDesc + "|释放分布式锁结束" + "|ruleCode:" + ruleCode + "|redisKey:" + redisKey);}}}return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());}/*** String.format效率较低* @param length* @param data* @return*/private String format(int length, int data) {char[] result = new char[length];for(int i=result.length-1;i>=0;i--) {result[i] = (char) (data % 10 + 48);data/=10;}return new String(result);}/*** 更新数据库当前值、队列入队处理、队列出队获取** @param dubheSerialNumberRule* @param cacheKey* @param cacheCurrentValue* @param numberStart* @param numberEnd* @return* @throws Exception*/private String updateAndPushAndPop(DubheSerialNumberRule dubheSerialNumberRule, String cacheKey, String cacheCurrentValue, Integer numberStart, Integer numberEnd) throws Exception {String methodDesc = "按规则编码生成流水号";/*2.1 更新数据库当前值为队列结束值*/logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|更新流水号规则中当前值开始" + "|ruleCode:" + dubheSerialNumberRule.getRuleCode() + "|numberEnd:" + numberEnd);int updateNum = dubheSerialNumberRuleService.updateCurrentNumberByParams(String.valueOf(numberEnd), dubheSerialNumberRule.getRuleCode());if (updateNum <= 0) {throw new Exception(methodDesc + "更新流水号规则中当前值失败");}logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|更新流水号规则中当前值结束" + "|ruleCode:" + dubheSerialNumberRule.getRuleCode() + "|numberEnd:" + numberEnd + "|updateNum:" + updateNum);/*2.2 队列入队开始*/logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了起始值,入队队列开始|ruleCode:" + dubheSerialNumberRule.getRuleCode() + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber() + "|numberStart:" + numberStart + "|numberEnd:" + numberEnd);String[] info = new String[numberEnd - numberStart];logger.info("缓存入队:start{},end{}",numberStart+1,numberEnd);for (int i = 0; i < info.length; i++) {info[i] =format(dubheSerialNumberRule.getNumberLength(), i + numberStart + 1);}r2mClusterClient.lpush(cacheKey, info);logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了起始值,入队队列结束|ruleCode:" + dubheSerialNumberRule.getRuleCode() + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber() + "|numberStart:" + numberStart + "|numberEnd:" + numberEnd);/*2.3 起始值直接返回*/if (StringUtils.isBlank(cacheCurrentValue)) {return String.valueOf(numberStart);}return cacheCurrentValue;}/*** 字符串最大值** @param length* @return*/private int getMaxValueString(int length) {String value = "";for (int i = 0; i < length; i++) {value = "9" + value;}return Integer.parseInt(value);}}
