1. CREATE TABLE `crl_serial_number_rule` (
    2. `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
    3. `rule_code` varchar(30) NOT NULL COMMENT '规则编码',
    4. `rule_desc` varchar(60) NOT NULL COMMENT '规则描述',
    5. `prefix` varchar(10) NULL COMMENT '前缀',
    6. `date_formatter` varchar(20) NULL COMMENT '日期格式',
    7. `random_mark` varchar(10) NOT NULL COMMENT '随机位',
    8. `number_type` varchar(10) NOT NULL COMMENT '序号类型,numberTypeEnum:RANDOM 随机;ORDER 顺序',
    9. `number_length` varchar(2) NOT NULL COMMENT '序号位数',
    10. `number_start` varchar(10) DEFAULT NULL COMMENT '序号起始位',
    11. `number_end` varchar(10) DEFAULT NULL COMMENT '序号结束位',
    12. `current_number` varchar(10) DEFAULT NULL COMMENT '当前序号',
    13. `created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    14. `modified_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    15. PRIMARY KEY (`id`),
    16. UNIQUE INDEX `uniq_idx_rule_code` (`rule_code`)
    17. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='流水号规则表';

    1、首先获取一次缓存中的序列号,不要直接加分布式锁,毕竟遇到查不到的情况约1000分之一
    2、流水号大约1秒多生成,建议超时时长设置为2-3秒,重试次数可以多次,每次间隔500ms即可。间隔过大,会导致撞锁。间隔太小会导致无用的重试次数太多。
    3、一次操作时间《加锁的重试次数间隔时间<两次操作时间。理由是:若重试的次数间隔时间比一次操作时间短,则会导致加锁失败。若大于两次操作时间,则造成浪费。
    4、入队的方式,除了执行一个个命令外,rpush是支持把数组一次性存入缓存的

    1. import org.apache.commons.lang3.StringUtils;
    2. import org.springframework.stereotype.Service;
    3. import javax.annotation.Resource;
    4. import java.text.SimpleDateFormat;
    5. import java.util.Date;
    6. import java.util.Random;
    7. import java.util.concurrent.TimeUnit;
    8. /**
    9. * @description:
    10. * @author: wzh
    11. * @date: 2019-01-21 上午10:44
    12. */
    13. @Service("dubheSerialNumberCenterServiceImpl")
    14. public class DubheSerialNumberCenterServiceImpl implements DubheSerialNumberCenterService {
    15. private static final Logger logger = LoggerFactory.getLogger(DubheSerialNumberCenterServiceImpl.class);
    16. /*超时时间:代码中与TimeUnit.SECONDS共用*/
    17. private static long expireTime = 3;
    18. /*重试次数*/
    19. private static int retryCount = 10;
    20. @Resource
    21. private DubheSerialNumberRuleService dubheSerialNumberRuleService;
    22. @Resource
    23. private DistributedLocker distributedLocker;
    24. @Resource
    25. private R2mClusterClient r2mClusterClient;
    26. @Override
    27. public CommonResponse<String> generateSerialNumber(CommonRequest<SerialNumberRequest> req) {
    28. String methodDesc = "DubheSerialNumberCenterServiceImpl|generateSerialNumber|按规则编码生成流水号|";
    29. SerialNumberRequest serialNumberRequest = req.getRequestData();
    30. String ruleCode = serialNumberRequest.getRuleCode();
    31. StringBuilder crlSerialNumber = new StringBuilder();
    32. logger.info(methodDesc + "查询流水号规则开始" + "|ruleCode:" + ruleCode);
    33. DubheSerialNumberRule dubheSerialNumberRule = dubheSerialNumberRuleService.getSerialNumberRuleByParams(serialNumberRequest.getRuleCode());
    34. if (null == dubheSerialNumberRule) {
    35. logger.warn(methodDesc + "查询流水号规则为空,请检查流水号规则配置" + "|ruleCode:" + ruleCode);
    36. 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();
    37. return CommonResponse.buildResponse(req, RespCodeEnum.SERIAL_NO_RULE_NOT_EXIST, crlSerialNumber.toString());
    38. }
    39. logger.info(methodDesc +"查询流水号规则结束" + "|ruleCode:" + ruleCode + "|dubheSerialNumberRule:" + dubheSerialNumberRule);
    40. String dateFormatter = dubheSerialNumberRule.getDateFormatter();
    41. String dateString = "";
    42. if (StringUtils.isNotBlank(dateFormatter)) {
    43. dateString = new SimpleDateFormat(dateFormatter).format(new Date());
    44. }
    45. String randomMark = "";
    46. if (0<dubheSerialNumberRule.getRandomMark()) {
    47. int maxNumber=getMaxValueString(dubheSerialNumberRule.getRandomMark());
    48. randomMark = format(dubheSerialNumberRule.getRandomMark(),new Random().nextInt(maxNumber));
    49. }
    50. int random = new Random().nextInt(getMaxValueString(dubheSerialNumberRule.getNumberLength()));
    51. if (NumberTypeEnum.RANDOM.getCode().equals(dubheSerialNumberRule.getNumberType())) {
    52. crlSerialNumber.append(dubheSerialNumberRule.getPrefix()).append(dateString).append(randomMark).append(format(dubheSerialNumberRule.getNumberLength(), random)).toString();
    53. return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());
    54. } else if (NumberTypeEnum.ORDER.getCode().equals(dubheSerialNumberRule.getNumberType())) {
    55. /*拼接前缀+时间长度+随机位数*/
    56. crlSerialNumber.append(dubheSerialNumberRule.getPrefix()).append(dateString).append(randomMark);
    57. int increase = dubheSerialNumberRule.getQueueNumber();
    58. String cacheKey=dubheSerialNumberRule.getPrefix() + dateFormatter + dubheSerialNumberRule.getNumberLength();
    59. String redisKey="";
    60. try {
    61. logger.info(methodDesc + "|获取当前key的value值开始" + "|ruleCode:" + ruleCode + "|cacheKey:" + cacheKey);
    62. String cacheCurrentValue = r2mClusterClient.rpop(cacheKey);
    63. if(StringUtils.isNotBlank(cacheCurrentValue)){
    64. logger.info(methodDesc + "|获取当前key的value值|出参|cacheCurrentValue:",cacheCurrentValue,"|入参|cacheKey:" + cacheKey + "|ruleCode:" + ruleCode);
    65. crlSerialNumber.append(format(dubheSerialNumberRule.getNumberLength(), Integer.parseInt(cacheCurrentValue))).toString();
    66. logger.info(methodDesc + "|返回流水号|出参|crlSerialNumber:",crlSerialNumber.toString(),"|入参|ruleCode:" + ruleCode);
    67. return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());
    68. }
    69. logger.info(methodDesc + "|获取当前key的value值不存在|出参|cacheCurrentValue:",cacheCurrentValue,"|入参|cacheKey:" + cacheKey + "|ruleCode:" + ruleCode);
    70. /*1 未能从缓存查询,先加锁*/
    71. logger.info(methodDesc + "|加分布式锁开始" + "|ruleCode:" + ruleCode);
    72. CommonResponse<String> crlCommonResponse = distributedLocker.lockRetry(ruleCode, expireTime, TimeUnit.SECONDS, retryCount);
    73. if (!crlCommonResponse.isSuccess()) {
    74. throw new BizException(RespCodeEnum.SERIAL_NO_LOCK_FAIL);
    75. }
    76. redisKey=crlCommonResponse.getResponseData();
    77. logger.info(methodDesc + "|加分布式锁结束" + "|ruleCode:" + ruleCode + "|crlCommonResponse:" + crlCommonResponse);
    78. /*2 再获取一次缓存*/
    79. logger.info(methodDesc + "|获取当前key的value值开始" + "|ruleCode:" + ruleCode + "|cacheKey:" + cacheKey);
    80. cacheCurrentValue = r2mClusterClient.rpop(cacheKey);
    81. if (StringUtils.isNotBlank(cacheCurrentValue)) {
    82. logger.info(methodDesc + "|获取当前key的value值|出参|cacheCurrentValue:", cacheCurrentValue, "|入参|cacheKey:" + cacheKey + "|ruleCode:" + ruleCode);
    83. crlSerialNumber.append(format(dubheSerialNumberRule.getNumberLength(), Integer.parseInt(cacheCurrentValue))).toString();
    84. logger.info(methodDesc + "|返回流水号|出参|crlSerialNumber:", crlSerialNumber.toString(), "|入参|ruleCode:" + ruleCode);
    85. return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());
    86. }
    87. logger.info(methodDesc + "|获取当前key的value值不存在|出参|cacheCurrentValue:",cacheCurrentValue,"|入参|cacheKey:" + cacheKey + "|ruleCode:" + ruleCode);
    88. /*3 处理起始值与结束值*/
    89. int numberStart;
    90. int numberEnd;
    91. if (StringUtils.isBlank(dubheSerialNumberRule.getCurrentNumber())) {
    92. logger.info(methodDesc + "|当前缓存值不存在|数据库当前值不存在|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());
    93. /*3.1 数据库当前值不存在处理*/
    94. numberStart = dubheSerialNumberRule.getNumberStart();
    95. numberEnd = numberStart + increase;
    96. /*3.1.1 结束值处理*/
    97. if (numberEnd > dubheSerialNumberRule.getNumberEnd()) {
    98. logger.info(methodDesc + "|当前缓存值不存在|流水号限制了结束值,比队列结束值小,设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());
    99. numberEnd = dubheSerialNumberRule.getNumberEnd();
    100. } else {
    101. logger.info(methodDesc + "|当前缓存值不存在|流水号未限制结束值,比队列结束值大,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());
    102. }
    103. } else {
    104. logger.info(methodDesc + "|当前缓存值不存在|数据库当前值存在|ruleCode:" + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());
    105. Integer currentNumber = Integer.valueOf(dubheSerialNumberRule.getCurrentNumber());
    106. /*3.2 数据库当前值存在处理*/
    107. numberStart = 1 + currentNumber;
    108. numberEnd = 1 + currentNumber + increase;
    109. /*3.2.1 当前值与流水号起始值*/
    110. if (currentNumber < dubheSerialNumberRule.getNumberStart()) {
    111. logger.info(methodDesc + "|当前缓存值不存在|配置了流水号区间|可能由于修改了规则导致数据库当前值比左区间小,缓存按起始值set|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|currentNumber:" + currentNumber + "|numberStart:" + dubheSerialNumberRule.getNumberStart());
    112. numberStart = dubheSerialNumberRule.getNumberStart();
    113. numberEnd = numberStart + increase;
    114. }
    115. /*3.2.2 当前值与流水号结束值*/
    116. else if (currentNumber >= dubheSerialNumberRule.getNumberEnd()) {
    117. logger.info(methodDesc + "|当前缓存值不存在|流水号限制了结束值|数据库当前值达到位数最大值,设置1为队列起始值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|currentNumber:" + currentNumber);
    118. numberStart = dubheSerialNumberRule.getNumberStart();
    119. numberEnd = numberStart + increase;
    120. }
    121. /*3.2.3 队列结束值与流水号结束值*/
    122. if (numberEnd > dubheSerialNumberRule.getNumberEnd()) {
    123. logger.info(methodDesc + "|当前缓存值不存在|配置了流水号区间,比队列结束值小,则设置为队列结束值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());
    124. numberEnd = dubheSerialNumberRule.getNumberEnd();
    125. } else {
    126. logger.info(methodDesc + "|当前缓存值不存在|配置了流水号区间,比队列结束值大,设置队列起始值+99为队列结束值|ruleCode:" + ruleCode + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber());
    127. }
    128. }
    129. /*4 入队列并更新数据库*/
    130. cacheCurrentValue = updateAndPushAndPop(dubheSerialNumberRule, cacheKey, cacheCurrentValue, numberStart, numberEnd);
    131. crlSerialNumber.append(format(dubheSerialNumberRule.getNumberLength(), Integer.parseInt(cacheCurrentValue))).toString();
    132. /*5 返回流水号*/
    133. logger.info(methodDesc + "|返回流水号|出参|crlSerialNumber:", crlSerialNumber.toString(), "|入参|ruleCode:" + ruleCode);
    134. return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());
    135. } catch (Exception e) {
    136. logger.error(methodDesc + "异常|入参|ruleCode:" + ruleCode,"|exception:",e);
    137. crlSerialNumber.append(dubheSerialNumberRule.getPrefix()).append(dateString).append(9).append(format(dubheSerialNumberRule.getNumberLength(),random)).toString();
    138. return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());
    139. }finally {
    140. if(StringUtils.isNotBlank(redisKey)){
    141. logger.info(methodDesc + "|释放分布式锁开始" + "|ruleCode:" + ruleCode + "|redisKey:" + redisKey);
    142. distributedLocker.unlock(ruleCode, redisKey);
    143. logger.info(methodDesc + "|释放分布式锁结束" + "|ruleCode:" + ruleCode + "|redisKey:" + redisKey);
    144. }
    145. }
    146. }
    147. return CommonResponse.buildResponse(req, RespCodeEnum.SUCCESS, crlSerialNumber.toString());
    148. }
    149. /**
    150. * String.format效率较低
    151. * @param length
    152. * @param data
    153. * @return
    154. */
    155. private String format(int length, int data) {
    156. char[] result = new char[length];
    157. for(int i=result.length-1;i>=0;i--) {
    158. result[i] = (char) (data % 10 + 48);
    159. data/=10;
    160. }
    161. return new String(result);
    162. }
    163. /**
    164. * 更新数据库当前值、队列入队处理、队列出队获取
    165. *
    166. * @param dubheSerialNumberRule
    167. * @param cacheKey
    168. * @param cacheCurrentValue
    169. * @param numberStart
    170. * @param numberEnd
    171. * @return
    172. * @throws Exception
    173. */
    174. private String updateAndPushAndPop(DubheSerialNumberRule dubheSerialNumberRule, String cacheKey, String cacheCurrentValue, Integer numberStart, Integer numberEnd) throws Exception {
    175. String methodDesc = "按规则编码生成流水号";
    176. /*2.1 更新数据库当前值为队列结束值*/
    177. logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|更新流水号规则中当前值开始" + "|ruleCode:" + dubheSerialNumberRule.getRuleCode() + "|numberEnd:" + numberEnd);
    178. int updateNum = dubheSerialNumberRuleService.updateCurrentNumberByParams(String.valueOf(numberEnd), dubheSerialNumberRule.getRuleCode());
    179. if (updateNum <= 0) {
    180. throw new Exception(methodDesc + "更新流水号规则中当前值失败");
    181. }
    182. logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|更新流水号规则中当前值结束" + "|ruleCode:" + dubheSerialNumberRule.getRuleCode() + "|numberEnd:" + numberEnd + "|updateNum:" + updateNum);
    183. /*2.2 队列入队开始*/
    184. logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了起始值,入队队列开始|ruleCode:" + dubheSerialNumberRule.getRuleCode() + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber() + "|numberStart:" + numberStart + "|numberEnd:" + numberEnd);
    185. String[] info = new String[numberEnd - numberStart];
    186. logger.info("缓存入队:start{},end{}",numberStart+1,numberEnd);
    187. for (int i = 0; i < info.length; i++) {
    188. info[i] =format(dubheSerialNumberRule.getNumberLength(), i + numberStart + 1);
    189. }
    190. r2mClusterClient.lpush(cacheKey, info);
    191. logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|当前缓存值不存在|流水号限制了起始值,入队队列结束|ruleCode:" + dubheSerialNumberRule.getRuleCode() + "|prefix:" + dubheSerialNumberRule.getPrefix() + "|length:" + dubheSerialNumberRule.getNumberLength() + "|cacheCurrentValue:" + cacheCurrentValue + "|currentNumber:" + dubheSerialNumberRule.getCurrentNumber() + "|numberStart:" + numberStart + "|numberEnd:" + numberEnd);
    192. /*2.3 起始值直接返回*/
    193. if (StringUtils.isBlank(cacheCurrentValue)) {
    194. return String.valueOf(numberStart);
    195. }
    196. return cacheCurrentValue;
    197. }
    198. /**
    199. * 字符串最大值
    200. *
    201. * @param length
    202. * @return
    203. */
    204. private int getMaxValueString(int length) {
    205. String value = "";
    206. for (int i = 0; i < length; i++) {
    207. value = "9" + value;
    208. }
    209. return Integer.parseInt(value);
    210. }
    211. }