Redis锁得住,世界就是你的:一探Redis分布式锁的原理、姿势与深度思考

"锁得住Redis的心,才锁得住并发的魂。"

在微服务、分布式、集群化的今天,如果你还在用 synchronized 对抗并发,那你就像拿着锁门的钥匙试图锁防盗门......根本锁不到点上!

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

🔒 单体应用的锁,还行

java 复制代码
synchronized (this) {
    // 线程安全了
}

ReentrantLock 也好,synchronized 也罢,它们都只在当前JVM进程里奏效。

🧨 分布式应用的锁,就不灵了

假设你部署了两个实例在两台机器上:

时间 实例 A 实例 B
T1 检查库存为 1,准备下单 -
T2 - 也检查库存为 1,准备下单
T3 下单成功,库存变为 0 -
T4 - 也下单成功,库存变成 -1 ❌

❗ 这就尴尬了 ------ 数据出现了"超卖"。

此时你需要一个能跨进程、跨JVM、所有实例可见的锁机制

🎯 Redis 天生是分布式的,基于网络通信,天然跨 JVM,是锁的好载体。


二、Redis锁的实现原理(基础要讲透)

📌 本质:用 SETNX + 过期时间 来模拟"加锁"

sql 复制代码
SETNX(Set if Not Exists)命令实现原子性地加锁
EXPIRE 设置过期时间避免死锁

典型实现(Java版):

javascript 复制代码
Boolean isLocked = redisTemplate.opsForValue().setIfAbsent("lock:order", "uuid", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(isLocked)) {
    // 成功获取锁
    try {
        // 执行业务逻辑
    } finally {
        // 删除锁
        redisTemplate.delete("lock:order");
    }
} else {
    // 获取锁失败,返回或重试
}

⚠️ 问题:删除锁时可能误删他人锁

如果A线程加锁成功,10秒后业务未完成,锁过期自动删除;B线程获取了锁并更新了值;但此时A线程仍在执行,直接 delete("lock:order") 就会误删B的锁!

✅ 解决:加锁时加唯一标识 + 删除时先比对标识

ini 复制代码
String uuid = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue().setIfAbsent("lock:order", uuid, 10, TimeUnit.SECONDS);
...
// 删除锁之前先比较 uuid
String value = redisTemplate.opsForValue().get("lock:order");
if (uuid.equals(value)) {
    redisTemplate.delete("lock:order");
}

❗注意:比较 + 删除要原子操作,否则还是不安全 ------ 所以需要 Lua 脚本!


三、Lua 脚本保证解锁原子性

vbnet 复制代码
if redis.call("get", KEYS[1]) == ARGV[1]
then
    return redis.call("del", KEYS[1])
else
    return 0
end

Java中执行方式(SpringBoot + Jedis):

ini 复制代码
String luaScript = "..."; // 上面那段
Object result = jedis.eval(luaScript, Collections.singletonList("lock:order"), Collections.singletonList(uuid));

四、加点味道:自旋锁实现(尝试多次获取)

Redis原生命令失败后,可以选择返回 false,也可以 "死磕到底" ------ 自旋!

vbnet 复制代码
public boolean tryLock(String key, String uuid, long timeoutMs) {
    long start = System.currentTimeMillis();
    do {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, uuid, 10, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(result)) {
            return true;
        }
        try {
            Thread.sleep(50); // 稍微等等
        } catch (InterruptedException ignored) {}
    } while (System.currentTimeMillis() - start < timeoutMs);
    return false;
}

适用于秒杀、排行榜更新等短时间竞争激烈的场景


五、Redisson:优雅而安全的分布式锁

不想手撸 Lua?不想维护自旋逻辑?Redisson 来拯救你!

Redisson封装了:

  • 自动续期(看门狗机制)
  • 可重入锁、读写锁、公平锁
  • 集群下 RedLock 实现
  • 异步锁机制

☂️ 自动续期(Watch Dog)

默认锁时间:30秒

如果线程未释放,看门狗会自动续期,直到业务逻辑完成!

csharp 复制代码
RLock lock = redissonClient.getLock("lock:order");
try {
    lock.lock();  // 默认30s自动续期
    // 执行业务逻辑
} finally {
    lock.unlock();
}

🧠 为什么推荐 Redisson?

功能 手撸 Redis Redisson
原子性删除锁 需要 Lua 内置封装
自动续期 需自己实现 默认支持
锁类型 基本锁 支持读写锁、公平锁、RedLock
容错性 自己保证
易用性 一般 极佳

六、几个常见"锁"姿势比较

锁方式 原理 优点 缺点
setnx + expire 简单 实现快 容易死锁,删除不安全
setnx + uuid + lua 更安全 原子删除锁 需要写Lua
Redisson 内置机制 稳定、高可用 引入第三方库,有一定重量级
自旋锁 重试获取 保证最终拿到锁 可能造成Redis压力、线程饥饿

七、结合 SpringBoot 实战小结

csharp 复制代码
@Service
public class OrderService {

    @Autowired
    private RedissonClient redissonClient;

    public void placeOrder() {
        RLock lock = redissonClient.getLock("lock:order");
        try {
            if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
                // 拿到锁,执行核心逻辑
                doBusiness();
            } else {
                throw new RuntimeException("高并发下获取锁失败,请稍后再试");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("线程中断", e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

Redisson 配置(application.yml):

yaml 复制代码
redisson:
  config: |
    singleServerConfig:
      address: "redis://localhost:6379"

八、终章 · 思考与哲理

  1. 分布式锁不是万能药。Redis 宕机,锁就飞了------需搭配高可用集群;
  2. RedLock 并非绝对安全,需权衡 CAP;
  3. 锁粒度需控制好:太细无效,太粗浪费资源;
  4. **锁是否必须?**乐观锁(CAS)+ 队列控制,也许能化解锁带来的副作用;
  5. 锁得住逻辑,才锁得住业务。

🧠 总结一下

模块 内容
为什么用锁 分布式环境无法靠 JVM 锁
原理 SETNX + 过期时间 + 唯一ID + Lua 脚本
自旋锁 不甘失败,尝试多次
Redisson 自动续期,优雅封装
SpringBoot 实践 @Service + RedissonClient
深度思考 锁与业务、性能、容错性的平衡

如果你看到这儿,说明你已经把 Redis 锁吃得七七八八了。愿你在高并发的世界里,锁得住代码、锁得住数据,也锁得住生活的稳定与从容。

相关推荐
不知道叫什么呀17 分钟前
【C】vector和array的区别
java·c语言·开发语言·aigc
wan_da_ren1 小时前
JVM监控及诊断工具-GUI篇
java·开发语言·jvm·后端
【本人】1 小时前
Django基础(一)———创建与启动
后端·python·django
cui_hao_nan1 小时前
JAVA并发——什么是Java的原子性、可见性和有序性
java·开发语言
best_virtuoso1 小时前
JAVA JVM垃圾收集
java·开发语言·jvm
lifallen1 小时前
Kafka 时间轮深度解析:如何O(1)处理定时任务
java·数据结构·分布式·后端·算法·kafka
顾林海1 小时前
Android 性能优化:启动优化全解析
android·java·面试·性能优化·zygote
你的人类朋友2 小时前
【✈️速通】什么是SIT,什么是UAT?
后端·单元测试·测试
risc1234563 小时前
BKD 树(Block KD-Tree)Lucene
java·数据结构·lucene
kk_stoper3 小时前
如何通过API查询实时能源期货价格
java·开发语言·javascript·数据结构·python·能源