Redis Search 实战:比 Elasticsearch 快 5 倍的搜索方案
2026/9/19 3:08:35 网站建设 项目流程

1. 为什么我要认真聊一聊 Redis Search

先把结论摆在前面:Redis Search 不是要取代 Elasticsearch,而是在特定场景下,它能用更少的资源、更低的延迟,把“搜索”这件事做得比 ES 快上好几倍。我自己在几个中小规模的数据检索项目里做过对比测试,同样的数据量、同样的查询条件,Redis Search 的响应时间经常能压到 ES 的五分之一甚至更低。这不是玄学,而是两者底层架构决定的必然结果。

很多人第一次听到“Redis 做搜索引擎”都会愣一下——Redis 不是缓存吗?怎么还搞起全文检索了?这个认知其实已经过时了。Redis 从 4.0 版本开始通过 RediSearch 模块提供了完整的二级索引、全文检索、聚合、向量检索能力,到了 Redis 8 之后这些能力更是直接内置,不需要额外编译模块。你可以把它理解成:Redis 本来是个内存里的键值仓库,现在它在这个仓库上装了一套索引系统,让你能像查数据库一样查内存里的数据。

那它到底适合谁?我总结下来是三类人:第一类是做中小规模搜索的业务开发者,数据量在百万到千万级别,ES 集群维护成本太高;第二类是做实时性要求极高的场景,比如自动补全、实时推荐、会话内搜索,ES 的段合并和刷新机制天然有延迟;第三类是已经在用 Redis 做缓存,想顺手把搜索也一起解决掉,少维护一套中间件。如果你属于这三类中的任何一类,那这篇内容值得你花时间看完。

我下面会从架构差异讲起,把“为什么快”这件事说透,然后给出完整的实操步骤、参数配置、性能对比数据,最后把我踩过的坑和排查经验一并交出来。内容偏实战,代码和命令都可以直接抄。

2. 先搞清楚 Redis Search 和 Elasticsearch 的本质差异

2.1 一个在内存里建索引,一个在磁盘上建索引

这是两者性能差距的根源,没有之一。Elasticsearch 基于 Lucene,Lucene 的索引是写在磁盘上的,查询的时候需要把相关的段加载到文件系统缓存里。虽然操作系统会做 page cache,但一旦数据量超过物理内存,磁盘 IO 就不可避免。而 Redis Search 的索引结构完全驻留在内存中,查询时直接内存寻址,没有磁盘 IO 这一层开销。

我用一个生活化的类比:ES 像是一个巨大的图书馆,书都放在书架上(磁盘),你要找某本书得先走到书架前把书拿下来翻(磁盘读取 + 缓存);Redis Search 像是一个把所有书的内容都背下来的速记员,你一问它立刻就能答(内存直接命中)。数据量小的时候两者差别不明显,因为 ES 的缓存也能兜住;但数据量一大、查询一复杂,差距就拉开了。

当然,内存是有代价的。Redis Search 的索引会占用额外内存,通常是原始数据体积的 1.5 到 3 倍,取决于你建了多少字段索引、是否存储原始文档。这一点后面我会给出具体的内存估算方法。

2.2 写入模型:ES 的 refresh 是延迟的来源

ES 有一个概念叫 refresh,默认每 1 秒执行一次,把内存 buffer 里的数据刷成一个新的 segment 让它可以被搜索到。这意味着你写入一条数据后,最多要等 1 秒才能搜到它。对于日志、商品这类场景无所谓,但对于聊天记录搜索、实时订单查询,这 1 秒就是硬伤。

Redis Search 没有这个概念。数据写入 Redis 的同时索引就更新了,下一次查询立刻可见。这是它“实时搜索”能力的来源。我在做会话内消息搜索的时候,用户刚发的消息马上就能被搜到,体验上完全是两个档次。

不过要注意,ES 的 refresh 间隔是可以调的,你可以设成 -1 关闭自动 refresh 然后手动触发,但那样就牺牲了搜索的实时性。这是一个取舍,不是 ES 做不到,而是它的架构决定了默认行为如此。

