当前位置: 首页 > news >正文

Redis 分布式锁:从原理到 Spring Boot 实战,避开 90% 开发者踩的坑!

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

在分布式系统中,多个服务实例同时操作共享资源(如扣减库存、生成订单号)时,必须使用分布式锁来保证数据一致性。而 Redis 凭借其高性能和原子操作,成为实现分布式锁的首选方案。

但!看似简单的SETNX,背后却隐藏着无数陷阱:锁失效、死锁、主从切换丢锁、误删他人锁……稍有不慎,轻则数据错乱,重则资损事故!

本文将带你:

  • ✅ 深入理解 Redis 分布式锁的核心原理
  • ✅ 用Java + Spring Boot实现高可用、可重入、防误删的分布式锁
  • ✅ 揭露 4 大经典反例与生产级避坑指南

一、为什么需要分布式锁?

🎯 场景:电商秒杀扣库存

// 单机环境下,synchronized 足够 public void deductStock(Long productId) { synchronized (this) { int stock = getStockFromDB(productId); if (stock > 0) { updateStock(productId, stock - 1); // 扣减 } } }

问题:当部署多个服务实例(如 3 台服务器),synchronized只在 JVM 内生效,无法跨进程同步

✅ 解决方案:Redis 分布式锁

  • 所有实例竞争同一把“Redis 锁”
  • 谁拿到锁,谁操作数据库
  • 保证同一时间只有一个实例执行关键代码

二、基础版:SETNX + EXPIRE(❌ 有致命缺陷!)

❌ 反例 1:非原子操作 → 死锁风险

// 错误写法! Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order", "1"); if (locked) { // 设置过期时间(防止业务卡死导致锁永不过期) redisTemplate.expire("lock:order", 10, TimeUnit.SECONDS); try { // 执行业务... } finally { redisTemplate.delete("lock:order"); // 释放锁 } }

🔥 问题分析:

  • setIfAbsent()expire()两个命令,非原子!
  • 如果在setIfAbsent成功后、expire执行前,服务宕机 →锁永不过期 → 死锁!

三、正确姿势 1:SET 命令原子加锁(Redis 2.6.12+)

Redis 的SET命令支持NX(不存在才设) +EX/PX(自动过期)组合,原子性保证

✅ Spring Boot 实现(基础版)

public boolean tryLock(String lockKey, String requestId, long expireTimeMs) { Boolean result = redisTemplate.execute( (RedisCallback<Boolean>) connection -> connection.set( lockKey.getBytes(), requestId.getBytes(), Expiration.milliseconds(expireTimeMs), RedisStringCommands.SetOption.SET_IF_ABSENT ) ); return Boolean.TRUE.equals(result); } public void unlock(String lockKey, String requestId) { // 注意:这里仍有问题!见下文 redisTemplate.delete(lockKey); }

📌关键点

  • requestId:建议用UUID + 线程ID,用于标识锁持有者
  • expireTimeMs:必须设置,防止死锁

❌ 但!解锁仍有问题:可能误删他人锁!

假设:

  • 服务 A 拿到锁,但业务执行超时(>10s),锁自动过期
  • 服务 B 拿到同一把锁
  • 此时服务 A 执行完,调用delete(lockKey)删掉了服务 B 的锁!

四、正确姿势 2:Lua 脚本保证“判断+删除”原子性

要安全释放锁,必须满足:

只有锁的持有者(requestId 匹配)才能删除锁

这需要先 GET 判断值,再 DEL,但两步操作非原子 → 必须用Lua 脚本

✅ 安全解锁 Lua 脚本

-- unlock.lua if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end

✅ Spring Boot 集成

@Component public class RedisDistributedLock { @Autowired private StringRedisTemplate redisTemplate; private static final DefaultRedisScript<Long> UNLOCK_SCRIPT; static { UNLOCK_SCRIPT = new DefaultRedisScript<>(); UNLOCK_SCRIPT.setLocation(new ClassPathResource("lua/unlock.lua")); UNLOCK_SCRIPT.setResultType(Long.class); } public boolean tryLock(String lockKey, String requestId, long expireTimeMs) { Boolean result = redisTemplate.opsForValue() .setIfAbsent(lockQey, requestId, Duration.ofMillis(expireTimeMs)); return Boolean.TRUE.equals(result); } public void unlock(String lockKey, String requestId) { redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(lockKey), requestId); } }

优势:Lua 脚本在 Redis 中原子执行,杜绝误删!


五、进阶需求:可重入锁 & 自动续期

问题 1:同一线程能否多次获取同一把锁?(可重入)

  • 场景:方法 A 加锁 → 调用方法 B(也需要同一把锁)
  • 基础版会阻塞 → 需要可重入锁

问题 2:业务执行时间 > 锁过期时间?(自动续期)

  • 如锁 TTL=30s,但业务需 60s → 锁提前释放 → 并发安全失效

✅ 解决方案:Redisson(生产推荐!)

Redisson是 Redis 官方推荐的 Java 客户端,内置可重入、自动续期、看门狗机制的分布式锁。

