查看: 54|回复: 0

[Redis] RedisJSON中JSON.SET的用法小结

[复制链接]

0

主题

0

回帖

0

积分

积极分子

金币
0
阅读权限
220
精华
0
威望
0
贡献
0
在线时间
0 小时
注册时间
2012-2-6
发表于 2025-11-12 11:26:00 | 显示全部楼层 |阅读模式

1 · 为什么要写这篇文章?

在结构化数据存储场景里,传统的字符串键值对已难以满足灵活查询与字段级更新的需求。RedisJSON 插件为 Redis 带来了对原生 JSON 文档的读写支持,其中最核心也最常用的指令就是 JSON.SET。本文将带你彻底吃透 JSON.SET 的各个细节——从语法、时间复杂度到多路径批量更新,再到在生产环境中容易踩到的坑和性能调优方法,并提供一套 Go-Redis 的完整示例代码,帮助你快速落地。

2 · RedisJSON 与 JSON.SET 概览

特性说明
插件版本RedisJSON ≥ 1.0 (推荐 2.x 与 Redis 7.x+ 搭配使用)
指令`JSON.SET key path value [NXXX]`
功能按 JSONPath 将 value 写入 key 对应的 JSON 文档
ACL 标记@json @write @slow
时间复杂度O(M + N) —— M 为原值大小,N 为新值大小 × 匹配路径数

3 · 语法详解

JSON.SET <key> <path> <value> [NX | XX]
参数必填说明
keyRedis 中的键;不存在时必须从根路径写入
pathJSONPath 表达式;$ 或 . 代表根
value合法 JSON 字符串,或原样字符串(无需额外引号)
NX仅当目标不存在时写入
XX仅当目标已存在时覆盖

小贴士:NX 与 XX 不仅作用于 Redis 键,也作用于 JSON 文档内部键。这点经常被忽略!

4 · JSONPath 规则速查

  • 根路径:$ 或 .
  • 点式:$.address.city
  • 数组下标:$.items[0]
  • 切片:$.items[1:3](RedisJSON 2.x 支持)
  • 通配:$..price 匹配所有层级的 price 字段
  • 多路径:$..a 与 $..b 可一次性传入多条

注意:JSON.SET 不支持在一次调用中写入 不同 的值到多条路径;如果传入多路径,所有匹配点都会被同一个 value 覆盖。

5 · 返回值与错误处理

情况返回
写入成功OK
路径不存在且无法创建(nil)
NX/XX 条件不满足(nil)
根键不存在但路径不是 $(error) ERR new objects must be created at the root

生产环境中推荐显式检查返回值,而不要只依赖 err == nil。示例见 § 9。

6 · 典型用法示例

6.1 替换已有字段

redis> JSON.SET doc $ '{"a":2}'
OK
redis> JSON.SET doc $.a 3
OK
redis> JSON.GET doc $
"[{\"a\":3}]"

6.2 追加新字段

redis> JSON.SET doc $ '{"a":2}'
OK
redis> JSON.SET doc $.b 8
OK
redis> JSON.GET doc $
"[{\"a\":2,\"b\":8}]"

6.3 一次性批量更新多路径

redis> JSON.SET doc $ '{"f1":{"a":1},"f2":{"a":2}}'
OK
redis> JSON.SET doc $..a 3
OK
redis> JSON.GET doc
"{\"f1\":{\"a\":3},\"f2\":{\"a\":3}}"

6.4 结合 NX / XX 条件

# 仅当字段不存在时写入
redis> JSON.SET user:1 $.nickname '"neo"' NX
OK          # 第一次成功
redis> JSON.SET user:1 $.nickname '"smith"' NX
(nil)       # 条件未满足

7 · 易踩坑汇总

现象解决方案
键不存在却写子路径抛错 ERR new objects must be created at the root第一次写必须用根路径 $
值未用 JSON 字符串包裹若忘记加引号:JSON.SET $.name neo ⇒ 解析失败非数值 / 布尔需双引号或 'neo'
NX/XX 作用域误解误以为只检查 Redis 键其实同时检查 JSON 内目标字段
字符串过长超出 512 MB 限制拆分存储或压缩再存

