文档:Valkey JSON

在 Valkey 中,JSON 数据类型和命令在 valkey-json 模块中实现,这是一个兼容 Valkey 8.0 及更高版本的官方 Valkey 模块。用户需要将此模块加载到其 Valkey 服务器上才能使用此功能。

Valkey JSON 是一个用 C++ 编写的 Valkey 模块,为 Valkey 提供原生的 JSON (JavaScript Object Notation) 支持。该实现符合 RFC7159ECMA-404 JSON 数据交换标准。用户可以使用 JSONPath 查询语言原生存储、查询和修改 JSON 数据结构。查询表达式支持高级功能,包括通配符选择、过滤表达式、数组切片、联合操作和递归搜索。

Valkey JSON 命令示例

  • JSON.SET 在指定路径设置 JSON 值。
  • JSON.GET 获取一个或多个路径上的序列化 JSON。
  • JSON.ARRINSERT 在指定路径的数组值中,在指定索引前插入一个或多个值。
  • JSON.ARRLEN 获取指定路径下数组的长度。

查看 valkey-json 命令的完整列表

Valkey JSON 的常见用例

在 valkey-json 模块出现之前,在 Valkey 中存储 JSON 通常涉及将其序列化为字符串或使用哈希数据类型。这种方法使得处理嵌套数据或执行局部更新变得困难。valkey-json 通过启用原生 JSON 支持解决了这些限制。

Valkey JSON 提供了一种高效存储和操作结构化数据的方法。其主要优点包括快速搜索和过滤功能。它还提供了对 JSON 数据执行就地更新的能力,而无需覆盖整个文档。这些功能使您能够高效地查询、修改和管理复杂的数据结构,使其成为需要动态和灵活数据存储的应用程序的理想选择。

JSON 属性

  • 最大文档大小 - valkey-json 允许配置单个 JSON 键的大小限制,以防止恶意或无限制插入(例如,通过 JSON.ARRINSERT)可能导致的内存不足问题。默认情况下,此值为 0,表示没有限制。您可以使用 CONFIG SET json.max-document-size <value> 命令设置此限制。使用 JSON.DEBUG MEMORY <key>MEMORY USAGE <key> 检查内存使用情况。

  • 最大深度 - JSON 对象和数组的最大嵌套级别。如果一个 JSON 对象或数组包含另一个对象或数组,则视为嵌套。默认允许的最大嵌套深度为 128。任何尝试超出此限制的操作都将导致错误。您可以使用以下命令调整此限制:CONFIG SET json.max-path-limit <value>,其中 value 是所需的深度限制。

性能

Valkey 中 JSON 操作的性能主要受 JSONPath 评估的复杂性以及 JSON 对象的大小或深度的影响。大多数操作(包括 JSON.GETJSON.SETJSON.DELJSON.NUMINCRBYJSON.STRAPPENDJSON.ARRAPPEND)在使用直接路径时通常是 O(1) 的复杂度,但当匹配的路径数量为 N 时,复杂度可能会增加到 O(N)。像 $.user.name 这样更简单的路径比诸如 $..[?(@.active==true)] 等递归或过滤查询的执行速度显著更快。

为了获得最佳性能,建议尽量减少过度嵌套,避免频繁修改深层嵌套对象,并谨慎使用路径过滤器。

JSON ACL

  • 与现有的按数据类型分类(@string、@hash 等)类似,新增了一个 @json 类别,以简化对 JSON 命令和数据的访问管理。没有其他现有 Valkey 命令属于 @json 类别。所有 JSON 命令都强制执行任何键空间或命令限制和权限。

  • 有 4 个现有的 Valkey ACL 类别已更新以包含新的 JSON 命令:@read、@write、@fast、@slow。下表显示了 JSON 命令与相应类别的映射关系。

对于每个类别和每个命令的行,如果单元格包含“y”,则表示该命令已添加到该类别中。

JSON 命令@json@read@write@fast@slow
JSON.ARRAPPENDyyy
JSON.ARRINDEXyyy
JSON.ARRINSERTyyy
JSON.ARRLENyyy
JSON.ARRPOPyyy
JSON.ARRTRIMyyy
JSON.CLEARyyy
JSON.DEBUGyyy
JSON.DELyyy
JSON.FORGETyyy
JSON.GETyyy
JSON.MGETyyy
JSON.MSETyyy
JSON.NUMINCRBYyyy
JSON.NUMMULTBYyyy
JSON.OBJKEYSyyy
JSON.OBJLENyyy
JSON.RESPyyy
JSON.SETyyy
JSON.STRAPPENDyyy
JSON.STRLENyyy
JSON.TOGGLEyyy
JSON.TYPEyyy

路径语法

Valkey JSON 支持两种路径语法:

  • 增强语法 – 遵循 Goessner 描述的 JSONPath 语法,如下表所示。我们对表中的描述进行了重新排序和修改以提高清晰度。
  • 受限语法 – 查询功能有限。

如果查询路径以 $ 开头,则使用增强语法。否则,使用受限语法。建议您在新开发中使用增强语法。

增强语法符号与表达式