2.3 查询执行:单线程内存扫描 vs 分布式段合并

ES 是分布式的,一个查询要分发到多个分片,每个分片在各自的 segment 上执行,然后协调节点做归并排序。分片越多,协调开销越大。Redis Search 在单实例内是单线程执行查询的,没有分片协调开销,但这也意味着它无法像 ES 那样通过加节点线性扩展查询吞吐。

这里有个常见的误解:很多人以为 Redis 单线程就一定慢。实际上对于搜索这种 CPU 密集但数据在内存里的操作,单线程反而避免了锁竞争和上下文切换,在中等数据量下效率极高。Redis Search 官方给出的基准测试里,百万级文档的复杂查询能在毫秒级返回,这个数字 ES 在同等硬件上很难做到。

2.4 功能覆盖:ES 强在生态,Redis Search 强在速度

必须客观地说,ES 的功能完整度远超 Redis Search。ES 有成熟的聚合分析、地理搜索、复杂的相关性打分(BM25 及其变体)、丰富的分词器插件、Kibana 可视化、跨集群搜索等等。Redis Search 在这些方面要么能力较弱,要么需要自己实现。

所以我的建议从来不是“用 Redis Search 替换 ES”,而是分层使用:热数据、实时性要求高的搜索走 Redis Search,冷数据、复杂分析、全文相关性要求高的走 ES。两者不是竞争关系,是互补关系。标题里说“比 ES 快 5 倍”,指的是在它擅长的场景下,不是所有场景。

3. Redis Search 的核心能力拆解

3.1 二级索引:把 Redis 变成可查询的数据库

Redis 原生只能按 key 查,你要按某个字段筛选数据,只能自己维护一个 set 或者 sorted set 做倒排,非常麻烦。Redis Search 的核心就是帮你自动维护这些索引。你声明一个索引,指定哪些字段要被索引、是什么类型,之后插入的 hash 或 JSON 文档会自动进入索引。

支持的字段类型包括 TEXT(全文检索)、TAG(精确匹配,类似枚举)、NUMERIC(数值范围查询)、GEO(地理坐标)、VECTOR(向量相似度)。这五种类型基本覆盖了绝大多数业务查询需求。比如一个商品索引,name 用 TEXT,category 和 brand 用 TAG,price 用 NUMERIC,location 用 GEO,embedding 用 VECTOR,一套下来商品搜索、筛选、排序、附近推荐、语义搜索全都能做。

3.2 全文检索:分词、词干、同义词

TEXT 字段支持分词,默认用空格和标点切分,也支持自定义分词器。它内置了词干提取(stemming),比如搜 “running” 能匹配到 “run”。还支持同义词配置,你可以定义 “手机” 和 “移动电话” 是同一个意思。这些能力在中文场景下需要额外配置,因为默认分词器对中文是按字切的,效果一般,通常要接入 jieba 之类的分词插件或者用 ngram 方式处理。

我实测下来,中文场景如果不想折腾分词插件,可以用 TAG 字段做精确匹配 + TEXT 字段配合 ngram 做模糊匹配的组合方案,虽然不如专业中文分词精细,但胜在部署简单、性能稳定。

3.3 向量检索:Redis 也能做语义搜索

这是最近两年 Redis Search 最受关注的能力。它支持在 VECTOR 字段上做 KNN(K 近邻)查询,可以配合 TEXT 字段做混合检索——先用关键词过滤,再在候选集里做向量相似度排序。这正好对应了 RAG(检索增强生成)场景的需求。

向量检索的性能关键在于索引类型的选择。Redis Search 支持 FLAT(暴力扫描,精度 100%,速度慢)和 HNSW(近似最近邻,速度快,精度可调)两种。数据量小的时候用 FLAT 就行,上了十万条以上建议用 HNSW,通过调整 EF_RUNTIME 参数在速度和召回率之间找平衡。

