0

0

Redis如何利用Lua实现原子性的JSON局部更新

P粉602998670

P粉602998670

发布时间:2026-03-11 19:00:33

|

163人浏览过

|

来源于php中文网

原创

json.set 无法安全实现局部更新,因其路径写入不保证“读-改-写”原子性,高并发下易丢数据;需用 lua 脚本封装 json.get + 内存修改 + json.set 全量写回,依赖 redis 单线程执行保障原子性。

redis如何利用lua实现原子性的json局部更新

为什么不能直接用 JSON.SET 做局部更新

因为 JSON.SET 是全量覆盖,不是局部修改。你传一个新对象进去,整个字段就替换了,中间状态完全不可控——这在并发写同个 JSON 字段时会丢数据。比如两个客户端同时读出 {"a":1,"b":2},各自改 ab 再写回,必然有一个覆盖另一个。

真正需要的是类似 JSON.SET user:123 $.a 100 这种路径级原子操作,但 Redis 原生 JSON 模块(v4+)的 JSON.SET 虽支持路径,仍不保证“读-改-写”原子性;它只保证单次命令原子,不解决竞态。

  • 想局部改 $.user.profile.age?必须先 JSON.GET,再本地解析、修改、JSON.SET —— 三步,非原子
  • 用 Lua 就能把这三步压进一个服务端原子上下文里
  • 注意:Redis 7.0+ 的 JSON.SET 加了 XX/NX 等选项,但依然不解决“读取后计算再写入”的原子性问题

怎么写一个安全的 Lua 脚本做 JSON 局部更新

核心思路:用 EVAL 把 JSON 解析、路径定位、值替换、序列化全部塞进 Lua,靠 Redis 单线程执行保障原子性。别指望 redis.call("JSON.GET",...) 后再用 Lua 处理——JSON 字符串得自己解析,而 Redis 内置的 cjson 不可用(Lua 沙箱禁用),所以得用纯字符串操作或预编译逻辑。

更实际的做法是:把更新逻辑写死在脚本里,用参数传入 key、path、new_value,由 Lua 用 redis.call("JSON.GET", key) 拿原始 JSON,然后调用 redis.call("JSON.SET", key, path, new_value) —— 等等,这还是两步?错,JSON.SET 本身支持路径和原子写入,但前提是你要确保它不会被并发干扰。所以真正安全的组合是:JSON.GET + Lua 内存中 patch + JSON.SET 全量写回,且全程无网络往返。

银河易创
银河易创

一站式AIGC创作平台,集成GPT-3.5、GPT-4、文心一言等对话模型、Midjourney、DallE等绘画工具、AI音乐、AI视频和AI PPT等功能!

下载
  • 脚本必须用 redis.call("JSON.GET", KEYS[1]) 读,不能用 redis.pcall(失败会中断脚本)
  • 如果 JSON 值为空或不存在,JSON.GET 返回 nil,Lua 中要显式判断,否则 cjson.decode(nil) 会报错 —— 但注意:Redis Lua 沙箱里没有 cjson!得用 redis.call("JSON.GET",...) 拿字符串,再用 string.match 或提前约定结构简化逻辑
  • 推荐做法:只支持简单路径如 $.a$.data.count,用正则提取字段名,拼接新 JSON 字符串,避免通用 JSON 解析
  • 示例脚本片段:
    local json = redis.call("JSON.GET", KEYS[1])
    if not json then
      return nil
    end
    -- 假设只更新 $.score,且原结构固定
    local new_json = string.gsub(json, '"score":%s*%d+', '"score":' .. ARGV[1])
    redis.call("JSON.SET", KEYS[1], "$", new_json)
    return 1

EVAL 调用时的参数和坑

脚本本身没状态,但传参稍有不慎就会失败。KEYS 和 ARGV 必须严格对应,且所有 key 必须显式传入 KEYS 列表(Redis Cluster 强制要求),否则集群模式下直接报错 CROSSSLOT Keys in request don't hash to the same slot

  • KEYS 参数只能是 key 名,不能是表达式或拼接结果,例如 "user:"..ARGV[1] 在 Lua 里合法,但 Redis 不认这个为 KEY,会导致集群路由失败
  • ARGV 可以传任意字符串,但 JSON 值里如果有双引号、反斜杠,必须由客户端提前转义,Lua 里不做二次处理
  • 错误信息如 (error) ERR Error running script (call to f_...): @user_script:5: user_script:5: bad argument #2 to 'gsub' (string expected, got nil),通常是因为 JSON.GET 返回 nil(key 不存在或字段路径错),没做空值判断
  • 性能上,Lua 脚本执行时间不能超 lua-time-limit(默认 5 秒),复杂 JSON 遍历或大 payload 容易触发 BUSY 错误

Redis 版本和模块依赖的实际约束

别假设你的 Redis 装了 JSON 模块。很多云厂商托管 Redis(比如阿里云、腾讯云基础版)默认不启用 redis-json,得手动开启或选高配版本。而且模块版本影响功能边界:

  • Redis Stack 或 Redis 7.0+ 自带 JSON 模块,支持 JSON.SET 路径语法;6.x 需单独加载 redis-json.so
  • 6.2 以下版本连 JSON.GET 都没有,Lua 里根本没法读 JSON —— 此时只能退化为字符串字段 + 正则替换,风险自担
  • 模块未加载时执行 JSON.GET 会直接报错 (error) ERR unknown command `JSON.GET`,这个错误发生在 EVAL 前,脚本甚至不会运行
  • 验证方式很简单:MODULE LIST 看输出里有没有 name:rejsonname:json

最易被忽略的一点:Lua 脚本里不能调用 redis.call("MULTI") 或任何事务命令,Redis 的 Lua 沙箱禁止嵌套事务。所谓“原子性”,纯粹来自 Redis 单线程顺序执行脚本这一事实,不是靠 MULTI/EXEC 实现的。

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

455

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

546

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

334

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

82

2025.09.10

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1010

2023.08.02

counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

203

2023.11.20

scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

492

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

377

2023.10.25

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号