news 2026/4/17 15:27:19

Redis防重复点击与分布式锁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis防重复点击与分布式锁

在生产环境中,我们经常会遇到两个需求:

  1. 限制用户在N秒内不能重复操作(如连续点击导出按钮)

  2. 确保同一时间只有一个线程能操作共享资源(如扣减库存)

很多开发者习惯用Redisson的RLock来解决这两个问题,但这其实是一种语义错位。今天我们来聊聊为什么"防重复点击"不应该用分布式锁。


一、防重复点击:设置一个"冷却标记"

1.1 业务本质

防重复点击的核心需求是:在用户操作后,设置一个N秒后自动消失的"冷却标记"。这个时间与业务执行时长无关,纯粹是业务规则限制。

1.2 正确实现(RedisTemplate)

@Service public class DuplicateCheckService { @Autowired private RedisTemplate<String, String> redisTemplate; /** * 防重复点击检查 * @param bizType 业务类型(如export、submit) * @param userId 用户ID * @param cooldown 冷却时间(秒) */ public void checkDuplicate(String bizType, Long userId, long cooldown) { String key = String.format("duplicate:%s:%d", bizType, userId); // 核心:SET NX EX - 不存在才设置,并自动过期 Boolean success = redisTemplate.opsForValue() .setIfAbsent(key, "1", cooldown, TimeUnit.SECONDS); if (Boolean.FALSE.equals(success)) { Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS); throw new BusinessException(ttl + "秒内不可重复操作"); } } }

关键点

  • setIfAbsent= Redis的SET NX命令,原子性判断+设置

  • 自动过期:Redis会在cooldown秒后删除Key,无需手动清理

  • 无锁竞争:失败时直接返回,不等待

1.3 使用示例

@Transactional public Result exportData(ExportRequest request, User user) { // 检查60秒内是否重复点击 duplicateCheckService.checkDuplicate("export", user.getId(), 60); // 执行导出逻辑(在事务内) return doExport(request); }

二、分布式锁:确保"排他性访问"

2.1 业务本质

分布式锁的核心需求是:保护共享资源,确保同一时间只有一个线程能修改它。必须手动释放锁,否则会造成死锁。

2.2 正确实现(Redisson)

@Service public class DistributedLockService { @Autowired private RedissonClient redissonClient; /** * 带锁执行业务逻辑 * @param lockKey 锁标识 * @param waitTime 获取锁最大等待时间(秒) * @param leaseTime 锁自动释放时间(秒,防死锁) */ public void executeWithLock(String lockKey, long waitTime, long leaseTime, Runnable businessLogic) { RLock lock = redissonClient.getLock(lockKey); try { // 尝试获取锁(可重入) if (!lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS)) { throw new BusinessException("获取锁失败,请稍后重试"); } // 执行业务逻辑 businessLogic.run(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException("操作被中断"); } finally { // ⚠️ 必须手动释放! if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }

关键点

  • tryLock:尝试获取,失败时等待

  • 手动释放:必须在finallyunlock(),否则死锁

  • 看门狗:未指定leaseTime时会自动续期

2.3 使用示例

public void deductStock(Long productId, int quantity) { String lockKey = "stock:" + productId; // 保护库存扣减操作 lockService.executeWithLock(lockKey, 3, 10, () -> { int stock = getStockFromDB(productId); if (stock < quantity) { throw new BusinessException("库存不足"); } updateStock(productId, stock - quantity); }); }

三、核心区别对比

对比维度防重复点击分布式锁
业务语义冷却计时器(N秒后自动解除)互斥信号(必须手动释放)
Redis命令SET key value NX EX secondsSET key value+ 续期+手动DEL
生命周期自动过期(与业务无关)手动释放(与业务强相关)
失败策略直接拒绝(不等待)可选等待或失败
性能极高(单次O(1)操作)较高(有竞争开销)
代码复杂度极低(3行代码)较高(try-finally+异常处理)
事务兼容性✅ 完美兼容(无状态)⚠️ 需分离锁与事务
适用场景防重、限流、短信冷却库存扣减、并发写文件

四、致命误区:用锁实现防重复点击

❌ 错误代码(最常见)

// 误区1:finally立即释放(锁无效) @Transactional public Result export() { lock.tryLock(0, 60, TimeUnit.SECONDS); try { return doExport(); } finally { lock.unlock(); // ⚠️ 业务还没结束,锁就没了 } } // 误区2:不释放等过期(用户体验差) public Result export() { lock.tryLock(0, 60, TimeUnit.SECONDS); return doExport(); // ⚠️ 导出5秒完成,用户必须等55秒 } // 误区3:与事务冲突 @Transactional public Result export() { lock.lock(); // 事务提交前释放锁 → 脏读 // 事务回滚后释放锁 → 锁已失效 return doExport(); }

问题根源:分布式锁的生命周期必须人为控制,而防重复点击需要的是"设置后不管"的临时标记。


五、决策指南:何时用哪个?

5.1 选择流程图

需求:限制操作频率 ↓ 是"限制同一用户N秒内不能重复操作"? ↓ 是 使用 RedisTemplate.setIfAbsent()(防重复点击) ↓ 否 → 是"保护共享资源,防止并发修改"? ↓ 是 使用 Redisson RLock(分布式锁) ↓ 否 → 其他方案(如限流器RateLimiter)

5.2 一句话总结

当你想"限制用户在N秒内不能操作"时,用带过期时间的标记;当你想"确保只有一个线程能操作"时,才用分布式锁。


六、生产环境最佳实践

6.1 Key设计规范

// 防重复点击:用户级粒度 String key = "duplicate:export:" + userId; // 分布式锁:资源级粒度 String key = "lock:stock:" + productId;

6.2 冷却时间设置建议

  • 导出类:60-300秒(防止频繁生成大文件)

  • 提交类:5-10秒(防止表单重复提交)

  • 短信类:60秒(运营商普遍限制)

6.3 锁时长设置建议

  • leaseTime:必须大于业务最大执行时间

  • waitTime:根据业务容忍度设置,避免长时间阻塞


七、总结

防重复点击和分布式锁是两种完全不同的语义,但开发者常因"都用到Redis"而混用。记住:

  • 防重复点击 = 冷却计时器:用SET NX EX,自动过期,无需释放

  • 分布式锁 = 互斥信号:用Redisson RLock,手动释放,保护资源

选错工具不仅代码复杂,还会引入死锁、性能下降、用户体验差等隐患。希望这篇文章能帮你避开这个90%开发者都踩过的坑。

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

作者头像 李华
网站建设 2026/4/17 7:31:55

60、系统行为分析与故障排除案例解析

系统行为分析与故障排除案例解析 在当今数字化时代,计算机系统的稳定性和安全性至关重要。然而,系统中常常会出现一些难以解释的问题,如网络连接异常、进程资源消耗不明等。下面我们将通过几个实际案例,深入探讨如何运用专业工具进行系统行为分析和故障排除。 无法解释的…

作者头像 李华