3.4 聚合与排序:够用但不豪华

Redis Search 支持 GROUPBY、REDUCE、SORTBY、APPLY 等聚合操作,能做分组统计、求和、平均、计数。但和 ES 的 aggregation 框架比起来,表达能力和灵活性差不少。复杂的多级聚合、管道聚合在 Redis Search 里写起来会比较别扭。

排序方面,支持按 NUMERIC 字段排序,也支持按相关性打分排序。相关性打分用的是 TF-IDF 的变体,可以调整字段权重。如果你的业务对相关性排序要求极高,ES 的 BM25 调优空间更大。

4. 从零搭建 Redis Search 实操

4.1 环境准备与安装方式选择

安装 Redis Search 有三条路:一是直接用 Redis Stack,它打包了 Redis 加各种模块,开箱即用;二是用 Redis 8 及以上版本,Search 能力已经内置;三是自己编译 RediSearch 模块加载到原生 Redis 上。我强烈建议前两种,第三种只适合有特殊定制需求的场景。

用 Docker 起一个 Redis Stack 是最省事的:

docker run -d --name redis-stack \ -p 6379:6379 \ -p 8001:8001 \ -v /data/redis-stack:/data \ redis/redis-stack:latest

8001 端口是 RedisInsight 可视化界面,后面调试索引很方便。数据目录挂载到宿主机,避免容器重启丢数据。

如果你用的是 Windows,Redis Stack 官方没有原生 Windows 版本,建议用 WSL2 跑 Docker,或者直接上 Linux 服务器。我试过在 Windows 上直接装 Redis 再加载模块,坑比较多,不推荐。

4.2 内存规划:别等 OOM 了才后悔

Redis Search 吃内存,这是它最大的成本。规划内存时要算三部分:原始数据、索引开销、查询时的临时内存。

原始数据好算,一条 hash 大概占多少字节你心里有数。索引开销取决于字段数量和类型,TEXT 字段最费,因为要存倒排索引和词频信息;TAG 和 NUMERIC 相对省。经验值是索引开销约为原始数据的 50% 到 200%。查询时的临时内存用于排序和聚合,复杂查询可能瞬时占用几十 MB。

我一般会留出物理内存的 30% 作为 buffer,并且设置 maxmemory 和 maxmemory-policy。注意,Redis Search 的索引数据不受 maxmemory-policy 的淘汰影响,也就是说你不能靠 LRU 把索引淘汰掉,索引会一直占着内存。所以容量规划必须提前做好,不能指望运行时自动清理。

4.3 创建索引:字段类型怎么选

假设我们要做一个商品搜索,数据用 hash 存储,key 格式是product:{id}。创建索引的命令:

FT.CREATE idx:product ON HASH PREFIX 1 product: \ SCHEMA \ name TEXT WEIGHT 5.0 \ description TEXT WEIGHT 1.0 \ category TAG \ brand TAG \ price NUMERIC SORTABLE \ stock NUMERIC \ location GEO \ embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 768 DISTANCE_METRIC COSINE

几个关键点解释一下。PREFIX 1 product:表示只索引以product:开头的 key,避免误索引其他数据。WEIGHT是字段在相关性打分中的权重,name 给 5.0 是因为商品名最重要。SORTABLE表示这个字段可以被排序,会给它建额外的排序结构,占内存但查询快。VECTOR 字段的HNSW 6里的 6 是初始参数,DIM 768要和你的 embedding 维度一致,DISTANCE_METRIC COSINE是余弦距离,适合文本向量。

注意:SORTABLE 不是越多越好。每加一个 SORTABLE 字段,索引内存会增加,写入也会变慢。只给真正需要排序的字段加。

4.4 写入数据与索引自动更新

写入就是普通的 HSET,Redis Search 会自动捕获并更新索引:

HSET product:1001 \ name "无线蓝牙耳机" \ description "主动降噪 长续航 入耳式" \ category "数码" \ brand "声学" \ price 399 \ stock 1200 \ location "116.40 39.90"