8 · 性能调优与并发安全

  1. Pipeline / MULTI
    连续多次 JSON.SET 可使用 Pipelining 或事务批量发送,减少 RTT。
  2. 避免大文档频繁写
    时间复杂度包含原值大小 M:越大的 JSON,写一次越慢。可将热点字段拆分为独立键或使用 JSON.NUMINCRBY 等增量指令。
  3. 合理选择 NX/XX
    在幂等场景下使用 NX 或 XX 可避免无谓的重写,降低写放大。
  4. 监控慢日志
    Redis 会将执行时间 > slowlog-log-slower-than 的指令记录;JSON 操作本质属 @slow 类别,应重点关注。
  5. Lua/RedisGears 乐观锁
    高并发写同一字段,可结合 WATCH/MULTI 或 Lua 脚本实现 CAS。

9 · Go-Redis 完整示例(可直接运行)

依赖:Go ≥ 1.22、github.com/redis/go-redis/v9

// 文件 main.go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/redis/go-redis/v9"
)

var ctx = context.Background()

type Profile struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Tags  []string `json:"tags"`
}

func must(err error) {
	if err != nil {
		log.Fatalf("fatal: %v", err)
	}
}

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})
	defer rdb.Close()

	// ---------- 1. 写入根文档 ----------
	profile := Profile{Name: "Alice", Age: 27, Tags: []string{"gopher", "redis"}}
	raw, _ := json.Marshal(profile)

	if res, err := rdb.Do(ctx, "JSON.SET", "user:1001", "$", raw).Text(); err != nil {
		must(err)
	} else {
		fmt.Println("set root:", res) // OK
	}

	// ---------- 2. 更新子字段 ----------
	if res, err := rdb.Do(ctx, "JSON.SET", "user:1001", "$.age", 28, "XX").Text(); err != nil {
		must(err)
	} else if res == "" {
		fmt.Println("XX unmet, age not updated")
	} else {
		fmt.Println("update age:", res) // OK
	}

	// ---------- 3. 读取文档 ----------
	data, err := rdb.Do(ctx, "JSON.GET", "user:1001", "$").Result()
	must(err)
	fmt.Println("profile =", data.(string))

	// ---------- 4. 错误示例 ----------
	if _, err := rdb.Do(ctx, "JSON.SET", "user:1002", "$.name", "\"Bob\"").Result(); err != nil {
		fmt.Println("expected error:", err)
	}

	// Output:
	// set root: OK
	// update age: OK
	// profile = [{"name":"Alice","age":28,"tags":["gopher","redis"]}]
	// expected error: ERR new objects must be created at the root
}

运行后,你将看到成功写入、条件更新、读回以及错误处理的完整流程。

10 · 进阶话题

方向价值
与 RediSearch 集成对 JSON 字段建立二级索引,支持全文检索与聚合分析
存储版本化配置使用 NX 写入新版本,配合 JSON.NUMINCRBY 维护版本号
灰度发布通过数组/对象结构保存多线路配置信息,再用 JSON.DEL 快速回滚
Streaming JSON将大文档拆分到 Stream 或 List,再按需合并到 RedisJSON

11 · 总结

  • JSON.SET 是 RedisJSON 最核心的写入指令,先掌握根写入,再逐步学习多路径与条件写。
  • 时间复杂度与文档大小 线性相关,因此避免频繁重写大 JSON。
  • 正确使用 NX/XX 可实现幂等更新、乐观锁等高级场景。
  • 在 Go-Redis 中通过 Do(ctx, "JSON.SET", …) 可无缝调用,务必检查返回值而非只关注 err。
  • 生产环境要结合 慢日志、Pipeline、事务 等手段进行性能与可靠性保障。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

相关侵权、举报、投诉及建议等,请发 E-mail:qiongdian@foxmail.com

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部