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 锁吃得七七八八了。愿你在高并发的世界里,锁得住代码、锁得住数据,也锁得住生活的稳定与从容。

相关推荐
舒一笑28 分钟前
为什么where=Version就是乐观锁了?
后端·mysql·程序员
sunshine-sm30 分钟前
CentOS Steam 9安装 Redis
linux·运维·服务器·redis·centos
GoGeekBaird32 分钟前
关于垂类AI应用落地行业的方法论思考
后端·github·agent
小宁爱Python1 小时前
Django 基础入门:命令、结构与核心配置全解析
后端·python·django
老华带你飞1 小时前
考研论坛平台|考研论坛小程序系统|基于java和微信小程序的考研论坛平台小程序设计与实现(源码+数据库+文档)
java·vue.js·spring boot·考研·小程序·毕设·考研论坛平台小程序
CHEN5_021 小时前
leetcode-hot100 11.盛水最多容器
java·算法·leetcode
songx_991 小时前
leetcode18(无重复字符的最长子串)
java·算法·leetcode
你的人类朋友1 小时前
认识一下Bcrypt哈希算法
后端·安全·程序员
tangweiguo030519872 小时前
基于 Django 与 Bootstrap 构建的现代化设备管理平台
后端·django·bootstrap
在路上`2 小时前
前端学习之后端java小白(三)-sql外键约束一对多
java·前端·学习