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 的协同
如果你需要 先写字段(不存在才写),然后持续追加,常见流程是:
JSON.SET key $.log '"init"' NX
—— 初始化- 若返回
OK
或已存在 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,感受无需搬数据就能追加的丝滑体验吧!