dot 快速未来的浪潮即将席卷您的城市。

加入我们,参加 Redis 发布会

精通 RediSearch / 第一部

我最近一直在使用 RediSearch 模块,它是在 Redis 生态系统中最令人着迷的发展之一,值得拥有自己的系列文章。

如果您构建了一个以 Redis 作为主要数据存储的应用程序,那么您可能已经体验过原生数据类型的喜悦和困惑。当您了解数据类型时,您会意识到您的许多数据都非常适合其中之一。但是,许多常见的应用程序模式都需要索引(“哪个键具有 x 值?”)和搜索(“哪个键包含 一些文本字符串?”)。虽然可以通过创造性地利用原生数据类型来回答这些问题,但代码可能很复杂,并且存在速度和/或空间效率方面的权衡。RediSearch 模块弥补了这些空白,并且几乎没有权衡。在本期内容中,我们将探索该模块的最基本内容,作为一种简短的介绍。

什么是模块?

模块是您的 Redis 服务器的附加组件。在最基本的层面上,它们实现新的命令,但它们也可以实现新的数据类型。模块是用系统编程语言编写的;C/C++、Rust 和 Golang 已经使用过,但其他语言也是可能的。由于它们是用编译语言编写的,因此可以实现极高的性能。

模块不同于 Redis 脚本(Lua),因为它们是系统中的一级命令,可以与存储直接交互,从而能够创建自己的数据类型。它们与内置命令唯一的区别是,模块命令使用前缀命名空间,通常是两个字母和一个点(例如:XX.SOMECOMMAND)。

模块可以通过 MODULE LOAD 随时加载,通过 redis.conf 文件中的 loadmodule 加载,或通过命令行参数“loadmodule”加载。我个人更倾向于通过配置文件加载它们,因为这可以确保它们始终可用,并且配置是可移植的。

什么是 RediSearch?

我一直在问自己 RediSearch 不是 什么 - 但我将尝试回答这个问题,而不会将其颠倒过来。RediSearch 是一个提供三个主要功能的模块

  • 全文搜索,
  • 二级索引,
  • 建议/自动完成引擎。

RediSearch 利用其自己的数据类型和内置的 Redis 数据类型。这样,它更像是一个使用 Redis 并与 Redis 共同驻留的解决方案。现在这可能看起来很混乱,但请耐心听我讲。

让我们从上述功能中评估每个功能。首先,考虑 全文搜索。使用 RediSearch,您可以索引尚未处理的文本。假设您有一百万条客户评论的列表,您想要找到所有提及“渲染”的评论。在 RediSearch 之前,您当然可以将这些评论存储在 Redis 中(例如,在哈希中),但是查找这些评论中的特定单词充其量是困难的。即使您设法构建了自己的词语到评论的索引(这涉及在应用程序级别将每个评论拆分为词语),匹配也需要是精确的 - “render”、“rendering”和“rendered”不会相互匹配。相反,通过使用 RediSearch 存储数据,您可以找到所有评论,而无需在应用程序级别执行任何特殊操作,它会自动将“rendered”与“rendering”匹配,因为它可以智能地处理索引和查询。

显然,如果可以执行上述操作,那么也可以在没有语言处理智能的情况下执行 - 当您开始思考这一点时,您开始意识到 RediSearch 可以用作通用二级索引。但是,也可以超越文本匹配 - RediSearch 可以对单个项目(称为“文档”)执行数字和地理索引。每个文档可以有多个字段 - 每个字段都有单独的属性。

最后,在某种程度上独立于上述内容,RediSearch 提供了一个建议引擎,可以驱动类似自动完成的服务。这使您可以获取已知的有效值,并为用户提供“提示”。它基于前缀模型,因此如果用户开始输入“Hamb”,建议引擎将提供“Hamburger”、“Hambone”和“Hamburg”等。重要的是要注意,这些建议没有直接与搜索结果集成,因此您的应用程序需要负责将它们添加或删除到该建议存储中。

实践

作为实践练习,让我们安装该模块

$ git clone https://github.com/RedisLabsModules/RediSearch.git
$ cd RediSearch
$ make all
$ cd src
$ redis-cli
> MODULE LOAD ./redisearch.so

(或者将其安装在您的 redis.conf 文件中,然后重新启动 redis-server)

加载模块后,在 redis-cli 中运行以下命令以验证模块是否正在运行

> module list
1) 1) "name"
   2) "ft"
   3) "ver"
   4) (integer) 2000

在该命令的结果中,您应该看到您安装的每个模块的条目(可能只有一个)。其中一个条目的“name”字段应该读作“ft”(表示 全文)。这就是 RediSearch 的识别方式和命令前缀。您的版本号可能与我的不同,该模块的开发速度很快。

现在该模块已启动并运行,最好为这些练习从一个干净的数据库开始(flushdb 或一个干净的数据库/实例)。首先,让我们创建一个索引并添加一个项目

> FT.CREATE shakespeare SCHEMA line TEXT SORTABLE play TEXT NOSTEM speech NUMERIC SORTABLE speaker TEXT NOSTEM entry TEXT location GEO

这可能看起来有点复杂,尤其是如果您习惯于使用 1 或 2 个参数的命令。让我们将其分解

FT.CREATE shakespeare
这只是命令和“键”(稍后将详细介绍)。

