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

相关推荐
CodeSheep18 分钟前
Stack Overflow,轰然倒下了!
前端·后端·程序员
GoGeekBaird26 分钟前
GoHumanLoopHub开源上线,开启Agent人际协作新方式
人工智能·后端·github
水痕0144 分钟前
gin结合minio来做文件存储
java·eureka·gin
Victor3561 小时前
Redis(8)如何安装Redis?
后端
寒士obj1 小时前
Spring事物
java·spring
Victor3561 小时前
Redis(9)如何启动和停止Redis服务?
后端
柯南二号2 小时前
【Java后端】Spring Boot 集成 MyBatis-Plus 全攻略
java·spring boot·mybatis
程序员爱钓鱼3 小时前
Go语言实战案例-创建模型并自动迁移
后端·google·go
javachen__3 小时前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql