Redis缓存击穿场景_如何实现基于Lua脚本的原子加锁与查询

千墨吖_7393

千墨吖_7393

2026-05-05

507人浏览

原创

单纯用setnx不够用,因其无法保证加锁、查库、写缓存三步原子性,且无自动过期易致死锁;必须用lua脚本在redis端原子执行全流程。

redis缓存击穿场景_如何实现基于lua脚本的原子加锁与查询

缓存击穿时直接用 SETNX 为什么不够用

缓存击穿本质是:某个热点 key 过期瞬间,大量并发请求同时发现缓存缺失,全部穿透到 DB,造成瞬时压力。单纯用 SETNX 加锁只能解决“谁来重建缓存”,但无法保证“重建完立刻回填、且后续请求能拿到新值”——因为 SETNX 成功者查库、写缓存、释放锁是三步非原子操作,中间任意环节失败(比如写缓存前进程挂了),锁就丢了,其他请求又会重复击穿。

更麻烦的是,多个客户端对同一 key 竞争锁时,SETNX 没有自动过期机制,容易死锁;而加 EXPIRE 又不是原子的,存在竞态窗口。

所以必须把“尝试加锁 + 设置过期时间 + 查询 DB + 写缓存 + 返回结果”这几步压进一个原子执行单元里——Lua 脚本就是 Redis 唯一能保证这点的方式。

用 EVAL 执行 Lua 实现“查缓存→未命中则加锁并查库→写缓存→返回”全流程

核心思路是:所有逻辑在 Redis 服务端一次性跑完,避免客户端往返和状态不一致。脚本里用 redis.call("GET", KEYS[1]) 先查缓存;命中直接返回;未命中则用 redis.call("SET", KEYS[1]..":lock", ARGV[1], "NX", "EX", ARGV[2]) 尝试加带过期时间的锁(注意 key 名要隔离,避免和业务 key 冲突);加锁成功才去模拟查库(实际用 redis.call("HGETALL", "db:fake") 或其他方式占位),然后 redis.call("SET", KEYS[1], result, "EX", ARGV[3]) 回填缓存。

关键细节:

  • KEYS[1] 是业务 key(如 "user:1001"),ARGV[1] 是唯一锁标识(建议用客户端生成的 UUID,防止误删他人锁)
  • ARGV[2] 是锁过期时间(秒),设为 5–10 秒较安全;ARGV[3] 是业务 key 的缓存 TTL,通常更长(如 300 秒)
  • 加锁失败时不能直接返回空,要等待一小段时间后重试(客户端控制),否则可能集体放弃导致 DB 压力转移
  • 脚本末尾必须用 return 显式输出结果(缓存值或空),方便客户端判断是否需要走降级逻辑

示例简化脚本(生产环境需补全错误分支):

if redis.call("GET", KEYS[1]) ~= false then
  return redis.call("GET", KEYS[1])
else
  if redis.call("SET", KEYS[1]..":lock", ARGV[1], "NX", "EX", ARGV[2]) then
    local db_result = "fake_data_from_db" -- 实际替换为真实查询逻辑
    redis.call("SET", KEYS[1], db_result, "EX", ARGV[3])
    redis.call("DEL", KEYS[1]..":lock")
    return db_result
  else
    return nil -- 表示锁竞争中,客户端应 sleep 后重试
  end
end

为什么不用 Redlock 或 Redisson 的分布式锁

Redlock 理论上更“严谨”,但它依赖多个独立 Redis 实例和时钟一致性,在大多数单集群部署场景下属于过度设计;而且它解决的是跨节点故障下的锁可靠性,而缓存击穿问题本身只发生在单个 key 的本地并发,用单实例 Lua 锁已足够。

Redis Skill - 高性能缓存管理
Redis Skill - 高性能缓存管理

Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。

下载

Redisson 的 RLock 底层也是靠 Lua 脚本实现加锁/续期/释放,但它封装太厚:每次调用都走网络、序列化、连接池管理,对高频热点 key 来说延迟和资源开销明显更高;而手写 Lua 可以把整个“查-锁-查库-写-返”压成一次 EVAL 请求,吞吐量提升显著。

真正要注意的是锁标识(ARGV[1])必须全局唯一且不可预测,否则可能被恶意 DEL;另外脚本里禁止调用可能阻塞的命令(如 KEYS、SLOWLOG),否则会影响整个 Redis 实例。

