⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/huandiaoESRedisguanfangsousuoyinqinglailexingnengzhalie/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

反向定位:90% 项目其实不需要 ES

Java 项目里只要谈到「搜索」,不假思索的答案永远是 ES——上 Elasticsearch、Logstash、Kibana 三件套。这个组合是好,但它为大数据规模的全文检索 / 日志分析而设计。

90% 的中后台项目其实只要:

  • 商品名 / 文章标题模糊搜索;
  • 几张表的中文分词检索;
  • 类目过滤 + 关键词组合查询;
  • 偶尔的同义词、拼写纠错。

这种规模上 ES 是「拿火箭炮打蚊子」——一台 ES 节点起步要 1 GB 内存,多语言、聚合、分布式、副本、shard 一整套配下来你大半都用不到。真正的痛点是:你要为搜索单独维护一个 ES 集群、配 IK 分词、写同步脚本、跟着业务表迭代 mapping。

直接给方案:Redis 官方推出的搜索模块 RediSearch——它不是「另一个搜索引擎」,而是 Redis 的一个 module,跟着 Redis 实例一起跑,没有额外服务、没有额外集群。对已经在用 Redis 的项目,等于零运维成本接入全文搜索。

横向对比:4 个搜索方案怎么选

不是所有搜索场景都该上 RediSearch。先看自己在哪个象限:

方案 数据规模 内存占用 中文分词 致命短板
MySQL LIKE / MATCH AGAINST < 50 万行 几乎为 0 弱 百万级以上性能崩
PostgreSQL FTS < 500 万行 数据库自带 需插件 仍是关系库,全文不是它强项
Elasticsearch 千万 ~ 数亿行 1 GB+ 起 IK 分词成熟 重运维、单独集群、内存吃满
RediSearch(本文) 百万 ~ 千万行 100 MB 量级 内置中文 集群版仅 Redis 企业版,开源版单机

判断很简单:

  • 数据 < 50 万行、查询不频繁:MySQL FULLTEXT 即可,别造方案;
  • 已经用 Redis、数据百万到千万、单机够用:RediSearch 性价比最高;
  • 真正的大数据 / 复杂聚合 / 多副本 / 分布式:ES 还是必选;
  • 一个新项目、想要现代搜索体验:MeiliSearch / Typesense 也值得看。

性能实测:吞吐 4 倍、内存 1/10

官方做了一组对比测试,机器配置:

数据源(同样的语料导入):

RediSearch 配置:

Elasticsearch 配置:

版本:

索引构建:RediSearch 221 秒 vs Elasticsearch 349 秒,领先 58%:

查询吞吐:32 客户端并发跑两词搜索——RediSearch 12.5K ops/sec,ES 仅 3.1K ops/sec,4 倍差距;延迟 8 ms vs 10 ms:

最关键的是内存:低配 2 核 4 GB 服务器上,Redis Stack 镜像(Redis + RediSearch 模块)总内存占用不到 100 MB,ES 实例至少 1 GB。原本要单独申请一台 ES 服务器的预算,现在零成本搞定。

RediSearch 2.0+ 比 1.6 又把吞吐和延迟提升了 2.4 倍。

Docker 一行装,4 条命令验证

装:

docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest

# 带密码版
docker run -e REDIS_ARGS="--requirepass redis-stack" \
-p 6379:6379 redis/redis-stack:latest

验证模块装好:

redis-cli -h localhost
> MODULE list
3) 1) "name" 2) "search" 3) "ver" 4) "20809"

看到 search 模块即装好。

4 条核心命令完成搜索全流程:

# 1. 创建索引(hash 数据源、中文分词、goodsName 字段可排序)
> FT.CREATE idx:goods ON HASH PREFIX 1 "goods:" \
LANGUAGE chinese SCHEMA goodsName TEXT SORTABLE
"OK"

# 2. 写入数据(直接 hset,索引自动构建)
> HSET goods:1001 goodsName 小米手机
> HSET goods:1002 goodsName 华为手机

# 3. 搜索
> FT.SEARCH idx:goods "手机"
1) "2"
2) "goods:1001"
3) 1) "goodsName" 2) "小米手机"
4) "goods:1002"
5) 1) "goodsName" 2) "华为手机"

# 4. 删索引(不删源数据)
> FT.DROPINDEX idx:goods

LANGUAGE chinese 指定中文分词,SORTABLE 让字段可排序。不指定 LANGUAGE chinese 中文检索就退化成单字匹配,搜索质量直接报废。

Java 接入:Jedis 4.0+ 直接用

Jedis 4.0+ 内置 RediSearch 客户端,不用额外引依赖:

