1078 字
5 分钟
Redis 的 Lua 脚本功能是什么?如何使用?
  1. 1Redis 系列导读:知识地图、必会主线与复习顺序
  2. 2Redis 中常见的数据类型有哪些?
  3. 3Redis 通常应用于哪些场景?
  4. 4Redis为什么这么快?
  5. 5Redis 常用的过期时间、删除 Key 命令有哪些?
  6. 6Redis 的过期 Key 是如何被删除的?
  7. 7Redis 内存满了怎么办?有哪些淘汰策略?
  8. 8缓存穿透、击穿、雪崩的区别与解决方案
  9. 9Redis 如何保证缓存与数据库的数据一致性?
  10. 10Redis 的持久化机制有哪些?
  11. 11Redis 主从复制的完整流程是什么?
  12. 12Redis 的哨兵机制是什么?
  13. 13Redis Cluster 模式与 Sentinel 模式的区别是什么?
  14. 14Redis 支持事务吗?如何实现?
  15. 15Redis 中如何实现分布式锁?
  16. 16Redis 的 Lua 脚本功能是什么?如何使用?本文
  17. 17如何解决 Redis 中的热点 Key 问题?
  18. 18Redis BigKey 有什么危害?如何发现和治理?
  19. 19为什么 Redis 设计为单线程?6.0 版本为何引入多线程?
  20. 20Redis 的 LRU 和 LFU 有什么区别?
  21. 21Redis 数据类型和底层编码有什么区别?
  22. 22Redis 中跳表的实现原理是什么?
  23. 23Redis 的 ZSet 为什么使用跳表,而不是平衡树?
  24. 24Redis 的 RDB 快照如何生成?为什么会有写时复制?
  25. 25Redis 的 AOF 刷盘和重写有什么区别?
  26. 26Redis 断线后如何通过 PSYNC 部分重同步?
  27. 27Redis 主从切换为什么会丢数据?WAIT 能解决吗?
  28. 28Redis 集群会出现脑裂问题吗?
  29. 29Redisson 看门狗(watch dog)机制了解吗?
  30. 30Redis Cluster:哈希槽、Gossip 与故障转移
  31. 31Redis Pipeline 为什么能提高吞吐?能保证原子性吗?
  32. 32Redis 的 SCAN 和 KEYS 有什么区别?
  33. 33Redis 变慢时如何排查?SLOWLOG 能看到所有耗时吗?
  34. 34Redis 和 MySQL 有什么区别?
  35. 35Redis 为什么使用 SDS,而不是 C 字符串?
  36. 36Redis 的哈希表如何处理冲突和渐进式 rehash?
  37. 37Redis Cluster 的 MOVED 和 ASK 重定向有什么区别?
  38. 38Redis Cluster 为什么会报 CROSSSLOT?Hash Tag 怎么用?
  39. 39Redis 如何实现限流?固定窗口和滑动窗口怎么选?
  40. 40Redis Stream 如何保证消息消费?ACK 和 PEL 是什么?
  41. 41Redis 删除数据后为什么内存不下降?如何理解内存碎片?

这道题的核心是:Redis 的 Lua 脚本可以把多个 Redis 操作组合成一个原子操作,避免操作执行到一半时被其他命令插入。

你之前学过的分布式锁释放操作,就是 Lua 脚本的典型应用。

一、为什么 Redis 需要 Lua 脚本?#

先看一个分布式锁的例子。

假设请求 A 获得了锁,锁的值是 token-A。

释放锁时,需要做两件事:

  1. 判断锁是不是自己的。
  2. 如果是自己的,删除锁。

如果使用普通命令分开执行:

GET lock:order:1001
DEL lock:order:1001

可能出现下面的问题:

  • 请求 A 执行 GET,发现锁属于自己。
  • 此时锁过期了,请求 B 获得了这把锁。
  • 请求 A 接着执行 DEL,把请求 B 的锁删除了。

问题就在于:判断和删除是两个独立操作,中间可能发生其他事情。

使用 Lua 脚本,就可以把这两个步骤合并成一个原子操作。

二、Lua 脚本怎么使用?#

Redis 支持通过 EVAL 命令执行 Lua 脚本。

例如,安全地释放分布式锁:

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

执行命令:

EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 lock:order:1001 token-A

看起来有点复杂,我们拆开理解。

部分含义
EVAL执行 Lua 脚本
1后面有 1 个 Key 参数
lock:order:1001KEYS[1],锁的名称
token-AARGV[1],锁的唯一标识
redis.call()在 Lua 中调用 Redis 命令
return返回执行结果

脚本的执行逻辑是:

  • 如果锁的值等于 token-A,就删除锁并返回 1。
  • 如果锁不存在或不属于 token-A,就不删除并返回 0。

为什么它是原子的?#

Redis 执行 Lua 脚本期间,不会穿插执行其他客户端的普通命令。

因此,从判断锁的归属到删除锁,中间不会插入其他客户端的命令。

这就解决了刚才的误删问题。

注意:Lua 脚本执行期间会阻塞 Redis 的其他命令,所以脚本应当尽量简短,避免执行耗时很长的循环。

三、除了分布式锁,还能用在哪里?#

1. 扣减库存#

例如,商品库存是 10,要扣减 1 件。

可以在一个 Lua 脚本中完成:

  1. 读取库存。
  2. 判断库存是否充足。
  3. 库存充足则扣减。
  4. 返回执行结果。

这样多个请求并发扣减时,不会因为读取和更新之间被其他命令插入而导致超卖。

2. 限流#

例如,限制一个用户每分钟最多请求 100 次。

Lua 脚本可以把计数器更新、过期时间设置和阈值判断组合起来,避免多个步骤之间出现并发问题。

3. 多个操作需要保持整体原子性#

当一项业务需要连续执行多个 Redis 命令,而且中间不能被其他客户端的命令打断时,可以考虑 Lua 脚本。

四、面试时怎么回答?#

Redis 的 Lua 脚本功能允许我们在 Redis 中执行自定义脚本,将多个 Redis 操作组合成一个原子操作。

使用时,可以通过 EVAL 命令执行 Lua 脚本,并通过 KEYS 和 ARGV 传入 Key 和其他参数。

典型应用是分布式锁的释放:通过 Lua 脚本先判断锁的唯一标识是否属于当前请求,再决定是否删除锁,保证判断和删除的原子性,避免误删其他请求的锁。

此外,Lua 脚本还可以用于库存扣减、限流等需要多个 Redis 操作保持原子性的场景。

需要注意,Lua 脚本执行期间会阻塞 Redis 的其他命令,因此脚本应该尽量简短。

五、记住三个重点#

  • 作用: 将多个 Redis 操作组合成一个原子操作。
  • 使用: EVAL 执行脚本,通过 KEYS 和 ARGV 传参。
  • 典型场景: 分布式锁释放、库存扣减、限流。

面试官如果继续追问,最常见的就是:Redis Lua 脚本为什么具有原子性? 答案是脚本执行期间不会穿插执行其他客户端命令,但这不代表脚本发生运行时错误后,之前执行过的写操作会自动回滚。

Redis 的 Lua 脚本功能是什么?如何使用?
https://blog.xqcherry.top/posts/interview/redis/redis-lua-scripts/
作者
xqcherry
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0