dot Redis 8 发布了,它是开源的

了解更多

使用 Redis 对 JSON 文档进行索引、查询和全文搜索

相关资源:点击下载 RedisJSON 模块

RedisJSON 和 RediSearch 是我们云中最受欢迎的 Redis 模块。(参见图 1)RedisJSONRediSearch(与 Redis 捆绑)的 Docker 镜像每天被拉取 2000 多次。这就是为什么我们认为 Redis 的技术布道师 Itamar Haber 在 4 年前写下第一个版本时是一位有远见的人。今年 4 月,我们在 RedisConf 上宣布了几项与 JSON、索引和全文搜索功能相关的消息。今天,我们很高兴宣布这些功能的私有预览版发布。

在这篇博客中,我们将概述当前的 RedisJSON 功能。之后,我们将深入探讨此私有预览版的新功能部分。使用 RediSearch 对 JSON 文档进行索引、查询和全文搜索的能力是此版本中最酷的新功能。最后,我们将向您展示如何快速上手。

modules by REC
图 1. 至少使用一个模块的 Redis Cloud 数据库(2021 年 5 月)

JSON 功能

当您没有 RedisJSON 时,您可以使用 String 数据结构在 Redis 中建模嵌套文档。

redis.cloud:6379> SET myDoc '{"colors": ["green"]}'
OK

但是,如果我们需要更新文档的子部分怎么办?

为了保持操作的原子性,我们需要

  1. WATCH 文档
  2. 读取上一个版本并反序列化
  3. 将更新嵌入到 Redis 事务中
  4. 序列化为 JSON 并更新文档
  5. 执行事务

如果在执行此操作期间有其他客户端更新了文档,我们可能需要重试所有这些步骤。

redis.cloud:6379> WATCH myDoc
OK
redis.cloud:6379> GET myDoc
"{\"colors\": \"green\"}"
redis.cloud:6379> MULTI
OK
redis.cloud:6379>(TX) SET myDoc '{"colors": ["green", "blue"]}'
QUEUED
redis.cloud:6379>(TX) EXEC
1) OK

然而,使用 RedisJSON,我们可以通过单个原子事务完成此更新

redis.cloud:6379> JSON.ARRAPPEND myDoc colors '"blue"'
(integer) 2

让我们看另一个例子,一个您拥有大型 JSON,但在应用程序中只需要该文档的子部分的情况。

没有 RedisJSON 时

您必须

  1. 检索整个 JSON 字符串,它是序列化后的字符串
  2. 反序列化 JSON
  3. 提取您需要的子部分
client.get("myDoc", function(err, reply) {
  const myJson = JSON.parse(myJsonString);
  const color = myJson.colors[0];
});

使用 RedisJSON,您只需一个命令即可检索所需的数据,从而最大限度地减少 CPU 周期、网络开销,最重要的是,降低延迟。

redis.cloud:6379> JSON.GET myDoc $.colors[0]
"\"green\""

正如您所见,RedisJSON 简化了 JSON 文档操作。RedisJSON 当前的 GA 版本(v1.0)是社区已经广泛使用的版本,它精确地解决了使用 String 数据结构建模嵌套结构的缺点。以下是其一些关键功能的概述。

在 Redis 中存储(或更新)与键关联的 JSON 文档

redis.cloud:6379> JSON.SET myDoc . '{"title": "css", "colors": ["green"]}'
OK

替换子部分(例如,键的字符串值)

redis.cloud:6379> JSON.SET myDoc title '"style"'
OK

向集合或映射添加项目

redis.cloud:6379> JSON.ARRAPPEND myDoc colors '"red"' '"blue"'
(integer) 3

提取整个文档

redis.cloud:6379> JSON.GET myDoc .
"{\"title\":\"css\",\"colors\":[\"green\"]}"

使用 JSONPath 的子集提取其中的一部分

redis.cloud:6379> JSON.GET myDoc colors[0]
"\"green\""

RedisJSON 2.0:私有预览版发布