线上踩过的坑:锁误删、脚本超时、缓存值为空怎么处理

最常见问题是锁标识复用或硬编码,导致 A 客户端加的锁被 B 客户端用相同字符串 DEL 掉。务必让每个客户端生成自己的随机 token(如 UUID.randomUUID().toString()),并在释放锁时用 EVAL 脚本比对再删,而不是简单 DEL。

Redis 默认脚本超时是 5 秒,如果查库逻辑(比如调外部 HTTP)放在 Lua 里会直接报错 ERR Error running script (call to f_...): @user_script:xx: user_script:xx: script tried to execute a command that may have side effects——Lua 里根本不能发网络请求。所有“查库”动作必须由客户端完成,Lua 只负责协调状态和缓存写入。

如果 DB 查出来是空值(比如用户已被删除),不能不缓存,否则每次都会击穿。应该缓存一个特殊标记(如 "NULL" 或 "MISS")并设置较短 TTL(如 60 秒),避免永久性空缓存污染。

复杂点永远在边界:锁过期时间怎么设才既防死锁又不导致重复重建?空值缓存该不该压缩?Lua 脚本要不要预加载(SCRIPT LOAD)?这些没标准答案,得看你的 QPS、DB 抗压能力、以及能接受的缓存不一致窗口。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

redis

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
常用的数据库软件
常用的数据库软件

常用的数据库软件有MySQL、Oracle、SQL Server、PostgreSQL、MongoDB、Redis、Cassandra、Hadoop、Spark和Amazon DynamoDB。更多关于数据库软件的内容详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.02

4249

19

内存数据库有哪些
内存数据库有哪些

内存数据库有Redis、Memcached、Apache Ignite、VoltDB、TimesTen、H2 Database、Aerospike、Oracle TimesTen In-Memory Database、SAP HANA和ache Cassandra。更多关于内存数据库相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.14

3735

11

mongodb和redis哪个读取速度快
mongodb和redis哪个读取速度快

redis 的读取速度比 mongodb 更快。原因包括:1. redis 使用简单的键值存储,而 mongodb 存储 json 格式的数据,需要解析和反序列化。2. redis 使用哈希表快速查找数据,而 mongodb 使用 b-tree 索引。因此,redis 在需要高性能读取操作的应用程序中是一个更好的选择。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.02

6712

6

redis怎么做缓存服务器
redis怎么做缓存服务器

redis 作为缓存服务器的答案:redis 是一款开源、高性能、分布式的键值存储,可作为缓存服务器使用。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

623

6

redis怎么解决数据一致性
redis怎么解决数据一致性

redis 提供了两种一致性模型,以维护副本数据一致性:强一致性 (sync) 确保写操作仅在复制到所有从节点后才完成;最终一致性 (async) 则在主节点上写操作后认为已完成,牺牲一致性换取性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

736

6

mysql和redis怎么保证双写一致性
mysql和redis怎么保证双写一致性

确保 mysql 和 redis 双写一致性的技术包括:1、事务性更新:同时更新 mysql 和 redis,保证一致性;2、主从复制:mysql 主服务器更改同步到 redis 从服务器;3、基于事件的更新:mysql 记录更改并发送到 redis等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

6262

6

redis缓存一般存些什么数据
redis缓存一般存些什么数据

redis缓存中存储的数据类型包括:字符串、哈希、列表、集合、有序集合、位图、地理空间数据和hyperloglog。这些数据类型适用于存储各种数据,从简单信息到复杂对象和地理位置。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

1140

6

redis的8种数据类型有哪些
redis的8种数据类型有哪些

redis 提供 8 种数据类型:字符串(文本、数字、二进制)、哈希(键值对)、列表(有序集合)、集合(无序唯一元素)、有序集合(按分数排序)、地理空间(地理位置)、hyperloglog(估计大数据基数)和位图(位序列存储)。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

996

6

redis主要作用有哪些
redis主要作用有哪些

redis 的主要作用包括:1. 缓存数据,提高访问速度;2. 充当消息队列,实现消息传递;3. 存储各种数据类型,如字符串、散列和集合;4. 管理会话信息,确保可靠性和可用性;5. 限制请求速率,防止服务器超载等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

5618

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
phpEnv手册
phpEnv手册

共0课时 | 0人学习

进程与SOCKET
进程与SOCKET

共6课时 | 0.5万人学习