news 2026/2/22 8:48:58

Redis防重复点击与分布式锁实现方案对比笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis防重复点击与分布式锁实现方案对比笔记

一、核心概念辨析

1.1 业务场景本质

场景核心需求正确类比
防重复点击设置临时冷却标记,N秒内禁止重复操作计时器(N秒后自动解除)
分布式锁排他性资源访问,同一时间只允许一个线程操作互斥信号量(手动释放)

1.2 技术选型对比

组件抽象层次适用场景依赖
RedisTemplate底层命令操作防重复点击(推荐)Spring Data Redis
RedissonClient高级分布式对象分布式锁(推荐)、防重复点击(可用)Redisson

二、防重复点击实现方案

2.1 RedisTemplate实现(推荐⭐⭐⭐⭐⭐)

@Autowired private RedisTemplate<String, String> redisTemplate; /** * 防重复点击 - Redis标记方案 * @param key 业务唯一标识 * @param cooldownSeconds 冷却时间(秒) */ public void checkDuplicateRequest(String key, long cooldownSeconds) { Boolean success = redisTemplate.opsForValue() .setIfAbsent(key, "1", cooldownSeconds, TimeUnit.SECONDS); if (Boolean.FALSE.equals(success)) { Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS); throw new BusinessException(ttl + "秒内不可重复操作"); } } // 使用示例 public Result exportData(User user) { String key = "export:" + user.getId(); checkDuplicateRequest(key, 60L); // 60秒内禁止重复导出 // 执行导出逻辑... }

✅ 优点

  • 语义精准:SET NX EX 完美匹配"冷却"需求

  • 自动过期:无需手动清理

  • 性能最优:单次Redis操作

  • 无死锁风险

  • 与事务完美兼容

2.2 RedissonClient实现

@Autowired private RedissonClient redissonClient; /** * 防重复点击 - Redisson RBucket方案 * @param key 业务唯一标识 * @param cooldownSeconds 冷却时间(秒) */ public void checkDuplicateRequest(String key, long cooldownSeconds) { RBucket<String> bucket = redissonClient.getBucket(key); boolean success = bucket.trySet("1", cooldownSeconds, TimeUnit.SECONDS); if (!success) { long ttl = bucket.remainTimeToLive() / 1000; throw new BusinessException(ttl + "秒内不可重复操作"); } } // 使用示例 public Result exportData(User user) { String key = "export:" + user.getId(); checkDuplicateRequest(key, 60L); // 执行导出逻辑(注意:不要在finally中释放) }

⚠️ 注意:虽然可用,但Redisson的RBucket看门狗机制可能导致行为不可控,不推荐


三、分布式锁实现方案

3.1 典型场景(必须使用锁)

  • 库存扣减

  • 并发写同一文件

  • 分布式任务调度

  • 缓存重建防击穿

3.2 RedissonClient实现(推荐⭐⭐⭐⭐⭐)