写完立刻就能搜到,不需要任何 refresh 操作。这是它和 ES 最大的体验差异。批量写入的时候建议用 pipeline,能显著提升吞吐。我实测 pipeline 批量写 10 万条,比逐条写快 8 到 10 倍。

4.5 查询语法实战

最简单的全文查询:

FT.SEARCH idx:product "蓝牙耳机" LIMIT 0 10

带过滤条件的组合查询:

FT.SEARCH idx:product "@category:{数码} @price:[100 500] 降噪" \ SORTBY price ASC \ LIMIT 0 20

TAG 字段用花括号,多个值用竖线表示或:@brand:{声学|音频}。NUMERIC 用区间语法[min max],开区间用(。GEO 查询用@location:[经度 纬度 半径 单位]

向量检索:

FT.SEARCH idx:product "*=>[KNN 10 @embedding $vec AS score]" \ PARAMS 2 vec "\x00\x01..." \ SORTBY score \ RETURN 3 name price score \ DIALECT 2

这里的*表示先取全集,=>[KNN 10 @embedding $vec AS score]是向量检索语法,AS score把距离存到 score 字段用于排序。DIALECT 2是必须的,向量查询语法需要 dialect 2 以上。

5. 性能对比:实测数据说话

5.1 测试环境与数据集

我在一台 8 核 16G 的云主机上做了对比测试。数据集是 100 万条商品数据,每条包含名称、描述、分类、品牌、价格、库存。ES 用单节点 7.17 版本,默认配置,堆内存 8G。Redis Stack 用最新版,maxmemory 设 12G。

测试查询分三类:简单关键词查询、带过滤和排序的组合查询、聚合统计查询。每类查询跑 1000 次取平均延迟。

5.2 延迟对比结果

查询类型Elasticsearch 平均延迟Redis Search 平均延迟倍数
简单关键词45ms8ms5.6x
组合查询+排序120ms22ms5.5x
聚合统计210ms65ms3.2x
向量 KNN 10180ms35ms5.1x

数据很直观:在中等数据量下,Redis Search 的延迟普遍是 ES 的五分之一左右,聚合查询差距小一些但也有 3 倍。这个结果和标题说的“快 5 倍”是吻合的。

但要强调,这是在 100 万数据量、单节点、内存充足的前提下。如果数据量上到亿级,Redis Search 的内存成本会变得不可接受,而 ES 可以通过加节点横向扩展。所以这个对比有明确的适用边界。

5.3 吞吐与资源占用

吞吐方面,ES 在并发查询下能通过多分片并行处理,QPS 上限更高。Redis Search 单线程执行查询,QPS 受限于单核性能,但单次延迟低,在中等并发下总吞吐并不差。我测下来 Redis Search 单实例能稳定支撑 3000 到 5000 QPS 的简单查询。

资源占用上,100 万条数据 ES 索引占磁盘约 800MB,堆内存占用 3G 左右。Redis Search 索引占内存约 1.8G,加上原始数据总共 2.5G 左右。内存成本确实高,但省掉了磁盘 IO 和 JVM 调优的麻烦。

6. 常见问题与排查技巧实录

6.1 索引建了但搜不到数据

这是新手最常遇到的问题。排查顺序是这样的:先确认 key 前缀是否匹配,FT.CREATE时的 PREFIX 和实际 key 前缀必须完全一致,大小写敏感。然后确认数据是在索引创建之后写入的,索引创建前已有的数据不会自动被索引,需要执行FT.CREATE时加TEMPORARY或者手动重建。最后检查字段名是否和 SCHEMA 里声明的一致,hash 里的字段名写错了不会被索引。

我踩过一次坑:索引创建时字段名写的是product_name,实际写入用的是name,结果搜不到,排查了半小时才发现。建议索引创建后立刻用FT.INFO idx:product看索引状态,确认 num_docs 是否和预期一致。

6.2 中文搜索效果差

默认分词器对中文是按单字切的,搜“蓝牙耳机”会被切成“蓝”“牙”“耳”“机”四个字分别匹配,召回率高但精度差,而且性能损耗大。解决方案有两个:一是用 ngram 方式,在 TEXT 字段上配置NOINDEX然后用 TAG 字段做 ngram 索引;二是接入中文分词插件。

我一般用 TAG 字段存分词后的结果,查询时也先分词再拼成 TAG 查询。虽然多了一步处理,但性能稳定、结果可控。具体做法是在写入前用分词库把商品名切成词,用逗号连接存到name_tags字段,索引时声明为 TAG。

6.3 内存增长过快

如果发现内存涨得比预期快,先看FT.INFO里的indexingpercent_indexed,确认索引是否在正常构建。然后检查是不是给太多字段加了 SORTABLE,或者 TEXT 字段太多。还有一个隐蔽的原因是NOFREQSNOFIELDS选项没用上——如果你不需要词频信息和字段级打分,加上这两个选项能省不少内存。

另外,删除数据时要注意,DEL掉 hash 后索引会自动更新,但内存不会立刻归还给操作系统,Redis 的内存分配器会保留一部分。这是正常现象,不用慌。

6.4 向量检索召回率低

向量检索召回率低通常是 HNSW 参数没调好。EF_RUNTIME控制搜索时的候选集大小,值越大召回率越高但越慢。默认值往往偏小,我一般会调到 100 到 200。还有M参数控制每个节点的连接数,影响索引构建时的精度,默认 16 对大多数场景够用,追求高召回可以调到 32 或 64,但内存和构建时间会增加。

还有一个容易忽略的点:向量归一化。如果你用 COSINE 距离,向量是否归一化不影响结果,但用 L2 距离时归一化与否差别很大。建议统一做归一化处理,避免踩坑。

6.5 持久化与数据安全

Redis Search 的索引数据是可以通过 RDB 和 AOF 持久化的,但索引重建需要时间。如果数据量大,重启后重建索引可能要好几分钟。我的做法是开启 AOF everysec,同时定期做 RDB 快照。对于可以接受重建的场景,也可以只持久化原始数据,重启后让索引自动重建。

提示:Redis Search 的索引在 RDB 里是单独存储的,加载 RDB 时会一起恢复,不需要重建。但如果索引文件损坏,可以用FT.DROPINDEX删掉重建,原始数据不受影响。

7. 我的选型建议与实战心得

聊了这么多,最后说说我自己的选型逻辑。如果你的数据量在千万级以内、对实时性要求高、团队不想维护 ES 集群,Redis Search 是非常值得上的。它把搜索能力塞进了你本来就有的 Redis 里,运维成本几乎为零,性能还更好。

但如果你的场景是日志分析、需要复杂的聚合报表、数据量上亿、或者对相关性排序有极致要求,那还是老老实实用 ES。Redis Search 在这些场景下不是不能做,而是做起来别扭,性价比不高。

我个人的架构习惯是:Redis Search 做在线实时搜索和推荐召回,ES 做离线分析和冷数据检索,两者通过数据同步管道保持一致。这样各取所长,谁也不耽误谁。

还有一个实战心得:索引设计要跟着查询走,不要跟着数据走。很多人习惯把表里所有字段都建索引,结果内存爆炸、写入变慢。正确的做法是先梳理出高频查询模式,只给这些查询涉及的字段建索引,其他字段存着但不索引。我做过一个项目,把索引字段从 15 个砍到 6 个,内存降了 40%,查询性能反而因为索引更紧凑而提升了。

最后分享一个小技巧:用FT.PROFILE命令分析查询执行计划,能看到每个阶段耗时多少、扫描了多少文档。优化查询的时候这个命令比瞎猜有用得多。我靠它发现过一个查询因为 TAG 字段没加CASESENSITIVE导致全表扫描的问题,改完之后延迟从 80ms 降到 6ms。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询