当前位置: 首页 > news >正文

高性能服务开发利器:redis+lua

Redis 与 Lua 脚本的结合,其核心价值在于 ​原子性操作​ 和 ​减少网络开销。

一、Redis 执行 Lua 脚本的优势

  1. 原子性
    • Lua 脚本在 Redis 中原子执行,避免多命令竞态条件。
  2. 减少网络开销
    • 将多个 Redis 命令合并为一个脚本,减少客户端与 Redis 之间的通信次数。
  3. 高性能
    • Redis 会将 Lua 脚本缓存(通过 SHA1 摘要),后续通过 EVALSHA 直接调用,减少传输开销。
  4. 复杂逻辑支持
    • 支持条件判断、循环等复杂逻辑,突破 Redis 单命令的限制。

二、基本用法

1. ​执行 Lua 脚本
  • ​**EVAL 命令**​:直接执行脚本。
    EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey hello
    -- KEYS[1] 对应 mykey,ARGV[1] 对应 hello
  • ​**EVALSHA 命令**​:通过 SHA1 摘要执行已缓存的脚本。
    EVALSHA a1b2c3d4... 1 mykey hello
2. ​参数传递
  • KEYS 和 ARGV 是 Redis 与 Lua 之间的参数传递约定:
    • KEYS:用于指定 Redis 键(支持多个键,常用于集群分片)。
    • ARGV:用于传递非键参数(如值、标志位等)。
  • 示例​:
    EVAL "return {KEYS[1], ARGV[1]}" 1 key1 arg1
    -- 返回 { "key1", "arg1" }
3. ​在 Lua 中调用 Redis 命令
  • 使用 redis.call() 或 redis.pcall() 执行 Redis 命令:
    -- 示例:原子递增并设置过期时间
    EVAL "
      local count = redis.call('INCR', KEYS[1])
      if count == 1 then
        redis.call('EXPIRE', KEYS[1], ARGV[1])
      end
      return count
    " 1 my_counter 60
    • redis.call() 遇到错误会抛出异常,终止脚本;redis.pcall() 会捕获错误并返回错误表。

三、高级特性

1. ​原子性与隔离性
  • 原子性​:脚本执行期间,其他客户端命令会被阻塞。
  • 隔离性​:Lua 脚本中不能访问外部全局变量,且 Redis 会重置脚本的 Lua 环境。
2. ​脚本缓存与复用
  • 使用 SCRIPT LOAD 预先缓存脚本,获取 SHA1 摘要:
    SCRIPT LOAD "return redis.call('GET', KEYS[1])"
    -- 返回 SHA1 摘要,如 'abc123...'
  • 后续通过 EVALSHA + SHA1 执行,避免重复传输脚本内容。
3. ​错误处理
  • 抛出异常​:使用 error() 函数返回错误信息。
    EVAL "if #KEYS == 0 then error('KEYS missing') end" 0
  • 捕获异常​:在客户端检查返回值类型,例如:
    local ok, result = pcall(redis.call, 'GET', 'nonexistent_key')
    if not ok then
      return {err = result}
    end
4. ​调试技巧
  • 日志输出​:使用 redis.log(redis.LOG_DEBUG, 'message') 输出调试信息到 Redis 日志。
  • 模拟断点​:在脚本中插入 redis.breakpoint()(需配合调试工具)。

四、性能优化

  1. 避免大循环
    • Lua 脚本执行期间会阻塞 Redis,避免在脚本中执行耗时操作(如遍历大表)。
  2. 复用脚本
    • 尽量复用已缓存的脚本(EVALSHA),减少 EVAL 的传输开销。
  3. 参数最小化
    • 减少 KEYS 和 ARGV 的数量和大小,避免内存压力。

五、常见应用场景

1. ​限流(Rate Limiting)​
-- KEYS[1]: 限流键,ARGV[1]: 时间窗口(秒),ARGV[2]: 最大请求数
local current = redis.call('GET', KEYS[1])
if current and tonumber(current) >= tonumber(ARGV[2]) then
    return 0
