news 2026/7/27 18:49:08

java使用Redison自旋锁和mysql生成唯一编号

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
java使用Redison自旋锁和mysql生成唯一编号

1. 数据库表设计(存储递增基准值)

CREATE TABLE `t_sequence` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `type_key` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '关键字段', `rule_date` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规则日期', `max_number` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '自增序列号', `version` bigint(20) DEFAULT NULL COMMENT '版本号', `deleted` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '删除状态:0-正常 1-删除', `create_user_id` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_user_id` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='序列表';

2、生成实体

@Getter @Setter @Accessors(chain = true) @TableName("t_sequence") @ApiModel(value = "TSequence对象", description = "序列表") public class TSequence implements Serializable { @ApiModelProperty("主键ID") @TableId(value = "id", type = IdType.AUTO) private Long id; @ApiModelProperty("关键字段") private String typeKey; @ApiModelProperty("规则日期") private String ruleDate; @ApiModelProperty("对应某年的自增序列号") private String maxNumber; @ApiModelProperty("版本号") private Long version; @ApiModelProperty("创建人ID") private Long createUserId; @ApiModelProperty("创建时间") private LocalDateTime createTime; @ApiModelProperty("更新人ID") private Long updateUserId; @ApiModelProperty("更新时间") private LocalDateTime updateTime; @ApiModelProperty("是否删除:0-未删除 1-已删除") private String deleted; }

3、实现业务

