RedisJSON 的 JSON.STRAPPEND字符串追加的正确姿势

发布于:2025-07-23 ⋅ 阅读:(17) ⋅ 点赞:(0)

1 · 写在前面

在数据模型中,“追加”是个高频需求:日志拼接、状态堆栈、消息跟踪……如果把这些信息存在 RedisJSON 文档里,与其整段读出再写回,不如直接用 JSON.STRAPPEND 就地完成。本文将带你从语法、返回值到性能陷阱,全方位掌握 JSON.STRAPPEND 的使用,并配套一段 Go-Redis 代码示例,随取随用。

2 · 指令总览

指令 功能 复杂度
JSON.STRAPPEND key [path] value value 追加到指定路径的 JSON 字符串 O(1)(单路径) / O(N)(多路径,与键大小相关)
  • 插件要求:RedisJSON ≥ 1.0
  • ACL 标签@json @write @slow
  • 默认路径$(根)

3 · 详细语法

JSON.STRAPPEND <key> [<path>] <value>
参数 必填 说明
key 目标键名
path JSONPath;省略时为根 $
value 要追加的 JSON 字符串,支持追加到多路径

字符串写法陷阱
RedisJSON 需要 “字符串的字符串”:

  • 直接写 "hello" 会被解析成 JSON 字符串 hello
  • 若它又是数组元素,应再套一层:'"hello"'

4 · 返回值解读

执行结果为一个 整数数组,对应每条路径上目标字符串的新长度;若匹配值不是字符串则返回 nil

1) (integer) 6   # 路径1 的新长度
2) (integer) 8   # 路径2 的新长度
3) (nil)         # 路径3 不是字符串

5 · 核心示例

5.1 基础追加

redis> JSON.SET doc $ '{"a":"foo"}'
OK
redis> JSON.STRAPPEND doc $.a '"bar"'
1) (integer) 6
redis> JSON.GET doc $.a
"foobar"

5.2 多路径一次追加

redis> JSON.SET doc $ '{"a":"foo","nested":{"a":"hello"}}'
OK
redis> JSON.STRAPPEND doc $..a '"baz"'
1) (integer) 6   # $.a -> foobaz
2) (integer) 8   # $.nested.a -> hellobaz

5.3 非字符串路径

redis> JSON.SET doc $ '{"num":123}'
OK
redis> JSON.STRAPPEND doc $.num '"x"'
1) (nil)          # $.num 不是字符串

6 · 常见踩坑 & 对策

场景 症状 解决方案
路径值非字符串 返回 nil,数据未改变 先用 JSON.TYPE 检查或存储时保持类型一致
忘记双层引号 追加失败 / JSON 解析错误 对字面量字符串使用 '"text"'
一次性大量追加 文档变“胖”,查询变慢 定期归档旧字段或改用 Redis Stream
多路径 O(N) 时间 键很大时操作卡顿 精确指定路径;不要过度使用 $..a 通配

7 · Go-Redis 实战

// go.mod 需加入: github.com/redis/go-redis/v9
package main

import (
	"context"
	"fmt"
	"log"

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

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

	// 初始化文档
	_, err := rdb.Do(ctx, "JSON.SET", "sess:42", "$", `{"trace":"start"}`).Result()
	if err != nil { log.Fatal(err) }

	// 1️⃣ 追加一段日志
	res, err := rdb.Do(ctx, "JSON.STRAPPEND", "sess:42", "$.trace", `"-step1"`).Slice()
	if err != nil { log.Fatal(err) }
	fmt.Println("new length:", res[0]) //  (integer)

	// 2️⃣ 读回验证
	trace, _ := rdb.Do(ctx, "JSON.GET", "sess:42", "$.trace").Text()
	fmt.Println("trace =", trace)      // "start-step1"

	// 3️⃣ 错误示例:对非字符串追加
	_, err = rdb.Do(ctx, "JSON.STRAPPEND", "sess:42", "$", `"-oops"`).Result()
	fmt.Println("expected err:", err)  // nil,但返回 slice 中含 (nil)
}

生产小贴士

  • 批量操作时用 Pipeline 减少 RTT。
  • 若日志量大可考虑将 trace 切分到数组,用 JSON.ARRAPPEND 追加,查询时再 STRJOIN

8 · 与 JSON.SET/NX/XX 的协同

如果你需要 先写字段(不存在才写),然后持续追加,常见流程是:

  1. JSON.SET key $.log '"init"' NX —— 初始化
  2. 若返回 OK 或已存在
  3. JSON.STRAPPEND key $.log '"|stepX"' —— 追加

这样可保证字段类型先被固定为字符串,后续 STRAPPEND 不会因为类型错误返回 nil

9 · 性能与监控

  • 慢日志:JSON 字符串越长,追加越慢;关注 SLOWLOG 超阈值指令。
  • 阈值报警:使用 MONITOR 或 Keyspace 通知,当字段长度超过上限及时拆分。
  • 内存碎片化:大量追加会导致底层 realloc;可定期对冷数据执行 MEMORY PURGE(Redis 7.2+)。

10 · 小结

  • JSON.STRAPPEND 是 RedisJSON原地字符串追加利器,避免全量读写。
  • 当目标值不是字符串时会静默返回 nil,务必检查返回数组。
  • 复杂路径(如 $..field)会提升时间复杂度,生产中应尽量 精准定位
  • 在 Go-Redis 中调用 Do() 即可,无需额外封装;并配合 Pipeline 与类型校验写出安全高效的业务代码。

掌握了 JSON.STRAPPEND,你的 JSON 文档字符串操作将更加优雅、高效。动手试试,把日志、追踪信息或动态状态存进 RedisJSON,感受无需搬数据就能追加的丝滑体验吧!


网站公告

今日签到

点亮在社区的每一天
去签到