应避免直接用set存json字符串,因其不支持字段级操作、易并发覆盖、无格式校验;推荐优先使用redisjson模块(需确认已启用),次选hash扁平化存储。

直接存复杂 JSON 对象,别无脑用 SET + JSON.stringify() —— 除非你确定永远只整条读写、不改字段、不查嵌套、不担心并发覆盖。真要操作嵌套结构或高频更新子字段,String 类型就是给自己埋雷。
用 SET 存整个 JSON 字符串的硬伤
看似最简单,实则限制最多:
-
GET后必须全量反序列化才能取一个address.city,CPU 和带宽白耗 - 改一个字段得
GET → parse → mutate → stringify → SET,四步操作 + 两次网络往返,高并发下容易丢更新 - Redis 不校验 JSON 格式,
SET user:1001 '{"name":"Alice",}'(末尾多逗号)完全合法,直到业务层JSON.parse()才崩出SyntaxError: Unexpected token - 空值处理混乱:
null、undefined、"null"在序列化后语义全乱,前端解析时容易误判 - 无法做路径查询,比如“找出所有
status == "active"的用户”,只能遍历所有 key + 全量解析,O(n) 级别扫库
启用 RedisJSON 模块前必须确认的三件事
它不是 Redis 内置功能,很多环境默认没开:
- 连上 Redis 后执行
MODULE LIST,检查输出里有没有name:RedisJSON或name:rejson;没有就别往下试 - Docker 用户优先用官方镜像
redis/json,不是redis:alpine;云数据库(如阿里云、腾讯云 Redis)需单独确认是否开通 RedisJSON 插件,多数默认关闭 - 模块版本建议 ≥ v2.4,老版本(如 v1.x)对
$..通配路径支持不全,JSON.GET user:1001 $..city可能返回空
JSON.SET 和 JSON.GET 的参数陷阱
命令行为和原生 SET/GET 完全不同,错一个字符就失败:
-
JSON.SET user:1001 $ '{"name":"Alice","address":{"city":"Beijing"}}':第二个参数必须是 JSONPath 表达式,$是根节点,不能省略 -
JSON.GET user:1001 $.name返回"Alice"(带双引号),不是Alice;若要取原始字符串值,得在客户端再JSON.parse()一次 - 更新子字段时,值必须是合法 JSON 字符串:
JSON.SET user:1001 $.address.city '"Shanghai"'—— 注意外层单引号包裹、内层双引号保留;写成"Shanghai"或Shanghai都会报ERR invalid JSON -
JSON.DEL user:1001 $.tags删除字段后,该路径彻底消失;而HDEL user:1001 tags在 HASH 中只是删 field,语义更轻量
嵌套深、字段多但环境不支持 RedisJSON?用 HASH 扁平化代替
比 String 强在原子性和内存效率,比 RedisJSON 强在零依赖:
- 把
{"user":{"profile":{"name":"Alice","age":30},"settings":{"theme":"dark"}}扁平成HSET user:1001 profile:name "Alice" profile:age "30" settings:theme "dark" - 字段名含点号不影响
HSET,但部分旧客户端(如 Jedis 2.x)解析HGETALL结果时可能出错,统一用下划线更稳:profile_name -
HGET user:1001 profile_age返回字符串"30",业务层必须自己parseInt;数字精度丢失风险(如30.00000000000001被截断)得提前防御 - 不支持真正嵌套,但对大多数业务对象(用户资料、商品属性、配置项)已够用;
HLEN user:1001查字段数比STRLEN查 JSON 长度快得多,适合监控元数据
真正难的是权衡点:如果字段更新频率低、嵌套不超过两层、团队不熟悉 JSONPath,HASH 往往比强行上 RedisJSON 更可靠;而一旦需要 $..items[?(@.price > 100)] 这类动态查询,又没法说服运维加模块——那只能在应用层做缓存预计算,别把压力全推给 Redis。










