Lua 脚本在 Redis 中的应用

在高并发场景下,Redis 不仅是一个缓存工具,更是实现分布式协调的重要组件。尤其是分布式锁,在防止资源争用、保证数据一致性方面有着关键作用。

实现分布式锁有多种方式,但无论是 SETNX 命令还是 Redisson 框架,背后都有一个绕不开的关键词------Lua 脚本


一、什么是 Lua 脚本

Lua 是一种轻量、高效的脚本语言,Redis 原生支持用 Lua 脚本执行命令,并且可以保证脚本中的所有 Redis 操作是原子性的。

原子性:脚本中多条命令要么全部执行,要么全部不执行,中间不会被其他请求打断。

换句话说,Lua 脚本在 Redis 中相当于"批量命令的事务",非常适合需要多步逻辑且不能被打断的场景。


二、分布式锁的常见实现

1. 使用 SETNX 实现加锁

最简单的分布式锁实现:

shell 复制代码
SETNX lock_key unique_id
  • lock_key:锁的键名
  • unique_id:锁的唯一标识(防止误删他人锁)

加锁逻辑:

  • 如果 SETNX 返回 1,表示加锁成功
  • 如果返回 0,说明锁已经被别人持有

解锁逻辑:

lua 复制代码
if redis.call("get", "lock_key") == unique_id then
    redis.call("del", "lock_key")
end

2. 解锁的并发安全问题

如果用普通代码分两步实现解锁(先 GETDEL):

java 复制代码
if (redisTemplate.opsForValue().get("lock_key").equals(unique_id)) {
    redisTemplate.delete("lock_key");
}

在高并发下可能发生:

  1. 线程 A GET 到锁是自己的
  2. 线程 B 抢占了锁(设置了新的 unique_id
  3. 线程 A 执行 DEL,结果误删了线程 B 的锁

这就破坏了锁的正确性。


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

解决方法是用 Lua 脚本将"判断锁持有者"和"删除锁"两个步骤打包成一个原子操作:

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

Java 调用示例:

java 复制代码
String script =
    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
    "return redis.call('del', KEYS[1]) else return 0 end";

Long result = (Long) redisTemplate.execute(
    new DefaultRedisScript<>(script, Long.class),
    Collections.singletonList("lock_key"),
    "unique_id"
);

if (result != null && result > 0) {
    System.out.println("解锁成功");
} else {
    System.out.println("解锁失败");
}

这样,无论并发多高,解锁过程都是安全的。


四、Redisson 的分布式锁

如果不想手写 Lua 脚本,可以直接使用 Redisson,它内部已经用 Lua 脚本实现了加锁、解锁的原子性,并且支持:

  • 可重入锁
  • 公平锁
  • 看门狗自动续期

示例:

java 复制代码
RLock lock = redissonClient.getLock("lock_key");
lock.lock();
try {
    // 业务逻辑
} finally {
    lock.unlock();
}

相关推荐
侠客行03173 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪3 小时前
深入浅出LangChain4J
java·langchain·llm
子兮曰3 小时前
OpenClaw入门:从零开始搭建你的私有化AI助手
前端·架构·github
吴仰晖3 小时前
使用github copliot chat的源码学习之Chromium Compositor
前端
1024小神3 小时前
github发布pages的几种状态记录
前端
老毛肚4 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎5 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
不像程序员的程序媛5 小时前
Nginx日志切分
服务器·前端·nginx
Yvonne爱编码5 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚5 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言