news 2026/4/16 10:46:25

Redis + Lua 脚本:这些技巧你用过吗?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis + Lua 脚本:这些技巧你用过吗?

Redis 支持执行 Lua 脚本,这是它最强大的特性之一。借助 Lua 脚本,可以在 Redis 服务端实现原子操作,避免并发问题,同时减少网络往返开销。

今天聊几个常用的 Lua 脚本技巧,不整虚的,直接上能跑的例子。

为什么用 Lua 脚本

先说个场景:库存扣减。

// 普通做法:两次网络往返Stockstock=redisTemplate.opsForValue().get("stock:001");if(stock.getCount()>0){redisTemplate.opsForValue().decrement("stock:001");}

问题在哪?getdecrement之间没有原子性,高并发下可能超卖。

用 Lua 脚本,一行搞定:

-- 库存扣减 Lua 脚本localstock=redis.call('GET',KEYS[1])ifstockandtonumber(stock)>0thenreturnredis.call('DECR',KEYS[1])endreturn0

Redis 执行 Lua 脚本是单线程的,天然原子,不用担心并发问题。

技巧一:分布式锁

publicclassRedisLock{@AutowiredprivateStringRedisTemplateredisTemplate;privatestaticfinalStringLOCK_SCRIPT="if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then "+" redis.call('EXPIRE', KEYS[1], ARGV[2]) "+" return 1 "+"end "+"return 0";publicbooleantryLock(Stringkey,Stringvalue,longexpireSeconds){Longresult=redisTemplate.execute(newDefaultRedisScript<>(LOCK_SCRIPT,Long.class),Collections.singletonList(key),value,String.valueOf(expireSeconds));returnresult!=null&&result==1;}}

Lua 脚本保证 SETNX 和 EXPIRE 的原子性,避免锁忘了设过期时间的坑。

技巧二:滑动窗口限流

限流最怕的是边界问题——在窗口边缘一瞬间请求集中通过。用 Lua 脚本实现滑动窗口限流:

-- 滑动窗口限流-- KEYS[1] = 限流key-- ARGV[1] = 窗口大小(毫秒)-- ARGV[2] = 最大请求数localkey=KEYS[1]localwindow=tonumber(ARGV[1])locallimit=tonumber(ARGV[2])localnow=tonumber(ARGV[3])localwindowStart=now-window-- 删除窗口外的旧记录redis.call('ZREMRANGEBYSCORE',key,0,windowStart)-- 统计当前窗口请求数localcount=redis.call('ZCARD',key)ifcount<limitthenredis.call('ZADD',key,now,now..':'..math.random())redis.call('PEXPIRE',key,window)return1endreturn0

Java 调用:

publicbooleanisAllowed(Stringkey,longwindowMs,intlimit){DefaultRedisScript<Long>script=newDefaultRedisScript<>();script.setScriptText("local key = KEYS[1] "+"local window = tonumber(ARGV[1]) "+"local limit = tonumber(ARGV[2]) "+"local now = tonumber(ARGV[3]) "+"local windowStart = now - window "+"redis.call('ZREMRANGEBYSCORE', key, 0, windowStart) "+"local count = redis.call('ZCARD', key) "+"if count < limit then "+" redis.call('ZADD', key, now, now .. ':' .. math.random()) "+" redis.call('PEXPIRE', key, window) "+" return 1 "+"end "+"return 0");script.setResultType(Long.class);Longresult=redisTemplate.execute(script,Collections.singletonList(key),String.valueOf(windowMs),String.valueOf(limit),String.valueOf(System.currentTimeMillis()));returnresult!=null&&result==1;}

用 ZSET 记录每次请求的时间戳,窗口大小用 PEXPIRE 自动过期,简洁又精准。

技巧三:点赞防刷

用 Lua 实现单用户点赞频率限制:

-- 用户点赞频率限制-- KEYS[1] = 点赞频率限制key (user:like:count:{userId})-- ARGV[1] = 限制时间(秒)-- ARGV[2] = 最大点赞次数localkey=KEYS[1]localexpire=tonumber(ARGV[1])locallimit=tonumber(ARGV[2])localcount=redis.call('GET',key)ifcountandtonumber(count)>=limitthenreturn0endifcountthenredis.call('INCR',key)elseredis.call('SET',key,1,'EX',expire)endreturn1

结合实际的点赞操作一起原子执行:

-- 完整点赞 Lua 脚本-- KEYS[1] = 点赞频率key-- KEYS[2] = 点赞状态key (article:likes:{articleId})-- ARGV[1] = 限制时间-- ARGV[2] = 最大次数-- ARGV[3] = 用户idlocalfreqKey=KEYS[1]locallikeKey=KEYS[2]localexpire=tonumber(ARGV[1])locallimit=tonumber(ARGV[2])localuserId=ARGV[3]-- 检查频率限制localcount=redis.call('GET',freqKey)ifcountandtonumber(count)>=limitthenreturn{-1,'点赞太频繁'}end-- 检查是否已点赞localalreadyLiked=redis.call('SISMEMBER',likeKey,userId)ifalreadyLikedthenreturn{-2,'已点赞过'}end-- 执行点赞redis.call('SADD',likeKey,userId)ifcountthenredis.call('INCR',freqKey)elseredis.call('SET',freqKey,1,'EX',expire)endlocallikeCount=redis.call('SCARD',likeKey)return{1,likeCount}

技巧四:批量扣减库存

-- 批量扣减库存(支持多商品)-- KEYS = 商品库存key列表-- ARGV = 要扣减的数量列表-- 返回:成功返回1,失败返回商品索引localquantities={}fori=1,#ARGVdoquantities[i]=tonumber(ARGV[i])endlocalsuccess=truelocalfailedIndex=-1fori=1,#KEYSdolocalstock=tonumber(redis.call('GET',KEYS[i])or0)ifstock<quantities[i]thensuccess=falsefailedIndex=ibreakendendifsuccessthenfori=1,#KEYSdoredis.call('DECRBY',KEYS[i],quantities[i])endreturn1endreturnfailedIndex

技巧五:延迟队列

用 Lua 实现简单的延迟队列:

-- 延迟任务入队-- KEYS[1] = 延迟队列key-- ARGV[1] = 任务数据-- ARGV[2] = 延迟执行时间戳(毫秒)localscore=tonumber(ARGV[2])redis.call('ZADD',KEYS[1],score,ARGV[1])return1
-- 取出可执行的延迟任务-- KEYS[1] = 延迟队列key-- ARGV[1] = 当前时间戳(毫秒)-- ARGV[2] = 每次最多取几条localnow=tonumber(ARGV[1])locallimit=tonumber(ARGV[2])localtasks=redis.call('ZRANGEBYSCORE',KEYS[1],0,now,'LIMIT',0,limit)if#tasks>0thenfori=1,#tasksdoredis.call('ZREM',KEYS[1],tasks[i])redis.call('RPUSH',KEYS[1]..':ready',tasks[i])endendreturntasks

总结

场景核心价值
分布式锁SETNX + EXPIRE 原子操作
滑动窗口限流ZSET 按时间戳排序,天然实现滑动窗口
点赞防刷多条件判断 + 状态更新一次完成
批量扣减先检查后操作,原子性保证不超卖
延迟队列ZADD 按时间戳排序,ZRANGEBYSCORE 取到期任务

Lua 脚本让 Redis 的能力大幅扩展,核心就是:把多次网络往返变成一次,把多个操作变成原子的。用好了,Redis 不只是个缓存库。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 15:03:44

3步掌握番茄小说下载器:如何高效离线阅读优质小说

3步掌握番茄小说下载器&#xff1a;如何高效离线阅读优质小说 【免费下载链接】Tomato-Novel-Downloader 番茄小说下载器不精简版 项目地址: https://gitcode.com/gh_mirrors/to/Tomato-Novel-Downloader 想象一下&#xff0c;当你在通勤路上想继续追更心爱的小说&#…

作者头像 李华
网站建设 2026/4/14 15:02:28

注意力头坍缩、模态偏置、时序错位——多模态大模型推理失效的三大隐性杀手,工程师必须在部署前48小时识别!

第一章&#xff1a;多模态大模型中的注意力机制 2026奇点智能技术大会(https://ml-summit.org) 多模态大模型的核心挑战在于如何对齐与融合来自图像、文本、音频等异构模态的语义表征&#xff0c;而注意力机制正是实现跨模态动态关联的关键引擎。它不再局限于单一模态内的局部…

作者头像 李华
网站建设 2026/4/14 15:02:17

告别B站视频无法下载的烦恼:BiliDownloader让你轻松收藏任何视频

告别B站视频无法下载的烦恼&#xff1a;BiliDownloader让你轻松收藏任何视频 【免费下载链接】BiliDownloader BiliDownloader是一款界面精简&#xff0c;操作简单且高速下载的b站下载器 项目地址: https://gitcode.com/gh_mirrors/bi/BiliDownloader 你是否曾在B站看到…

作者头像 李华