@Autowired private RedissonClient redissonClient; /** * 分布式锁执行模板 * @param key 锁标识 * @param waitTime 获取锁最大等待时间(秒) * @param leaseTime 锁自动释放时间(秒) */ public <T> T executeWithLock(String key, long waitTime, long leaseTime, Supplier<T> businessLogic) { RLock lock = redissonClient.getLock(key); boolean isLocked = false; try { // 尝试获取锁 isLocked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS); if (!isLocked) { throw new BusinessException("获取锁失败,请稍后重试"); } // 执行业务逻辑 return businessLogic.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException("操作被中断"); } finally { // 必须手动释放 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } // 使用示例:库存扣减 public void deductStock(Long productId, int quantity) { String lockKey = "stock:" + productId; executeWithLock(lockKey, 3L, 10L, () -> { // 查询库存 int stock = getStockFromDB(productId); if (stock < quantity) { throw new BusinessException("库存不足"); } // 扣减库存 updateStock(productId, stock - quantity); return null; }); }

✅ 优点

  • 可重入锁:同一线程可多次获取

  • 看门狗机制:自动续期防死锁

  • 公平锁/非公平锁可选

  • 支持RedLock算法

3.3 RedisTemplate实现(不推荐)

// ❌ 不推荐:需自己处理死锁、续期、可重入等复杂逻辑 public boolean tryLock(String key, String value, long expireTime) { Boolean success = redisTemplate.opsForValue() .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); } public void unlock(String key, String value) { // 需用Lua脚本保证原子性判断和删除 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) else return 0 end"; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(key), value); }

四、关键对比总结

4.1 防重复点击 vs 分布式锁

维度防重复点击分布式锁
核心语义冷却计时器互斥访问
生命周期自动过期(无需手动)必须手动释放
性能极高(单次操作)较高(需竞争)
代码复杂度极低(3行)较高(try-finally)
事务兼容性✅ 完美⚠️ 需分离锁与事务
适用场景防重、限流资源竞争、排他操作

4.2 组件选型

需求场景RedisTemplateRedissonClient推荐理由
防重复点击⭐⭐⭐⭐⭐⭐⭐⭐Template语义更直接,无看门狗干扰
分布式锁⭐⭐⭐⭐⭐⭐⭐Redisson提供完整锁实现,无需造轮子
复杂数据结构⭐⭐⭐⭐⭐⭐⭐⭐Redisson封装了RMap、RQueue等高级对象

五、最佳实践建议

5.1 防重复点击(最终版)

@Service public class DuplicateCheckService { @Autowired private RedisTemplate<String, String> redisTemplate; /** * 通用防重检查 * @param bizType 业务类型(如:export、submit) * @param userId 用户ID * @param cooldown 冷却时间(秒) */ public void check(String bizType, Long userId, long cooldown) { String key = String.format("duplicate:%s:%d", bizType, userId); Boolean flag = redisTemplate.opsForValue() .setIfAbsent(key, "1", cooldown, TimeUnit.SECONDS); if (Boolean.FALSE.equals(flag)) { Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS); throw new BusinessException(String.format("操作太频繁,请%d秒后再试", ttl)); } } }

5.2 分布式锁(最终版)

@Service public class DistributedLockService { @Autowired private RedissonClient redissonClient; /** * 带锁执行业务逻辑 * @param lockKey 锁Key * @param businessLogic 业务逻辑(无返回值) */ public void execute(String lockKey, Runnable businessLogic) { execute(lockKey, 3L, 10L, () -> { businessLogic.run(); return null; }); } /** * 带锁执行业务逻辑(带返回值) */ public <T> T execute(String lockKey, long waitTime, long leaseTime, Supplier<T> businessLogic) { RLock lock = redissonClient.getLock(lockKey); try { if (!lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS)) { throw new BusinessException("系统繁忙,请稍后重试"); } return businessLogic.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException("操作中断"); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }

六、常见陷阱与避坑指南

❌ 陷阱1:用锁实现防重复点击

// 错误! lock.tryLock(0, 60, SECONDS); // 不释放 → 看门狗续期永不释放 // finally释放 → 锁无效

❌ 陷阱2:锁与事务范围错误

@Transactional public void method() { lock.lock(); // 事务提交前释放锁 → 脏读 // ... } // 正确:锁范围 > 事务范围

❌ 陷阱3:锁Key粒度错误

// 租户级Key(误锁所有用户) "export:" + tenantId // 用户级Key(正确) "export:" + userId

✅ 检查清单

  • [ ] 防重复点击用setIfAbsent+ 过期时间

  • [ ] 分布式锁必须try-finally释放

  • [ ] 锁范围必须大于事务范围

  • [ ] Key粒度确认是用户级而非租户级

  • [ ] RedisTemplate和Redisson不混用(除非必要)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/18 3:56:10

电池仓位置对重心影响的实验分析教程

电池仓位置如何“悄悄”改变小车性能&#xff1f;一次关于重心调控的实战探索你有没有遇到过这样的情况&#xff1a;明明代码写得没问题&#xff0c;PID参数也调得挺顺&#xff0c;可你的Arduino循迹小车一进弯就“推头”&#xff0c;直道跑着跑着突然甩尾脱轨&#xff1f;别急…

作者头像 李华
网站建设 2026/2/19 9:29:07

48、Windows系统实用工具与错误排查指南

Windows系统实用工具与错误排查指南 1. 系统信息实用工具 1.1 系统对象目录 在Windows系统中,有一些重要的系统对象目录,它们存储着各种关键信息: - \Sessions\n\BaseNamedObjects :在终端服务或快速用户切换(FUS)会话(由数字n标识)中运行的进程的本地命名空间中…

作者头像 李华
网站建设 2026/2/17 8:55:23

20、Windows Server 备份与恢复全攻略

Windows Server 备份与恢复全攻略 1. 恢复服务器的关键要点 在服务器恢复的操作中,有多种工具和方法可供选择,以下是一些关键信息的总结: - Windows Server Backup :可用于恢复除操作系统卷之外的文件、文件夹和卷。恢复文件时,有多种选择: - 恢复位置 :可选择原…

作者头像 李华
网站建设 2026/2/21 19:40:47

使用PSpice进行模拟电路仿真的操作指南

从零开始掌握PSpice&#xff1a;模拟电路仿真的实战心法你有没有过这样的经历&#xff1f;辛辛苦苦画完原理图&#xff0c;打样回来的PCB一通电&#xff0c;芯片发热、信号失真、电源振荡……问题千奇百怪&#xff0c;查来查去却像在“盲人摸象”。这几乎是每个硬件工程师都踩过…

作者头像 李华
网站建设 2026/2/21 17:50:31

61、系统行为理解与开发调试案例解析

系统行为理解与开发调试案例解析 在计算机系统的管理和开发过程中,我们常常会遇到各种难以解释的问题。本文将深入探讨NTLM通信分析以及开发者调试的相关案例,希望能为大家解决类似问题提供一些思路和方法。 NTLM通信分析 在Windows系统中,Kerberos是比NTLM更强大、更安全…

作者头像 李华
网站建设 2026/2/21 6:44:07

62、系统故障排查与性能优化全解析

系统故障排查与性能优化全解析 一、系统启动与调试相关问题 启动问题 启动配置数据库(BCD)磁盘签名 :磁盘签名在系统启动中起着关键作用,若出现磁盘签名冲突(403 - 408),可能导致系统无法正常启动。例如,在进行磁盘克隆或物理到虚拟转换(如使用 Disk2Vhd 工具)时…

作者头像 李华