我们在 RedisConf 2021 上宣布了此版本,今天我们很高兴地宣布它作为我们部分 Redis Enterprise 客户的私有预览版以及社区的发布候选版提供。此版本有三个主要功能,即全面支持 JSONPath 表达式、支持 Active-Active(通过 Redis Enterprise)以及使用 RediSearch 对 JSON 文档进行索引、查询和全文搜索的能力。但还有更多内容!让我们深入了解这些新特性。

使用 RUST 重写

系统编程语言是一类注重效率的语言。使用这些语言编写的程序通常轻量且提供最佳性能。这是 Redis 历史上使用 C 语言编写的原因。这也解释了为什么 Redis 能够实现极低的延迟和高吞吐量。大多数 Redis 模块都使用 C、C++ 或 Rust 编写,这些语言属于同一系列。

Rust 社区为 JSON 提供了特别好的支持,包括非常快速高效的 JSON 序列化JSONPath 实现。将这些实现的优势带给 Redis 用户是显而易见的,只需要在 Redis 模块 API 和 Rust 之间进行映射即可。

全面支持 JSONPath

这就是使用 RUST 重写的好处。这个新版本包含了对 JSONPath 的全面支持。现在可以使用 JSONPath 表达式的所有表达能力了。

给定一个 JSON 文档

redis.cloud:6379> JSON.SET myDoc $ '{"colors":["red", "blue", "green"]}'
OK

通配符(以前仅限于第一个项目)

redis.cloud:6379> JSON.GET myDoc $.colors[*]
"[\"red\",\"blue\",\"green\"]"

提取切片

redis.cloud:6379> JSON.GET myDoc $.colors[0:2]
"[\"red\",\"blue\"]"
redis.cloud:6379> JSON.GET myDoc $.colors[-1]
"["\"green\"]"

使用过滤表达式的更高级示例

redis.cloud:6379> JSON.SET myDoc $ '{"books": [{"title": "Peter Pan", "price": 8.95}, {"title": "Moby Dick", "price": 12.99}]}'

redis.cloud:6379> JSON.GET myDoc '$.books[?(@.price < 10)]'
"[{\"title\":\"Peter Pan\",\"price\":8.95}]"

支持 Active-Active

Active-Active 是 Redis Enterprise 提供的一项功能。Active-Active 允许您将数据库复制到多个地理分布的 Redis Enterprise 集群中。用户可以连接到最近的集群,享受本地读写延迟。

该实现基于无冲突复制数据类型CRDT)技术。在为 Redis 支持的大多数核心数据结构实现 CRDT 时,Redis 积累了丰富的知识和经验,这次为 JSON 实现的新功能也印证了这一点。

应用程序开发人员现在可以依靠此功能使用 JSON 文档构建地理分布式应用程序。以下是两个集群在 Active-Active 环境中一系列操作的示例

Clusters

让我们看看每个操作的详细信息

  • T1:客户端在集群 1 上设置一个 JSON 文档。
  • T2:同步过程将文档复制到集群 2。
  • T3:两个集群包含相同的文档。
  • T4:一个客户端在集群 1 的颜色数组中添加蓝色,同时,另一个客户端在集群 2 的同一数组中添加绿色。
  • T5:同步过程合并操作并更新两个集群上的文档。
  • T6:两个集群包含相同的文档。

RediSearch 2.2:私有预览版发布

这篇博客还宣布了 RediSearch 2.2 私有预览版的可用性(作为我们部分 Redis Enterprise 客户的私有预览版以及社区的发布候选版)。

在本节中,我们将描述 RediSearch 这个新版本提供的新功能。但首先,这里有一个原因,解释了为什么我们同时发布这两个流行的模块

对 JSON 文档进行索引、查询和全文搜索

这个特别的新功能将把 Redis 的 JSON 功能提升到一个全新的水平。RediSearch 不仅仅是一个 Key-Value 存储,它一直以来都在为哈希提供索引和搜索功能。在底层,RedisJSON 2.0 暴露了一个内部公共 API。之所以是内部的,是因为这个 API 暴露给运行在 Redis 节点内的其他模块。之所以是公共的,是因为任何模块都可以使用这个 API。RediSearch 2.2 也是如此!