@Component @AllArgsConstructor @Log4j2 public class TSequenceService { private final RedissonClient redissonClient; private final TSequenceMapper sequenceMapper; private static final String ORDER_SEQ_LOCK_KEY = "seq:lock:";//锁前缀 private static final int MAX_RETRY_TIMES = 10;//自旋次数 private static final long RETRY_INTERVAL = 200;//自旋一次等待时间 private static final long LOCK_EXPIRE_SECONDS = 30;//锁等待时间 /** * 生成唯一编码 * @param prefix :前缀 * @param typeKey :业务类型 * @param currentValue :自增数据 * @return */ @Transactional(rollbackFor = Exception.class) public String publicCreateNumber(String prefix,String typeKey,String currentValue) { String lockKey = ORDER_SEQ_LOCK_KEY + typeKey; int retryCount = 0; // 锁对象移到循环外,避免重复创建 RLock lock = redissonClient.getLock(lockKey); // 自旋获取锁 while (retryCount < MAX_RETRY_TIMES) { try { // 尝试获取锁(非阻塞) boolean locked = lock.tryLock(0, LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS); if (locked) { try { // 加锁成功后执行核心逻辑 return doCreateNumber(prefix,typeKey,currentValue); } finally { // 释放锁(确保当前线程持有锁) if (lock.isHeldByCurrentThread()) { lock.unlock(); log.info("释放分布式锁成功,lockKey:{}", lockKey); } } } else { // 自旋等待 retryCount++; log.warn("第{}次获取锁失败,等待{}ms后重试,lockKey:{}", retryCount, RETRY_INTERVAL, lockKey); TimeUnit.MILLISECONDS.sleep(RETRY_INTERVAL); } } catch (InterruptedException e) { log.error("自旋等待时被中断", e); Thread.currentThread().interrupt(); throw new RuntimeException("生成编号失败:线程被中断"); } catch (Exception e) { log.error("获取/释放锁异常", e); throw new RuntimeException("生成编号失败:锁操作异常", e); } } log.error("生成编号失败:超出最大自旋重试次数({}次),lockKey:{}", MAX_RETRY_TIMES, lockKey); throw new RuntimeException("业务繁忙,请稍后尝试"); } /** * 加锁后的核心业务逻辑 * @param prefix :前缀 * @param typeKey :业务类型 * @param currentValue :自增数据 * @return */ public String doCreateNumber(String prefix,String typeKey,String currentValue) { String yearMonth = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));//获取日期 long newVersion; int nextNumber; String number; //查询数据库序列数据,悲观锁查询,锁定行 TSequence tSequence = sequenceMapper.selectOne(new LambdaQueryWrapper<TSequence>() .eq(TSequence::getDeleted, DeletedEnum.DEFAULT.getValue()) .eq(TSequence::getTypeKey, typeKey) .eq(TSequence::getRuleDate, yearMonth) .last("FOR UPDATE")); if (ObjectUtil.isNull(tSequence)) { // 初始化编号 nextNumber = Integer.parseInt(currentValue)+ 1; // 使用 DecimalFormat 保持前导零 DecimalFormat df = new DecimalFormat(currentValue); String index = df.format(nextNumber); number = prefix+yearMonth+index;//拼接新编号 // 插入新记录 TSequence newSequence = new TSequence(); newSequence.setTypeKey(typeKey); newSequence.setMaxNumber(nextNumber); newSequence.setVersion(1L); newSequence.setRuleDate(yearMonth); newSequence.setCreateTime(LocalDateTime.now()); int insert = sequenceMapper.insert(newSequence); if (insert != 1) { throw new RuntimeException("初始化序列失败,enumValue:" + typeKey); } log.info("初始化序列成功,编号:{}", number); } else { // 已有序列,递增编号 String maxNumber = tSequence.getMaxNumber(); nextNumber = Integer.parseInt(tSequence.getMaxNumber())+ 1; // 使用 DecimalFormat 保持前导零 DecimalFormat df = new DecimalFormat(currentValue); String index = df.format(nextNumber); number = prefix+yearMonth+index;//拼接新编号 if (ObjectUtil.isEmpty(number)) { throw new RuntimeException("递增编号失败,当前最大编号:" + maxNumber); } // 乐观锁更新:校验旧版本号,设置新版本号 Long oldVersion = tSequence.getVersion(); newVersion = oldVersion + 1; LambdaUpdateWrapper<TSequence> updateWrapper = new LambdaUpdateWrapper<TSequence>() .eq(TSequence::getTypeKey, typeKey) .eq(TSequence::getRuleDate, yearMonth) .eq(TSequence::getMaxNumber, maxNumber) // 校验旧编号 .eq(TSequence::getVersion, oldVersion) // 校验旧版本号(核心修复) .set(TSequence::getVersion, newVersion) // 设置新版本号 .set(TSequence::getMaxNumber, nextNumber) .set(TSequence::getUpdateTime, LocalDateTime.now()); // 执行更新并校验结果 int update = sequenceMapper.update(null, updateWrapper); if (update != 1) { throw new RuntimeException("更新序列失败,并发冲突!enumValue:" + typeKey + ", 当前编号:" + maxNumber); } log.info("更新序列成功,旧编号:{},新编号:{}", maxNumber, number); } return number; } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/26 23:06:30

AI元人文构想:人机协作的“未来哪吒”架构

AI元人文构想&#xff1a;人机协作的“未来哪吒”架构——悟空来路与关山&#xff0c;提供了从哲学到实践的完整跃迁路径作者&#xff1a;岐金兰与她的手机AI们摘要本文从“余溪诗学空间”出发&#xff0c;系统评述了岐金兰提出的“AI元人文构想”。该构想以价值原语化为哲学内…

作者头像 李华
网站建设 2026/7/26 18:07:08

Qwen-Image:突破文本渲染与编辑的视觉生成模型

Qwen-Image&#xff1a;突破文本渲染与编辑的视觉生成模型 在当前AIGC技术飞速演进的背景下&#xff0c;图像生成早已不再局限于“能否画出一张像样的图”。真正的挑战在于——能否精准地将复杂语义转化为高保真、可编辑、结构一致的视觉内容。尤其是在中文场景下&#xff0c;…

作者头像 李华
网站建设 2026/7/27 0:29:05

四年累销600万,这就是“海洋速度”

今晚这场四周年盛典&#xff0c;比亚迪海洋网用数据直接“炸场”了&#xff01; 四年干了啥&#xff1f; 一句话&#xff1a;销量飙出“海洋速度”&#xff01;成立四年&#xff0c;累计销量600万辆&#xff0c;赶上了别人十年的路。宋PLUS、海鸥、海豚、海豹&#xff0c;个个…

作者头像 李华
网站建设 2026/7/26 19:24:37

Keras与TensorFlow-GPU配置避坑指南

Keras与TensorFlow-GPU配置避坑指南 在深度学习项目中&#xff0c;训练速度是决定迭代效率的关键。当你面对一个包含百万级参数的模型时&#xff0c;CPU上几小时的等待几乎无法忍受——而GPU的并行计算能力可以将这个时间缩短到几分钟。但理想很丰满&#xff0c;现实却常常骨感…

作者头像 李华
网站建设 2026/7/27 6:02:46

Redis + ThinkPHP 实战学习手册(含秒杀场景)

Redis ThinkPHP 实战学习手册&#xff08;含秒杀场景&#xff09; 目录基础准备&#xff1a;ThinkPHP 集成 RedisRedis 核心数据结构&#xff08;ThinkPHP 用法&#xff09;秒杀场景核心&#xff1a;Redis 原子性与事务ThinkPHP Redis 实战场景&#xff08;秒杀 / 缓存 / 限流…

作者头像 李华
网站建设 2026/7/27 0:30:31

展锐平台:修复使用触控笔主动橡皮擦键功能时, InputDispatcher 发生了致命错误(F 级别),导致 system_server 进程崩溃并重启。

接上篇文章&#xff1a;展锐平台&#xff1a;记录一次带触控笔的MIPI屏幕调试踩坑过程&#xff0c;在使用触控笔的主动橡皮擦功能时&#xff0c;系统会出现类似重启的现象。之所以说是类似重启&#xff0c;是因为从log中看&#xff0c;整个系统是还在运行的&#xff0c;只是某些…

作者头像 李华