简而言之:我们利用功能强大的 Redis 模块 API 从头开始构建了一个超级快速且功能丰富的搜索引擎,它拥有自己的自定义数据类型和算法。
今年早些时候,Redis 的作者 Salvatore Sanfilippo 开始开发一种方法,使开发人员能够使用新的功能扩展 Redis:Redis 模块 API。此 API 允许用户使用原生 C 编写代码,向 Redis 添加新命令和数据类型。在 Redis,我们很快开始使用模块进行实验,将 Redis 带到前所未有的新领域。
我们探索的一个超级激动人心但不太简单的案例是构建搜索引擎。Redis 已经通过客户端库被许多用户用于搜索(我们甚至发布了自己的搜索客户端库)。但是,我们知道 Redis 模块的力量将使搜索功能和性能能够实现这些库无法提供的功能。
因此,我们开始开发一个有趣的搜索引擎实现,它使用 C 从头开始构建,并使用 Redis 作为框架。结果,RediSearch 现在作为一个开源项目提供。
其主要功能包括
让我们使用这个使用 redis-cli 工具的示例来看看 RediSearch 的一些关键概念
使用 RediSearch 的第一步是创建索引定义,它告诉 RediSearch 如何处理我们将添加的文档。
我们使用 FT.CREATE 命令,其语法如下
FT.CREATE <index_name> <field> [<score>|NUMERIC] …
或者在我们这个例子中
FT.CREATE products name 10.0 description 1.0 price NUMERIC
这意味着 name 和 description 被视为文本字段,分别具有 10 和 1 的分数,而 price 是用于过滤的数值字段。
现在,我们可以使用 FT.ADD 命令将新产品添加到我们的索引中
FT.ADD <index_name> <doc_id> <score> FIELDS <field> <value> …
添加产品
FT.ADD products prod1 1.0 FIELDS
name “Acme 40 英寸 1080p LED 电视”
description “享受增强色彩和清晰度的惊人全高清 1080p”
price 277.99
该产品会立即添加到索引中,现在可以在将来的搜索中找到。
现在我们已经将产品添加到索引中,搜索非常简单
FT.SEARCH products “full hd tv” LIMIT 0 10
或使用价格过滤,介于 200 美元和 300 美元之间
FT.SEARCH products “full hd tv” FILTER price 200 300
结果在 redis-cli 中显示为
1) (integer) 1
2) “prod1”
3) 1) “name”
2) “Acme 40 英寸 1080p LED 电视”
3) “description”
4) “享受增强色彩和清晰度的惊人全高清 1080p,分辨率是标准高清电视的两倍”
5) “price”
6) “277.99”
我们在 RediSearch 中的第一个设计选择是使用自定义数据结构对 倒排索引 进行建模。这允许更有效地编码数据,并且可能更快地遍历和交叉索引。我们选择简单地使用 Redis 字符串键作为二进制编码索引,并在搜索时直接从内存中读取它们,这种技术在模块 API 中被称为字符串 DMA。另一方面,我们使用普通的旧 redis 哈希对象保存文档内容。
我们使用结合了 增量编码 和 Varint 编码 的技术在 Redis 中保存倒排索引,以对条目进行编码,从而最大限度地减少索引使用的空间,同时保持解压缩和遍历的效率。
RediSearch 的另一个重要功能是其自动完成或建议命令。这允许您创建加权术语字典,然后查询它们以获得对给定用户前缀的完成建议。例如,如果用户开始将术语“lcd tv”输入字典,则发送前缀“lc”将返回完整术语作为结果。
RediSearch 还允许模糊建议,这意味着即使用户在其前缀中输入了错误,您也可以获得对前缀的建议。这是通过使用 莱文斯坦自动机 实现的,允许高效地在字典中搜索所有术语,这些术语的 莱文斯坦距离 与某个术语或前缀的最大距离不超过某个值。
以下是在 RediSearch 中创建和搜索自动完成字典的示例
127.0.0.1:6379> FT.SUGADD completer “foo” 1
(integer) 1 127.0.0.1:6379> FT.SUGADD completer “football” 5
(integer) 2 127.0.0.1:6379> FT.SUGADD completer “fawlty towers” 7
(integer) 3 127.0.0.1:6379> FT.SUGADD completer “foo bar” 2
(integer) 4 127.0.0.1:6379> FT.SUGADD completer “fortune 500” 1
(integer) 5
127.0.0.1:6379> FT.SUGGET completer foo
1) “football”
2) “foo”
3) “foo bar”
127.0.0.1:6379> FT.SUGGET completer foo FUZZY
1) “football”
2) “foo”
3) “foo bar”
4) “fortune 500”
127.0.0.1:6379> FT.SUGGET completer faulty FUZZY
1) “fawlty towers”
为了评估 RediSearch 与其他开源搜索引擎的性能,我们创建了一组基准测试,测量延迟和吞吐量,同时对相同的数据集进行索引和查询。
以下是一些结果,显示 RediSearch 以显著优势优于其他开源搜索引擎(当然,与往常一样,基准测试结果应该谨慎对待。 查看链接的白皮书 以获取更多结果和关于基准测试的信息)
基准测试 1:简单的单字查询 - hello
基准测试 2:两个单词的查询 - barack obama
基准测试 3:自动完成 - 维基百科中排名前 1100 的 2-3 个字母的前缀