序言

各位观众老爷们好,很荣幸能邀请到我司虚拟化平台组的研发同学 Vici 做一些在产品中使用 redisearch 的心得分享(加戏)。

「搜索」是很多产品中无法绕开的一个功能模块,smartx 的虚拟化管理平台也提供了对主机,存储等对象的查询功能。近期的研发工作中,我们引入了 redisearch 来优化查询效果。

希望通过阅读本文,能让研发同学更加深入的了解 redisearch 模块一种高效经济的文本检索方案。

全文大约 7000 字,阅读需要 10-15分钟。

0. 为什么是 Redisearch

在引入全文检索之前,我们使用 mongoDB 内置的 正则匹配搜索文本。为了提升性能(正则搜索大部分情况下无法使用索引。)和引入语义搜索功能 (如搜索虚拟机 描述文档的内容), 正则匹配的方式已经远远不够了。我们优先考虑了 mongodb 自带的 text 索引和 search 关键字,考虑到当前客户环境的mongodb 版本较低(2.6),升级会引入额外成本。同时 text索引会给客户环境带来额外的内存压力。所以我们希望能找到一种独立的全文检索方案。

业界常用 elasticsearch 和 lucene 方案都对 JVM 的运行时内存有著最低限额,笔者在选型测试时优先测试了 elasticearch ,其运行时大小推荐 2G 以上的内存空间,并且需要额外的磁碟空间做持久化存储。

相对照的,我们还测试了 redisearch 的内存占用情况和中文分词功能的可用性。

如下结构的测试数据 比较贴近我们的业务搜索场景。

相同数据量的情况下(4-30W),redis 可以比 Elasticearch 节省 约75%的内存占用。

而且,ES 的一些对嵌套文档索引和查询的高级功能也不是需求的业务特性

中文的分片语件(friso)可以满足基本需求,模块化的设计也给我们后续的优化开发提供了余地。

1. Redis Modules

Redis Modules 是 redis 4.0 引入的一种扩展机制,用户可以通过实现 redis module 提供的 C api 介面为 redis 服务添加定制化功能。 redisLab 也希望籍此来规范 redis 社区的 ecosystem 实现。

redis module 本身的版本独立于redis,并且以编译成动态载入库 .so 文件的方式 release, 不同版本的 redis 可以 load 同一版本 module.so 文件。

redis 提供了两种载入方式。可以通过 在 conf 文件中 加入 loadmodule /path/to/mymodule.so ,也可以在 redis-cli 中 使用命令 MODULE LOAD /path/to/panda.so 动态载入,MODULE UNLOAD 卸载。

2. redisearch

Redis 社区两位核心开发@dvirsky和@mnunberg 在 2016 年启动了 redisearch 项目,旨在为 redis 提供全文索引相关的搜索功能(没错, 很像elasticsearch )。 截至2019年1月, redisearch 的最新发布版本是 v1.4.2 。

特性

  • 基于文档的全文索引。
  • 高性能增量索引。
  • 支持文档评分,文档栏位(field) 权重机制。
  • 支持布尔复杂查询。
  • 支持自动补全 (未测试)。
  • 基于 snowball 的词干分析,多语言支持。使用 friso 支持中文分词。
  • utf-8 字符集支持。
  • redis 数据持久化支持。
  • 自定义评分机制。

相比 elasticsearch,redisearch 在这些「主营业务」上其实没有什么优势,不过麻雀虽小却也五脏俱全。内存存储,轻量,文档的实时 index & search 特性才是我们选择它的原因。

有些遗憾的是,redisearch 分散式支持功能只在其企业版中可用。

在rediserch 的 的文档说明中,说明了其主要功能的实现都没有使用 redis的原生数据结构。

以全文检索使用到的倒排索引为例,对于常见的一些vm数据:

{"_id": "1a2c3d343e", "name": "我的测试vm", ...},
{"_id": "1a2c3d343d", "name": "vm-全文检索", },
{"_id": "1a2c3d343f", "name": "测试vm", }

进行分词后的倒排索引简单结构类似:

结构如下:

{text: "我", ids: ["1a2c3d343e",]},
{text: "vm", ids: ["1a2c3d343e","1a2c3d343d","1a2c3d343f",]},
{text: "全文", ids: ["1a2c3d343d",]},
{text: "检索", ids: ["1a2c3d343d",]},
{text: "测试", ids: ["1a2c3d343e","1a2c3d343f",]}

在 redis 的 hashmap 基础上就可以很容易实现倒排索引的结构。redisearch 倒排索引除了实现了基础功能外,还引入了内存管理等优化功能。如果有兴趣可以阅读源码中的 src/inverted_index.c 部分。

快速上手

Redislab 社区推荐测试时使用 docker 快速启动验证环境。

docker run -p 6666:6379 redislabs/redisearch:latest

这里我们同时需要 一个 redis-cli 来进行功能验证:

linux or Darwin:

cd /tmp
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
cp src/redis-cli /usr/local/bin/
chmod 755 /usr/local/bin/redis-cli

进入交互环境

redis-cli

或者硬核一点, 使用 linux 自带的 nc 命令 与 redisearch 交互:

nc -v 127.0.0.1 6379

检查 modules 是否成功载入

127.0.0.1:6666> MODULE list
1) 1) "name"
2) "ft"
3) "ver"
4) (integer) 10405

如果返回数组中存在 "ft" , 则表明 redisearch 已经成功载入。

创建索引 index

Redisearch 的索引概念 与elasticsearch 的 index 类似,表示某一类文档资源单元。

这里我们定义了一个 SMARTX_VM 索引,其中存储的文档 包含 了 name 和 desc 两个 类型为 TEXT 的field。

127.0.0.1:6666> FT.CREATE SMARTX_VM SCHEMA name TEXT WEIGHT 5.0 desc TEXT

查看索引信息

127.0.0.1:6666> FT.INFO SMARTX_VM

FT.INFO可以查询 index 的metadata. 包括 索引名,栏位信息 fields info,已索引文档数, 内存用量等。

向索引添加文档

向之前创建的索引 SMARTX_VM 存储文档:

{
"id": vm-2019030211110001,
"name": "测试虚拟机-01",
"desc": "我在北京故宫也能吃炸鸡"
}
127.0.0.1:6666> FT.ADD SMARTX_VM vm-2019030211110001 1.0 LANGUAGE "chinese" FIELDS
name "测试虚拟机-01" desc "我在北京故宫也能吃炸鸡"

LANGUAGE "chinese" 参数 表示 使用 中文分词器 处理文本。默认为英文.

文档检索

这里直接搜索 「故宫炸鸡」 是检索不到的,因为没有指定合理的文本分词器

127.0.0.1:6666> FT.SEARCH SMARTX_VM "故宫炸鸡"
1) (integer) 0

检索时指定语言类型

127.0.0.1:6666> FT.SEARCH SMARTX_VM "故宫炸鸡" LANGUAGE "chinese"
1) (integer) 1
2) "vm-2019030211110001"
3) 1) "name"
2) "xe6xb5x8bxe8xafx95xe8x99x9axe6x8bx9fxe6x9cxba-01"
3) "desc"
...

可以看到已经返回了我们想要的结果。

进阶

存储结构

redisearch的 索引文档存储复用了 redis 的 HASH table 类型。

通过redis 的 HGETALL ,HGET 命令可以查询文档内容。 比如查询上文示例中添加的文档

127.0.0.1:6666> HGETALL vm-2019030211110001

需要注意的是 redisarch 模块并没有做文档的资源隔离,当存在多个索引,不同索引的文档 id 出现重复时,相同 id 的文档将会合并。合理使用这一特性 可以节省内存空间。如果业务层需要严格的资源隔离,则可以使用 redis 的 多个 db,或者为 不同索引的 id 添加 prefix。

接入姿势

在 smartx 虚拟化管理平台,我们将 redisearch 置于 我们的 mongodb 业务资料库之后, 并且实现了一个 mongo 操作日志监听工具 goose,将 mongodb集群中业务方需要检索的文档栏位实时同步到 redisearch 中。