通过将其功能暴露给其他模块,RedisJSON 使 RediSearch 能够索引 JSON 文档,因此用户现在可以通过索引和查询内容来查找文档。这些组合的模块为您提供了一个功能强大、低延迟、面向 JSON 的文档数据库

让我们看看它是什么样子的。

我们应该首先使用 JSON.SET 命令向数据库填充一个 JSON 文档。

redis.cloud:6379> JSON.SET myDoc $ '{"title": "foo", "content": "bar"}'
OK

要创建新索引,我们使用 FT.CREATE 命令。索引的 schema 现在接受 JSONPath 表达式。表达式的结果被索引并与属性(此处:title)相关联。

redis.cloud:6379> FT.CREATE myIdx ON JSON SCHEMA $.title AS title TEXT
OK

我们现在可以使用 FT.SEARCH 进行搜索查询并找到我们的 JSON 文档

redis.cloud:6379> FT.SEARCH myIdx "@title:foo"
1) (integer) 1
2) "myDoc"
3) 1) "$"
   2) "{\"title\":\"foo\",\"content\":\"bar\"}"

JSON 文档上的聚合

聚合是 RediSearch 的一项强大功能,可用于创建分析报告或执行分面搜索式查询。既然 RediSearch 可以访问 JSON 文档,就可以使用 JSONPath 表达式从 JSON 文档中加载任何值,并在管道中使用它,无论该值是否已索引。

让我们创建一个索引

redis.cloud:6379> FT.CREATE myIdx ON JSON SCHEMA $.user.name AS name TEXT
OK

将 JSON 文档添加到数据库

redis.cloud:6379> JSON.SET myDoc . '{"user":{"name":"John 
Smith","hp":1000, "dmg":150}}'
OK

并使用从 JSON 文档中提取的两个数值进行简单计算

redis.cloud:6379> FT.AGGREGATE myIdx '*' LOAD 6 $.user.hp AS hp $.user.dmg AS dmg APPLY '@hp - @dmg' AS points
1) (integer) 1
2) 1) "point"
   2) "850"

索引策略的更多灵活性

使用新版本的 RediSearch,现在可以使用不同的参数索引相同的值(哈希上的字段,或 JSON 文档中的 JSON 值)。这是一个典型的用例,通过此新功能得以解决

让我们有一个包含属于不同类别的文档的数据库。

redis.cloud:6379> HSET myDoc category "foo,bar,hello world"
(integer) 1

使用 TAG 类型,您可以轻松地根据任何类别过滤搜索结果

redis.cloud:6379> FT.CREATE myIdx ON HASH SCHEMA category TAG
OK
redis.cloud:6379> FT.SEARCH myIdx "@category:{foo}"
1) (integer) 1
2) "myDoc"
3) 1) "category"
   2) "foo,bar,hello world"

但是,如果您还想对类别进行全文搜索怎么办?

redis.cloud:6379> FT.SEARCH myIdx "@category:{foo} @category:(hello)"
1) (integer) 0

到目前为止,使用哈希时,您必须将值复制到两个字段中,这将消耗两倍的内存。

这就是 FT.CREATE...AS 变得非常方便的地方。让我们回到我们简洁的文档

redis.cloud:6379> HSET myDoc category "foo,bar,hello world"
(integer) 1

......并使用新的 AS 功能

redis.cloud:6379> FT.CREATE myIdx ON HASH SCHEMA category TAG category 
AS cat_txt TEXT
OK

......然后......

redis.cloud:6379> FT.SEARCH myIdx "@category:{foo} @cat_txt:(hello)"
1) (integer) 1
2) "myDoc"
3) 1) "category"
   2) "foo,bar,hello world"

中了!我们现在可以通过标签进行过滤,并在同一字段中进行全文搜索,无需复制数据。

查询性能分析

大多数 Redis 命令的时间复杂度都有详细记录。例如,HMGET 的复杂度为 O(N),“其中 N 是请求的字段数量”。使用 RediSearch,可以编写高级查询。然而,FT.SEARCH 和 FT.AGGREGATE 命令的复杂度取决于查询的复杂性。