else
    redis.call('INCR', KEYS[1])
    redis.call('EXPIRE', KEYS[1], ARGV[1])
    return 1
end
2. ​原子操作(如扣减库存)​
-- KEYS[1]: 库存键,ARGV[1]: 扣减数量
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock >= tonumber(ARGV[1]) then
    redis.call('DECRBY', KEYS[1], ARGV[1])
    return 1  -- 成功
else
    return 0  -- 库存不足
end
3. ​分布式锁
-- KEYS[1]: 锁键,ARGV[1]: 锁值,ARGV[2]: 过期时间(毫秒)
if redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then
    return 1  -- 加锁成功
else
    return 0  -- 加锁失败
end
4. ​批量操作(如删除匹配模式的键)​
-- 注意:慎用,可能阻塞 Redis!
local keys = redis.call('KEYS', ARGV[1])
for i, key in ipairs(keys) do
    redis.call('DEL', key)
end
return #keys

六、注意事项

  1. 脚本阻塞风险
    • 避免在脚本中执行 KEYS * 或大循环,防止长时间阻塞 Redis。
  2. 参数大小限制
    • Lua 脚本和参数的总大小默认限制为 1MB(可通过 client-query-buffer-limit 调整)。
  3. 集群模式
    • 在 Redis Cluster 中,脚本操作的键必须位于同一节点(通过 {} 哈希标签强制路由)。

七、调试与监控

  1. 查看脚本缓存
    redis-cli SCRIPT EXISTS sha1_value
  2. 清除脚本缓存
    redis-cli SCRIPT FLUSH
  3. 监控脚本执行
    • 通过 Redis 的 SLOWLOG 监控慢脚本。
http://www.dtcms.com/a/121823.html

相关文章:

  • Spring 框架的核心基础:IoC 和 AOP
  • 【算法竞赛】回文字符串+思维模拟(蓝桥杯真题·回文字符串·代码清晰易懂)
  • 巧记英语四级单词 Unit3-上【晓艳老师版】
  • 【SpringCloud】从入门到精通(下)
  • TCP 与 UDP
  • Qt 开发时可以在函数内引用的头文件
  • 国网B接口协议调阅实时视频接口流程详解以及检索失败原因(电网B接口)
  • 蓝桥杯刷题总结 + 应赛技巧
  • MySQL表的增删查改(基础)
  • python学智能算法(九)|决策树深入理解
  • [前端]从人体结构看网页三要素:HTML、CSS 与 JavaScript
  • C#.NET模拟用户点击按钮button1.PerformClick自动化测试
  • 动手人形机器人(RL)
  • 去除Mysql表中的空格、回车、换行符和特殊字符
  • 淘宝API与小程序深度联动:商品详情页“一键转卖”功能开发
  • NO.83十六届蓝桥杯备战|动态规划-基础线性DP|台阶问题|最大子段和|传球游戏|乌龟棋(C++)
  • Elasticsearch 集群搭建
  • Vue3+Vite+TypeScript+Element Plus开发-10.多用户动态加载菜单
  • Hi Robot——大脑加强版的π0:基于「VLM的高层次推理+ VLA低层次任务执行」的复杂指令跟随及交互式反馈
  • Python标准库-copy
  • FairMOT复现过程中cython_bbox库问题
  • go游戏后端开发32:自摸杠处理逻辑
  • Elasticsearch中的基本全文搜索和过滤
  • Spring Boot应用中可能出现的Full GC问题
  • 滑动窗口(2)—最⼤连续1的个数III
  • git 查看某一文件夹下所有文件 修改记录
  • 深度学习总结(4)
  • LVGL开发指南
  • 如何构建并优化提示词?
  • 【LeetCode 热题100】73:矩阵置零(详细解析)(Go语言版)