符号/表达式描述
$根元素。
.[]子操作符。
..递归下降。
*通配符。对象或数组中的所有元素。
[]数组下标操作符。索引从 0 开始。
[ , ]联合操作符。
[start:end:step]数组切片操作符。
?()将过滤器(脚本)表达式应用于当前数组或对象。
()过滤表达式。
@用于引用正在处理的当前节点的过滤表达式。
==等于,用于过滤表达式。
!=不等于,用于过滤表达式。
>大于,用于过滤表达式。
>=大于或等于,用于过滤表达式。
<小于,用于过滤表达式。
<=小于或等于,用于过滤表达式。
&&逻辑与,用于组合多个过滤表达式。
||逻辑或,用于组合多个过滤表达式。

示例
以下示例基于 Goessner 的示例 XML 数据构建,我们通过添加额外字段对其进行了修改。

{ "store": {
    "book": [ 
      { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95,
        "in-stock": true,
        "sold": true
      },
      { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99,
        "in-stock": false,
        "sold": true
      },
      { "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99,
        "in-stock": true,
        "sold": false
      },
      { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99,
        "in-stock": false,
        "sold": false
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95,
      "in-stock": true,
      "sold": false
    }
  }
}
路径描述
'$.store.book[*].author'商店中所有图书的作者。
'$..author'所有作者。
'$.store.*'商店的所有成员。
'$["store"].*'商店的所有成员。
'$.store..price'商店中所有商品的价格。
'$..*'JSON 结构的所有递归成员。
'$..book[*]'所有书籍。
'$..book[0]'第一本书。
'$..book[-1]'最后一本书。
'$..book[0:2]'前两本书。
'$..book[0,1]'前两本书。
'$..book[0:4]'索引从 0 到 3 的书籍(不包含结束索引)。
'$..book[0:4:2]'索引 0 和 2 的书籍。
'$..book[?(@.isbn)]'所有带有 ISBN 号的书籍。
'$..book[?(@.price < 10)]'所有价格低于 $10 的书籍。
'$..book[?(@["price"] < 10)]'所有价格低于 $10 的书籍。(替代语法)
'$..book[?(@.["price"] < 10)]'所有价格低于 $10 的书籍。(替代语法)
'$..book[?(@.price >= 10 && @.price <= 100)]'所有价格在 $10 到 $100 之间(含两端)的书籍。
'$..book[?(@.sold == true || @.in-stock == false)]'所有已售出或缺货的书籍。
'$.store.book[?(@.["category"] == "fiction")]'所有小说类书籍。
'$.store.book[?(@.["category"] != "fiction")]'所有非小说类书籍。

更多过滤表达式示例

127.0.0.1:6379> JSON.SET k1 . '{"books": [{"price":5,"sold":true,"in-stock":true,"title":"foo"}, {"price":15,"sold":false,"title":"abc"}]]: '
OK
127.0.0.1:6379> JSON.GET k1 $.books[?(@.price>1&&@.price<20&&@.in-stock)]
"[{\"price\":5,\"sold\":true,\"in-stock\":true,\"title\":\"foo\"]: ]"
127.0.0.1:6379> JSON.GET k1 '$.books[?(@.price>1 && @.price<20 && @.in-stock)]'
"[{\"price\":5,\"sold\":true,\"in-stock\":true,\"title\":\"foo\"]: ]"
127.0.0.1:6379> JSON.GET k1 '$.books[?((@.price>1 && @.price<20) && (@.sold==false))]'
"[{\"price\":15,\"sold\":false,\"title\":\"abc\"]: ]"
127.0.0.1:6379> JSON.GET k1 '$.books[?(@.title == "abc")]'
[{"price":15,"sold":false,"title":"abc"]: ]

127.0.0.1:6379> JSON.SET k2 . '[1,2,3,4,5]'
127.0.0.1:6379> JSON.GET k2 $.*.[?(@>2)]
"[3,4,5]"
127.0.0.1:6379> JSON.GET k2 '$.*.[?(@ > 2)]'
"[3,4,5]"

127.0.0.1:6379> JSON.SET k3 . '[true,false,true,false,null,1,2,3,4]'
OK
127.0.0.1:6379> JSON.GET k3 $.*.[?(@==true)]
"[true,true]"
127.0.0.1:6379> JSON.GET k3 '$.*.[?(@ == true)]'
"[true,true]"
127.0.0.1:6379> JSON.GET k3 $.*.[?(@>1)]
"[2,3,4]"
127.0.0.1:6379> JSON.GET k3 '$.*.[?(@ > 1)]'
"[2,3,4]"

受限语法符号与表达式

符号/表达式描述
.[]子操作符。
[]数组下标操作符。索引从 0 开始。
路径描述
'.store.book[0].author'第一本书的作者。
'.store.book[-1].author'最后一本书的作者。
'.address.city'城市名称。
'["store"]["book"][0]["title"]'第一本书的标题。
'["store"]["book"][-1]["title"]'最后一本书的标题。

常见错误前缀

每个错误消息都有一个前缀。以下是常见错误前缀的列表。

前缀描述
ERR一般错误。
LIMIT当超出大小限制时发生的错误。例如,文档大小限制或嵌套深度限制被超出。
NONEXISTENT键或路径不存在。
OUTOFBOUNDARIES数组索引超出范围。
SYNTAXERR语法错误。
WRONGTYPE值类型错误。
信息描述
json_total_memory_bytes分配给 JSON 对象的总内存。
json_num_documentsValkey 中的文档总数

要查询核心指标,请运行以下命令:info json_core_metrics