尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Redis Search 实战:比 Elasticsearch 快 5 倍的搜索方案

Redis Search 实战:比 Elasticsearch 快 5 倍的搜索方案 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 基于 LuceneLucene 的索引是写在磁盘上的查询的时候需要把相关的段加载到文件系统缓存里。虽然操作系统会做 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 用 TEXTcategory 和 brand 用 TAGprice 用 NUMERIClocation 用 GEOembedding 用 VECTOR一套下来商品搜索、筛选、排序、附近推荐、语义搜索全都能做。3.2 全文检索分词、词干、同义词TEXT 字段支持分词默认用空格和标点切分也支持自定义分词器。它内置了词干提取stemming比如搜 “running” 能匹配到 “run”。还支持同义词配置你可以定义 “手机” 和 “移动电话” 是同一个意思。这些能力在中文场景下需要额外配置因为默认分词器对中文是按字切的效果一般通常要接入 jieba 之类的分词插件或者用 ngram 方式处理。我实测下来中文场景如果不想折腾分词插件可以用 TAG 字段做精确匹配 TEXT 字段配合 ngram 做模糊匹配的组合方案虽然不如专业中文分词精细但胜在部署简单、性能稳定。3.3 向量检索Redis 也能做语义搜索这是最近两年 Redis Search 最受关注的能力。它支持在 VECTOR 字段上做 KNNK 近邻查询可以配合 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:latest8001 端口是 RedisInsight 可视化界面后面调试索引很方便。数据目录挂载到宿主机避免容器重启丢数据。如果你用的是 WindowsRedis 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 写入数据与索引自动更新写入就是普通的 HSETRedis 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 20TAG 字段用花括号多个值用竖线表示或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里的indexing和percent_indexed确认索引是否在正常构建。然后检查是不是给太多字段加了 SORTABLE或者 TEXT 字段太多。还有一个隐蔽的原因是NOFREQS和NOFIELDS选项没用上——如果你不需要词频信息和字段级打分加上这两个选项能省不少内存。另外删除数据时要注意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。
返回列表