news 2026/4/18 7:54:32

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/4/17 15:27:19

Redis防重复点击与分布式锁

在生产环境中&#xff0c;我们经常会遇到两个需求&#xff1a;限制用户在N秒内不能重复操作&#xff08;如连续点击导出按钮&#xff09;确保同一时间只有一个线程能操作共享资源&#xff08;如扣减库存&#xff09;很多开发者习惯用Redisson的RLock来解决这两个问题&#xff0…

作者头像 李华
网站建设 2026/4/18 2:15:37

亲测2025年主流AI漫剧工具:帮作者打通小说转漫剧链路

我是一名写了十多年网文的作家&#xff0c;今年打算尝试一下小说转漫剧&#xff0c;一段时间下来&#xff0c;我可真的懂文字转化为漫剧的痛了。分镜的逻辑太混乱了&#xff0c;画风和人设也不匹配&#xff0c;手动来回调整场景花费的时间太长&#xff0c;尤其是我把小说《古今…

作者头像 李华
网站建设 2026/4/13 11:52:48

mybatis中达梦数据库的属性设置

<dataSource type"POOLED"><!-- 达梦数据库驱动 --><property name"driver" value"dm.jdbc.driver.DmDriver"/><!-- 连接URL格式 : jdbc:dm://主机名:端口号/数据库名--><property name"url" value"…

作者头像 李华
网站建设 2026/4/16 11:50:56

21、服务器备份与恢复操作指南

服务器备份与恢复操作指南 1. 系统镜像恢复操作 1.1 恢复步骤 以下是恢复系统镜像的具体步骤: 1. 在“选择要还原的系统镜像日期和时间”页面,选择最近可用的选项,连续点击两次“下一步”,然后点击“完成”。 2. 当出现数据将被替换的警告提示时,点击“是”。 3. 恢…

作者头像 李华
网站建设 2026/4/7 2:40:32

56、震网病毒(Stuxnet)感染分析与PNF文件探究

震网病毒(Stuxnet)感染分析与PNF文件探究 1. 震网病毒初步探查 在系统进程的Procexp DLL视图中,能看到两个Mrx驱动。它们的版本信息显示来自微软,但签名却来自瑞昱(Realtek),且证书已被撤销,不过因测试系统未联网,Procexp无法查询证书撤销列表服务器。 借助Autorun…

作者头像 李华
网站建设 2026/4/18 6:32:23

57、恶意软件分析与处理:多种案例深度剖析

恶意软件分析与处理:多种案例深度剖析 1. 震网(Stuxnet)病毒提权分析 震网病毒在感染系统时,许多操作如感染 Services.exe 等系统进程、安装设备驱动程序等都需要管理员权限。为了在标准用户账户下获取管理员权限,震网利用了两个零日漏洞: - Windows XP 和 Windows…

作者头像 李华