Maven 引入
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.23.5</version> </dependency>
Spring Boot 使用
@Service public class OrderService { @Autowired private RedissonClient redissonClient; public void createOrder() { RLock lock = redissonClient.getLock("lock:order:create"); try { // 尝试加锁,最多等待 10 秒,上锁后 30 秒自动解锁 boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS); if (!locked) { throw new RuntimeException("获取锁失败"); } // 执行业务(即使超过 30 秒,Redisson 会自动续期!) doCreateOrder(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); // 可重入:只有最外层 unlock 才真正释放 } } }

🌟Redisson 核心机制

  • 看门狗(Watchdog):只要线程持有锁,每隔 10s 自动续期(TTL 重置为 30s)
  • 可重入计数:同一线程多次加锁,内部计数器 +1,unlock 时 -1,归零才释放
  • 公平锁/读写锁:支持更复杂场景

六、四大经典陷阱与避坑指南

陷阱后果解决方案
非原子加锁死锁SET key val NX EX 10原子命令
无标识解锁误删他人锁用 Lua 脚本校验 requestId
锁过期时间固定业务未完成锁已丢用 Redisson 自动续期
主从架构丢锁主节点宕机,从节点未同步锁 → 多个客户端同时持锁使用RedLock(争议大)或ZooKeeper / ETCD

⚠️关于 RedLock
Redis 作者提出的一种多节点容错方案,但 Martin Kleppmann 等专家指出其在时钟漂移场景下仍不安全。一般业务建议用 Redisson + 单 Redis 实例(高可用由哨兵/Cluster 保证)即可


七、总结:分布式锁最佳实践

  1. 永远不要用SETNX + EXPIRE分开写!
  2. 解锁必须用 Lua 脚本校验持有者 ID!
  3. 业务时间不确定?上 Redisson!
  4. 高可用靠 Redis 哨兵/Cluster,而非 RedLock!
  5. 锁的粒度尽量小,减少持有时间!

结语

分布式锁看似简单,实则暗流涌动。一个没有自动续期、没有持有者校验的锁,等于没有锁!

在生产环境中,强烈建议直接使用 Redisson,它已经帮你踩平了所有坑。自己造轮子?除非你愿意承担资损风险!

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

http://www.cnnetsun.cn/news/753331.html

相关文章:

  • C++课后习题训练记录Day74
  • C++课后习题训练记录Day75
  • 科研必备:六大学术引用平台+AI智能排版推荐
  • 猎头视角:不同阶段候选人的关注点差异与猎头价值
  • 导师推荐!10个AI论文网站测评,研究生科研写作必备
  • 学术文献引用工具排名:六款主流平台及智能规范建议
  • 每日面试题分享152:什么是循环依赖?
  • 60N04NF-ASEMI中低压MOS标杆60N04NF
  • 科研发表加速器:paperzz 期刊论文智能写作工具,让投稿不再卡壳
  • 小迪安全2023-2024|第102天:漏洞发现-漏扫项目篇Poc开发Yaml语法插件一键生成匹配结_笔记|web安全|渗透测试|
  • EasyGBS打造智慧机场一体化智能监管体系
  • 从Java基础到微服务架构:互联网大厂面试三轮问答实录
  • java_ssm111羊毛毡手工艺品交流与分享系统的设计与实现_idea项目源码
  • java_ssm115计算机系统在线考试平台_idea项目源码
  • java_ssm119运列车物资定额及领用管理系统_idea项目源码
  • 导师严选2026 AI论文网站TOP8:专科生毕业论文必备测评
  • 终极清单:量子计算开发者的必备认证TOP5
  • java_ssm1 高校学生班费管理系统
  • java_ssm4在线教育系统的设计与开发
  • java_ssm6专门体检预约管理系统_idea项目源码
  • PCI0设备扩展下的实际建立的子设备扩展共9+4*8=41个--重要中间结果--说明这41个设备存在
  • 【开题答辩全过程】以 基于BS结构的旅游网站的设计与实现为例,包含答辩的问题和答案
  • 耐达讯自动化Profibus光纤链路模块:跨行业通信的“隐形桥梁”,让控制更丝滑!
  • 分布式架构:Dubbo 协议如何做接口测试
  • 【Pytest】结构介绍
  • 《使命召唤20》启动报错缺失 msvcp140.dll 怎么办?2026最新修复评测,哪款工具能一键挽救游戏体验?
  • 基于Python的社区设备报修住户反馈智能预测系统设计 计算机毕业设计选题 计算机毕设项目 前后端分离【源码-文档报告-代码讲解】
  • 免费文献检索网站推荐:高效获取学术资源的实用平台指南
  • ACPI!PciConfigSpaceHandlerWorker函数中的ACPI!GetOpRegionScope最终调用了ACPI!IsPciDevice
  • 岩石图像分割系统源码&数据集分享 [yolov8-seg-C2f-CloAtt等50+全套改进创新点发刊_一键训练教程