news 2026/3/28 3:57:04

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/3/22 22:55:52

43、Windows文件与磁盘实用工具全解析

Windows文件与磁盘实用工具全解析 1. 文件实用工具 1.1 流(Streams) NTFS 允许文件和目录拥有替代数据流(ADSes)。默认情况下,文件没有 ADSes,其内容存储在主无名流中。可以使用 filename:streamname 语法读写替代流。 例如,创建一个与 test.txt 文件关联的名为…

作者头像 李华
网站建设 2026/3/27 16:14:04

22、Windows Server 2012:备份恢复与高级文件服务指南

Windows Server 2012:备份恢复与高级文件服务指南 1. 备份与恢复相关 1.1 备份工具选择 在Windows Server环境中,有多种备份工具可供选择,不同工具适用于不同的备份需求: | 工具名称 | 功能描述 | 是否适用于特定备份类型 | | ---- | ---- | ---- | | Windows Server …

作者头像 李华
网站建设 2026/3/16 6:20:45

23、高级文件服务与存储技术详解

高级文件服务与存储技术详解 1. 高级文件服务 在当今的企业环境中,高效的文件服务和存储管理至关重要。以下将详细介绍一些关键的高级文件服务技术。 1.1 BranchCache BranchCache 允许分支机构的客户端在本地对等缓存或主机缓存中缓存从远程办公室服务器检索的文件共享文…

作者头像 李华
网站建设 2026/3/27 7:44:48

26、Windows Server 2012 高可用性集群与负载均衡技术解析

Windows Server 2012 高可用性集群与负载均衡技术解析 1. 集群技术的发展与现状 在过去,为确保 Exchange 和 SQL 等工作负载的高可用性,我们通常采用群集的方式部署它们。然而,如今这些产品自身已经具备了无需部署在故障转移集群上就能实现高可用性的技术,例如 AlwaysOn 可…

作者头像 李华
网站建设 2026/3/27 19:42:55

高频信号篇---电容与电感

第一部分&#xff1a;电容——电路中的“水库”与“阀门”你可以把电容想象成一个能储存电荷的小水库。它有两个口&#xff08;正负极&#xff09;&#xff0c;中间被一个绝缘的“水坝”&#xff08;电介质&#xff09;隔开。1. 隔直电容&#xff08;Blocking Capacitor / DC B…

作者头像 李华
网站建设 2026/3/27 17:24:15

SAP ABAP程序提交后台JOB执行实例

一、代码当一个报表程序ALV数据过多&#xff0c;点击功能按钮执行过慢时可以选择提交后台JOB执行。如下为提交后台执行的formFORM frm_submit_job .DATA: lv_jobname LIKE tbtcjob-jobname,lv_jobcount LIKE tbtcjob-jobcount,lt_stable TYPE TABLE OF rsparams.DATA: lv_…

作者头像 李华