RedisJSON 的 `JSON.ARRAPPEND`一行命令让数组动态生长
1 、 为什么选择 JSON.ARRAPPEND
在传统的键值模型里,若要往数组尾部追加元素,通常需要 取→改→写 三步:
GET
整个 JSON;- 在应用层把元素 push 进数组;
SET
回 Redis。
一条 JSON.ARRAPPEND
则可一次完成,具备三个显著优势:
- 原子性:整个追加在 Redis 内部完成,无并发写冲突。
- 网络开销极小:一次往返即可。
- O(1) 或 O(N) 级别时间复杂度:当路径只命中单个数组时为常数时间;若路径匹配多处才与数组大小成正比 ([AWS 文檔][1])。
2 、语法速查
JSON.ARRAPPEND <key> <path> <value> [value ...]
- key:待修改的 Redis 键。
- path:JSONPath,默认为
$
。 - value:一个或多个 JSON 值,依次附加到目标数组尾部。
字符串陷阱
要往数组里追加字符串,需再包一层外引号,如:'"blue"'
。这是因为 Redis CLI 会把参数当作原始文本;再套一层可避免 Redis 解析器把内部引号剥掉 ([Upstash: Serverless Data Platform][2])。
返回值
与路径一一对应的整型数组,元素为追加后的数组长度;若匹配到的值不是数组,则对应位置返回 nil
([AWS 文檔][1])。
3、动手实践:为商品新增颜色
# ① 创建文档
JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","colors":["black","silver"]
}'
# ② 追加 "blue"
JSON.ARRAPPEND item:1 $.colors '"blue"'
# -> (integer) 3
# ③ 验证
JSON.GET item:1 $.colors
# -> [["black","silver","blue"]]
以上流程仅用一次网络往返就完成数组更新,且返回值 3 表示数组新长度。
4、跨语言示例
4.1 Python(redis-py ≥ 5.0)
from redis import Redis
from redis.commands.json.path import Pathr = Redis(host="localhost", port=6379, decode_responses=True)
r.json().set("item:1", Path.root_path(), {"colors": ["black", "silver"]})
new_len = r.json().arrappend("item:1", Path(".colors"), "blue")
print("新长度:", new_len) # -> [3]
4.2 Node.js(@redis/client)
import { createClient } from 'redis';const client = createClient();
await client.connect();await client.json.set('item:1', '$', { colors: ['black', 'silver'] });
const [len] = await client.json.arrAppend('item:1', '$.colors', 'blue');
console.log(len); // 3
4.3 Go(go-redis/v9)
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
_, _ = rdb.Do(ctx, "JSON.SET", "item:1", "$", `{"colors":["black","silver"]}`).Result()
len, _ := rdb.Do(ctx, "JSON.ARRAPPEND", "item:1", "$.colors", `"blue"`).IntSlice()
log.Println(len[0]) // 3
5、性能与内存考量
场景 | 建议 | 说明 |
---|---|---|
单用户操作 | 直接调用 JSON.ARRAPPEND | O(1),延迟可忽略 |
批量写入 | 使用 Pipeline 或 MULTI/EXEC | 减少 RTT,可把 1 ms 延迟压到 0.2 ms |
高并发写同一键 | 利用 Redis 分区键 或 Sharding | 避免热点,单核 RDBGIL 成为瓶颈 |
极大数组(>10 MB) | 考虑拆分文档或转 KV 列表 | 巨型 JSON 会拖慢序列化与 AOF |
内存层面,RedisJSON 内部以二进制树存储 JSON,每个值至少 8 bytes ([DEV Community][3])。大量小数组元素时,务必关注 RSS。
6、常见误区 & 排错
现象 | 根因 | 解决方案 |
---|---|---|
返回 nil | 路径指向的值不是数组 | 确认 JSON.TYPE key path |
数组里出现多层引号 | 字符串未正确包双引号 | 改用 '"text"' |
CLI 追加 JSON 对象失败 | 参数被 shell 吞字符 | 用 --raw 或文件重定向 |
性能突降 | 路径命中多处,复杂度 O(N) | 精确路径或重构文档 |
7、进阶话题
- 与
JSON.ARRINSERT
、JSON.ARRPOP
搭配
ARRAPPEND
只能尾插,若需头插可用ARRINSERT key path 0 value
,同时ARRPOP
支持按索引弹出。 - 结合 RedisSearch
给数组字段加TAG
/TEXT
索引,追加后可实时被搜索,无需重建索引。 - 多路径写操作
JSON.ARRAPPEND key $..colors '"red"' '"green"'
一次更新多处,但复杂度 ≥ O(N)。 - 幂等写法
若不希望重复追加,可先JSON.ARRINDEX
检查元素是否已存在。
8、结语
JSON.ARRAPPEND
把「往数组尾部追加元素」这件小事做到极致简单,却蕴含了 RedisJSON 的设计哲学:把常见的 JSON 变更下沉到数据库层,用原子指令解除并发焦虑。
掌握其用法、理解性能边界、配合其他 JSON 指令组合拳,你就能在高吞吐实时应用中优雅地操纵复杂文档结构。