用户的检索查询请求会首先从redisearch 获取到匹配文档的id,再从mongoDB 中查询。流程图如下:

在mongodb的副本集(replicaSet) 部署中,各个副本节点与主节点使用 oplog 机制同步, oplog 会记录mongo客户端的操作历史,副本节点可以监听主节点的oolog 并重放就可以完成 同步数据的效果。

oplog的 prototype:

type OpLog struct {
Id // mongo doc 的 objectId
Operation // 操作数: 增,改,删
Namespace // db.collection
Timestamp // 操作时间戳
Doc // 文档内容
}

github 社区的开源同步工具 mongo-connector,就是通过监听 oplog的方式从mongo中做持久化数据同步,然后再根据disparcher 介面将数据分发到 相应的数据组件: elasticsearch,mongo,etc...

Mongo-connector 虽然提供了完备的mongo同步框架,但同步到 redisearch 相关的 dispatcher 仍然需要自行实现。同时其数据 filter 模块也不支持 field 粒度的查询。

出于上述考虑,为了能够更加精细化的做数据筛选同步和兼容产品后端服务,笔者实现了一个可以定制筛选条件的 oplog 同步中间件: goose(鹅)。

产品中的其他服务可以向 goose 注册筛选条件(以toml文件的方式):

# sync VOLUME => "ELF_VOLUME"
[[sync-rule]]
db = "resources"
collection = "resource"
index = "VOLUME" # also an unique id of rule
id = "_id"
filter = { super_type = "KVM_VOLUME_SUPER", status = "created"}
fields = ["name", "description"]

形如上述配置,goose会从 mongo 的 resouce 中筛选一些满足 supertype = "KVMVOLUME_SUPER" & status = "created" 的文档,将文档中的 name 和 decription 同步到 redisearch 的 VOLUME 集合中,完成从监听 --> 同步 --> 建立全文索引的过程。

相关数据的处理和流向关系用下图表示:

高可用

由于 redisearch 的开源版本不支持redis的集群模式部署, 该查询服务的高可用部分交由goose 和产品集群的 服务注册和代理服务合作。

我们在每个 mongo节点都部署了针对单节点的 数据同步和redisearch服务 并暴露本地6666埠, 如果goose服务健康, goose 会把当前节点注册到集群的 反向代理和负载均衡 服务中。其他服务可以访问集群任意节点的11900埠进行 全文检索查询服务。

开发

在实际开发中,笔者通读了 redisearch python 和 golang 的客户端实现,python-cli 作为社区推荐的客户端实现,目前覆盖封装了了1.4 版本之前的大部分功能, 可以满足开发使用要求。但是 golang 版本比较滞后,很多关键命令(如 ALTER, SUG* 等)没有封装,连接池复用等常用客户端特性均未实现,如有golang 使用需求,建议使用 redigo 项目自行实现。

license

RedisLab 在 2018 年 7月 将其大部分项目的 license 更换到了 Apache License with Commons Clause。

在 2019年 2月份,又替换为 Redis Source Available License Agreement。

为避免商业纠纷,我们也在第一时间联系了Redis社区,了解 license 被修改后相关产品的使用边界。

根据反馈结果,可以确认 redis 依然可以作为一种基础设施内存资料库存在于产品中。但是将redis作为产品的卖点,或封装后直接售卖 是不被允许的。

写在最后

Redisearch 是一个高效,功能完备的内存存储的高性能全文检索组件, 十分适合应用在数据量适中, 内存和存储空间有限的环境。借助数据同步手段,我们可以很方便的将redisearch 结合到现有的数据存储中, 进而向产品提供 全文检索, 自动补全等服务优化功能。

相关的数据同步组件部分会尽快提供与公司业务层解耦的开源版本回馈社区,敬请关注。

了解更多

1.2019 redis license agreement

2.redisearch doc

作者介绍

Vici,SmartX 研发工程师。SmartX 拥有国内最顶尖的分散式存储和超融合架构研发团队,是国内超融合领域的技术领导者。

推荐阅读:

相关文章