SCHEMA
这表示以下参数将与搜索索引中的字段有关。

line TEXT SORTABLE
在这里,我们创建了一个名为 line 的字段,它保存文本值,稍后将进行排序。

play TEXT NOSTEM
这是名为“play”的字段,用于保存文本值,但不会进行词干提取(例如,rendering 不会与 render 匹配)。

speech NUMERIC SORTABLE
我们创建了一个名为“speech”的字段,它是数值并且可以排序。

speaker TEXT NOSTEM
play 字段一样,speaker 字段将保存仅进行精确匹配(逐字匹配)的文本。

entry TEXT
该字段 (entry) 保存进行精确匹配或词干提取处理的文本值。

location GEO
location 字段保存地理坐标。

您看 - 只是在一行中包含很多内容,但实际上并不复杂。

现在,让我们将一个文档添加到我们的索引中

> FT.ADD shakespeare 57956 1 FIELDS text_entry "Out, damned spot! out, I say!--One: two: why," line "5.1.31" play macbeth speech 15  speaker "LADY MACBETH" location -3.9264,57.5243

比较这两个命令,您可能会注意到 FT.CREATE 和 FT.ADD 命令遵循类似的模式。让我们更深入地了解该命令

FT.ADD shakespeare 57956 1
我们将一个 ID 为 57956 的文档添加到索引 (shakespeare) 中。请注意,在此命令中,文档 ID 是一个数字(只是我使用的数据集的一个特征),但它可以是任何有效的 Redis 键。本节中的最后一个参数是权重 - 我们将在本系列的后面部分介绍它,但现在您只需要知道它可以介于 0 和 1 之间,而 1 是一个不错的默认值。

FIELDS …
“FIELDS”表示我们将以 [fieldname] [value] 重复模式指定文档的字段。请注意,当值为单个单词或数字时,您不需要使用引号,但如果您使用空格或其他奇特字符,请将您的值用引号括起来。另一个特殊的字段是位置字段,它包含一组坐标 (经度, 纬度)

RediSearch 键的奇特情况

回想一下,我们使用键“shakespeare”(通过 FT.CREATE 命令)创建了一个索引。让我们做一个快速实验

> TYPE shakespeare
none

很奇怪,对吧?从这里开始,我们将偏离正常的 Redis 行为,您将开始看到 RediSearch 是一种既使用 Redis 又与 Redis 集成的解决方案。

如果您在非生产数据库上运行此命令,让我们出于调试目的执行 KEYS *

> KEYS *
1) "ft:shakespeare/1"
2) "ft:shakespeare/31"
3) "idx:shakespeare"
4) "ft:shakespeare/5"
5) "ft:shakespeare/macbeth"
6) "ft:shakespeare/lady"
7) "nm:shakespeare/speech"
8) "geo:shakespeare/location"
9) "57956"

运行两个命令产生了 9 个键。我想要重点介绍其中一些键,以更全面地了解这里实际发生了什么

> TYPE idx:shakespeare
ft_index0

在这里,我们可以看到 RediSearch 使用其自己的数据类型创建了一个键(ft_index0)。我们无法直接对该键执行太多操作,但重要的是要知道它存在以及它是如何创建的。

现在,让我们看看键 57956

> TYPE 57956
hash

一个哈希!我们可以使用它 - 让我们直接查看该键

> HGETALL 57956
 1) "text_entry"
 2) "Out, damned spot! out, I say!--One: two: why,"
 3) "line"
 4) "5.1.31"
 5) "play"
 6) "macbeth"
 7) "speech"
 8) "15"
 9) "speaker"
10) "LADY MACBETH"
11) "location"
12) "-3.9264,57.5243"

这看起来应该很熟悉,因为它是您来自 FT.ADD 命令的数据,密钥只是您的文档 ID。虽然了解它是如何存储的非常重要,但不要直接使用 HASH 命令来操作此密钥。

> TYPE nm:shakespeare/speech
numericdx

有趣的是,我们数据集中名为 speech 的字段是一个数字索引,类型为“numericdx”。同样,由于这是一个 RediSearch 原生数据类型,我们无法使用任何“正常”Redis 命令来操作它。

> TYPE geo:shakespeare/location
zset

这里的关键为您提供了一个提示,虽然 TYPE 命令返回它是一个 ZSET,但 Redis 地理哈希集存储为 ZSET,并且在查询类型时会报告为 ZSET。话虽如此,让我们看一下几个 GEO 命令。

> GEOHASH geo:shakespeare/location 1
1) "gfjpnxuzk40"
> GEOPOS geo:shakespeare/location 1
1) 1) "-3.92640262842178345"
   2) "57.52429905544970268"

太棒了!RediSearch 已将坐标存储在一个标准的 GEO 集合中。但是,与上面的哈希一样,不要使用 ZSET 或 GEO 命令直接修改这些值。

最后,让我们看一下另一个键。

> TYPE ft:shakespeare/lady
ft_invidx

敏锐的读者可能会注意到“lady”这个词只在全文本字段 (speaker) 中被索引。存储在 ft_invidx 键中的数据是文本索引。

现在我们已经了解了 RediSearch 如何存储我们的数据,我们可以开始将更多实质性信息加载到数据库中并探索查询,但这将不得不等到几周后发布的 RediSearch 精通指南第二部分。