我们希望为您提供工具,帮助您了解查询执行时底层发生了什么,找出时间花费在哪里,以及如何优化查询。新的 FT.PROFILE 命令返回一个树状结构,显示 RediSearch 执行查询所使用的主要步骤。每个步骤都提供了时间信息。

那么,当我们使用模糊搜索进行查询时,RediSearch 内部会发生什么呢?

让我们看一个例子

redis.cloud:6379> HSET doc:1 text "hello world"
(integer) 1

redis.cloud:6379> HSET doc:2 text "hallo world" 
(integer) 1

redis.cloud:6379> FT.CREATE idx ON HASH SCHEMA text TEXT
OK

我们已准备好分析查询。让我们运行分析并分解分析结果。

redis.cloud:6379> FT.PROFILE idx SEARCH LIMITED QUERY "%hello%"

首先我们得到结果。这对于检查分析查询是否返回预期结果很有用。

1) 1) (integer) 2
   2) "doc:2"
   3) 1) "text"
      2) "hallo world"
   4) "doc:1"
   5) 1) "text"
      2) "hello world"

这是总时间,称为“分析时间”,因为它包括收集分析信息所花费的时间。

2) 1) 1) Total profile time
      2) "1.552"

解析查询和构建执行计划所花费的时间

2) 1) Parsing time
      2) "0.90900000000000003"
   3) 1) Pipeline creation time
      2) "0.105"

在字典中查找模糊匹配所花费的时间

4) 1) Iterators profile
      2)  1) Type
          2) UNION
          3) Query type
          4) "FUZZY - hello"
          5) Time
          6) "0.025999999999999999"
          7) Counter
          8) (integer) 2
          9) Child iterators
         10) "The number of iterators in the union is 2"

最后,您是否曾经想过构建搜索结果意味着什么?我们需要计算每个文档的全文得分,按得分排序,最后加载字段。有了这些信息,您可以识别瓶颈,加快查询速度,并提高服务器性能。

5) 1) Result processors profile
      2) 1) Type
         2) Index
         3) Time
         4) "0.040000000000000001"
         5) Counter
         6) (integer) 2
      3) 1) Type
         2) Scorer
         3) Time
         4) "0.026000000000000002"
         5) Counter
         6) (integer) 2
      4) 1) Type
         2) Sorter
         3) Time
         4) "0.032000000000000001"
         5) Counter
         6) (integer) 2
      5) 1) Type
         2) Loader
         3) Time
         4) "0.255"
         5) Counter
         6) (integer) 2

如何开始

我们相信这些新功能对应用程序开发人员和 Redis 社区来说是游戏规则的改变者。以下是您如何开始。

使用预览版的 Docker 镜像

要开始使用,您可以使用 :preview 标签拉取以下Docker 镜像

docker run -p 6379:6379 redis/redismod:preview

或者,您可以从两个仓库的 RC1 发布标签(RediSearch 的 v2.2.0,RedisJSON 的 v2.0.0)编译,并将它们加载到 Redis 中。

启动并运行后,您可以尝试上述所有命令,或者参考这篇快速入门指南。我们还将推出一系列关于 RedisMart 的博客,这是一个在线零售应用程序,我们在 RedisConf 2021 的主题演讲中进行了展示。RedisMart 利用 RediSearch 和 RedisJSON 以地理分布式方式部署,以提供最佳的在线零售体验。在此系列中,我们将逐步指导您构建此应用程序。

使用兼容客户端的最新版本进行开发

以下客户端列表目前正在升级,以便您能够以良好的开发体验使用新功能。请查看最新版本和/或拉取请求(目前大多数客户端的主分支都支持预览版)。

RedisJSONRediSearch
Node.jsredis-modules-sdkredis-modules-sdk
JavaJredisJSONJRediSearch
.NETNRedisJSONNRediSearch
Pythonredisjson-pyredisearch-py

加入社区

在努力迈向正式发布的同时,我们欢迎任何反馈、错误报告和功能请求。您可以在文档网站或 RediSearch(在 Github 上)或 RedisJSON(在 Github 上)的 Github 仓库中留下反馈,或者在 Discord 上与我们联系。