@Bean
public UnifiedJedis unifiedJedis(GenericObjectPoolConfig poolConfig) {
if (StringUtils.isNotEmpty(password)) {
return new JedisPooled(poolConfig, host, port, timeout, password, database);
}
return new JedisPooled(poolConfig, host, port, timeout, null, database);
}

创建索引(与 SQL 建表对应):

Schema schema = new Schema()
.addSortableTextField("goodsName", 1.0)
.addSortableTagField("tag", "|");

IndexDefinition rule = new IndexDefinition(Type.HASH)
.setPrefixes("goods:") // 与 CLI 端 PREFIX 1 "goods:" 保持一致
.setLanguage("chinese"); // 中文分词

String idxName = "idx:goods";
client.ftCreate(idxName,
IndexOptions.defaultOptions().setDefinition(rule),
schema);

注意:索引名(idx:goods)和数据 key 前缀(goods:)是两件事——前者是索引身份证,后者是索引盯的 hash key。别把索引名当 key 前缀用。

写入索引(写 hash 即同步入索引):

public boolean addGoodsIndex(Goods goods) {
Map<String, String> hash = MyBeanUtil.toMap(goods);
hash.put("_language", "chinese");
// key 必须以索引绑定的前缀「goods:」开头,否则不会被索引
client.hset("goods:" + goods.getGoodsId(), hash);
return true;
}

中文搜索(带分页 / 排序):

public SearchResult search(String idxName, SearchObjVO vo, Page<?> page) {
String queryKey = String.format("@goodsName:(%s)", vo.getKeyword());
Query q = new Query(queryKey).setLanguage("chinese");

if (StringUtils.isNotBlank(vo.getSidx())) {
q.setSortBy(vo.getSidx(), Constants.SORT_ASC.equals(vo.getOrder()));
}
q.limit((int) page.offset(), (int) page.getSize());
return client.ftSearch(idxName, q);
}

关键设计:写 Redis 的代码就是业务现有的 hash 写入代码——只要规范 key 前缀(goods:),索引就自动跟着 hash 写入实时更新,不用单独维护 ES 同步管道。

4 个真实使用边界

边界 1:开源版没有集群(最常见)

RediSearch 开源版只支持单实例——集群版只在 Redis 企业版(付费)里有。如果你的搜索数据量超过单机内存上限,必须考虑 Redis 企业版或回到 ES。这是这个工具最大的边界,先确认你的数据规模再决定。

边界 2:拼音转中文不支持(常见)

ES + IK 分词器有「拼音转中文」插件(pinyin),用户输入 xiaomi 能搜出「小米」。RediSearch 内置中文分词但没有拼音插件,需要自己在应用层做拼音转写。电商搜索这是硬需求,要提前评估。

边界 3:重启后索引要重建(少见但破坏力大)

RediSearch 2.0 起,索引本身会跟着 Redis 持久化文件一起 dump,但 RDB 加载时仍要做一次「索引重建」——百万级数据大约 3-5 分钟,期间搜索服务不可用。修法有两条独立选择:

  • 走 AOF 持久化 + everysec 同步(appendonly yes / appendfsync everysec)——重启时 replay AOF 比 RDB 重建索引快得多,但写入 IO 会高一些;
  • 维持 RDB(save 60 1000 等)+ 搜索请求走主从分离——主重启的几分钟由从节点顶上,搜索可用性不中断。

别把 AOF 和 RDB 混着写——save 60 1000 是 RDB 快照配置,appendonly yes 才是 AOF 开关,两者是独立的持久化策略。

边界 4:和业务 hash 共享 key 空间(高级场景)

HSET goods:1001 ... 既是业务数据也是索引源——业务侧误删 key 等于删索引数据。修法:用专属前缀(idx:goods:1001)隔离,或加权限控制。

最后说一句

搜索方案的选择不是「哪个最强」,是「哪个最匹配你的规模」。RediSearch 不是来替代 ES 的——它是为「90% 项目其实不需要 ES」这件事提供的更轻方案。好的搜索基础设施不是功能最多的那个,是让你不用为搜索单独维护一个集群的那个。

仓库:github.com/RediSearch/RediSearch

文章目录
  1. 1. 反向定位:90% 项目其实不需要 ES
  2. 2. 横向对比:4 个搜索方案怎么选
  3. 3. 性能实测:吞吐 4 倍、内存 1/10
  4. 4. Docker 一行装,4 条命令验证
  5. 5. Java 接入:Jedis 4.0+ 直接用
  6. 6. 4 个真实使用边界
  7. 7. 最后说一句