如果你平时用 Redis 只是做缓存,存的是 String、Hash、List 这一类简单结构,那么 Redis Stack 对你来说是一次非常明确的能力升级。Redis Stack 不是一个新数据库,它是 Redis 官方把 RediSearch、RedisJSON、RedisTimeSeries、RedisBloom 这几个模块统一打包进 Redis 核心,形成的整合发行版。换句话说,装上它之后,你不仅保留了原有的 KV 缓存能力,还能直接在 Redis 里做 JSON 文档存储、全文搜索、时序数据聚合、概率去重。这篇文章是我自己从安装到实战的完整记录,每个模块怎么用、适合什么场景、有哪些绕不过去的坑,都会一次性说清楚。
1. 先搞清楚 Redis Stack 到底解决了什么问题
1.1 从“缓存工具”到“多模型数据平台”
很多团队对 Redis 的认知是“缓存用的”,这个印象不算错,但已经过时了。过去我们做架构时,缓存就是解决热数据访问压力的问题,Redis 作为一个内存 KV 存储,速度快、数据结构多,确实很好用。但业务一旦复杂起来,就会出现一个尴尬局面:同一个项目里,搜索需要 Elasticsearch,存 JSON 需要 MongoDB,时序数据需要 InfluxDB,布隆过滤器还得自己写或者引个 Guava。
这一套组合下来,运维负担和业务复杂度都上去了。Redis Stack 的思路很简单:把这些高频使用的数据模型直接做成 Redis 模块,统一由 Redis 管理,用同一套协议、同一个端口、同样的部署方式提供服务。你在系统里少引三四个中间件,很多时候并不只是为了省资源,更关键的是让项目结构变简单。
1.2 它和普通 Redis 到底差在哪里
普通 Redis 是开源的核心版本,只有基础数据结构,不包含任何扩展模块。你在 redis.conf 里加载模块的写法是这样的:
loadmodule /opt/redis-stack/lib/redisjson.so loadmodule /opt/redis-stack/lib/rediSearch.so loadmodule /opt/redis-stack/lib/redistimeseries.so loadmodule /opt/redis-stack/lib/redisbloom.soRedis Stack 直接把上面这些加载动作替你做好了,下载下来的服务端解压启动就是完整环境。另一个需要搞清楚的概念是官方把它分成两个东西:
- Redis Stack Server:只包含服务端,提供 Redis 核心加上全部模块。
- Redis Stack Client:指的是一组官方在维护的客户端 SDK,方便你用对象映射的方式去操作这些模块。
所以你在拉镜像时如果看到redis/redis-stack和redis/redis-stack-server,区别就在一个带图形化工具,一个不带。实际生产部署我更推荐只跑redis-stack-server,把 RedisInsight 这样的可视化工具放到本地或者跳板机上用,没必要让容器多暴露一个 8001 端口。
1.3 早期版本和后来的路线调整
Redis Stack 刚推出时,里面其实还带过一个 RedisGraph 模块,做图数据库用的。后来官方调整了产品路线,新版 Stack 里逐步拿掉了图处理这一块。目前你实际能依赖的主要是四个模块:JSON、Search、TimeSeries、Bloom。到 Redis 8 之后,Stack 的能力直接并入了 Redis 主发行版,也就是说你装最新的 Redis,打开就有这些功能。
这个演进路线很重要,因为网上很多教程停留在 2023 年甚至更早,讲的还是单独下载模块、手动MODULE LOAD。你按照新版本部署时,很多步骤都可以简化。后面我会在安装部分明确区分不同时代的做法。
2. 核心模块逐个拆解:它们各自负责什么
2.1 RedisJSON:把 JSON 当成一等公民来存取
没有 RedisJSON 之前,想在 Redis 里存 JSON,绝大多数人都是先序列化成字符串,然后SET进去,取出来再反序列化。这个过程谈不上错,但有几个很别扭的问题。第一个问题是更新字段很麻烦,商品库存stock字段要减 1,你得把整个 JSON 取出来,反序列化,改字段,再序列化写回去。第二个问题是超大 JSON 对象的网络开销,一个 100KB 的文档只是改了一个数字,你却要全量传输。
RedisJSON 解决的就是这个痛点。它用类似 MessagePack 的二进制格式存 JSON,并且提供了一套 JSONPath 操作命令。写入一段数据:
JSON.SET product:1001 $ '{"name":"无线鼠标","price":99.9,"stock":200}'查询某个子路径:
JSON.GET product:1001 $.name原子自增某个数字字段:
JSON.NUMINCRBY product:1001 $.stock -1这个$.stock就是 JSONPath 的写法。更新完不需要管外层结构,Redis 内部只修改对应节点,性能高出一大截。我实际用下来,最爽的场景是购物车、用户配置、商品快照这一类“结构固定但又不想拆字段”的数据,用 RedisJSON 比用 Hash 更贴合业务模型。
需要注意一点:JSONPath 字符串以$开头,早期版本的模块还支持过以.开头的旧语法,新版兼容性上以$为准。写代码时最好统一用$,避免在升级之后踩到解析错误。
2.2 RediSearch:给 Redis 加上二级索引和全文检索
RediSearch 是这几个模块里最能改变使用方式的。它让 Redis 支持基于字段的查询、模糊匹配、数字范围、地理距离、甚至中文分词,这已经不是缓存能覆盖的范畴了。
举个例子,我们给商品 JSON 建立一个搜索索引:
FT.CREATE product_idx ON JSON PREFIX 1 product: SCHEMA $.name AS name TEXT $.price AS price NUMERIC这里每个参数都有讲究。ON JSON表示索引的对象类型是 JSON 文档;PREFIX 1 product:表示文档匹配product:开头;SCHEMA后面定义索引字段,$.name AS name是说从 JSONPath 取$.name并把它命名为name,然后声明它是文本类型。
索引建好之后,查询语法和普通 Redis 命令差异很大:
> FT.SEARCH product_idx "无线" 1) (integer) 1 2) "product:1001" 3) 1) "price" 2) "99.9"默认返回结果条数很少,只有 10 条,这个和 Elasticsearch 的size默认值有点像,但很多人第一次用都会觉得“数据怎么不全”。多条件查询时用@name:无线 @price:[50 100]这种语法:
FT.SEARCH product_idx "@name:无线 @price:[50 100]" LIMIT 0 20检索性能在千万级文档以下的表现是比较好的。但 RediSearch 不是万能的,它的索引同步、分词逻辑、内存占用都需要提前规划。最基本的注意点是:索引字段别图省事把整个 JSON 全塞进去,只索引你真的要搜索和排序的字段,其他字段留在 JSON 里按需RETURN。
2.3 RedisTimeSeries:时序数据的存取与降采样
RedisTimeSeries 解决的是监控、物联网设备上报这类场景的数据存取问题。时序数据的特点很明确:写入量大、按时间范围查询多、经常要做聚合统计。如果不做任何优化,一条设备温度数据就是一条普通 Key,量大之后 key 数量爆炸,就得想 TTL 分批删除,难受得很。
RedisTimeSeries 的基本用法是把时间序列建模成一个带标签的序列。创建一条保留 7 天的温度序列:
TS.CREATE device:001:temp RETENTION 604800 LABELS type temp device 001RETENTION单位是毫秒,意思是老数据过期自动清理;LABELS给序列打标签,后面做多序列查询时全靠它。写入一个带时间戳的采样点:
TS.ADD device:001:temp * 26.5这里的*表示使用当前时间戳。多序列批量查询用TS.MRANGE,配合标签过滤:
TS.MRANGE - + FILTER type=temp device=001 AGGREGATION avg 3600000AGGREGATION avg 3600000是按小时做平均降采样,这个功能在监控面板做曲线图时非常实用。写入端压力大时可以开压缩,内存占用会有明显下降。这一块的开发体验比较像 InfluxDB 的简化版,但没有 InfluxDB 那一整套集群和数据模型的复杂度。
2.4 RedisBloom:用很小的内存成本做大规模去重
RedisBloom 中最常用的就是布隆过滤器。布隆过滤器的核心思想是:用多个哈希函数把元素映射到一个位数组上,判断一个元素“一定不存在”或“大概率存在”。它没法保证百分之百准确判断“存在”,但能确定“不存在”。这个特点用来挡缓存穿透特别合适。
使用方法可以直接用命令,不需要提前创建:
> BF.ADD blacklist "ip:192.168.1.10" (integer) 1 > BF.EXISTS blacklist "ip:192.168.1.10" (integer) 1 > BF.EXISTS blacklist "ip:192.168.1.11" (integer) 0大数据量场景建议先BF.RESERVE指定容量和误判率:
BF.RESERVE blacklist 0.01 1000000误判率 0.01 表示百分之一,容量 100 万。为什么要显式指定?因为布隆过滤器扩容很麻烦,预分配好空间能避免后期性能衰减。注意容量越小误判率越高,内存和精度之间的平衡要看业务容忍度。做已读去重、爬虫 URL 过滤、恶意请求黑名单,这些场景我都实际用过,效果稳定。
3. 本地安装、容器部署和第一组实操命令
3.1 最快跑起来:Docker 一键部署
如果只是学习和验证,Docker 是最快的方式。我推荐先拉取带 RedisInsight 的镜像,因为图形界面可以把 JSON 和查询结果可视化,新手看起来更直观:
docker run -d --name redis-stack \ -p 6379:6379 \ -p 8001:8001 \ redis/redis-stack:latest这样启动后,6379 是 Redis 服务端口,8001 是 RedisInsight 的 Web 界面端口。浏览器打开http://localhost:8001就能看到数据面板。如果不想开放额外端口,也可以只用纯服务端:
docker run -d --name redis-stack-server \ -p 6379:6379 \ redis/redis-stack-server:latest生产环境我一般建议用这个。还有一点:容器部署时可以把数据放到宿主机目录挂载,避免容器重建丢失数据:
docker run -d --name redis-stack-server \ -p 6379:6379 \ -v /data/redis:/data \ redis/redis-stack-server:latest3.2 没有 Docker 时的本机安装
本机装 Redis Stack 也不复杂。官方提供 Linux 的 release 包,解压后目录里有redis-server、redis-cli和模块对应的.so文件。启动时可以直接用自带的配置:
./redis-server /opt/redis-stack/redis.confmac 上用 Homebrew:
brew tap redis-stack/redis-stack brew install redis-stack这种方式会装好redis-stack-server和redis-cli,路径在/opt/redis-stack下。Windows 用户建议直接用 WSL2 或者 Docker,官方没有提供原生的 Windows 安装包。
最稳妥的做法是启动后先确认模块状态:
redis-cli 127.0.0.1:6379> MODULE LIST这条命令会列出所有已加载的模块,看到json、search、timeseries、bf相关的名称就说明环境对了。如果缺少某个模块,有可能是版本差异,也可能是模块加载失败,需要回查启动日志。
3.3 组合实操:建 JSON、建索引、跑搜索
环境跑通之后,我建议按下面这个顺序完整走一遍,感受一下 Stack 的组合能力。先用 RedisJSON 写入几条商品数据:
JSON.SET product:1001 $ '{"name":"无线鼠标","brand":"logitech","price":99.9,"stock":200}' JSON.SET product:1002 $ '{"name":"机械键盘","brand":"logitech","price":399,"stock":50}' JSON.SET product:1003 $ '{"name":"USB-C 扩展坞","brand":"anker","price":159,"stock":0}'接着创建搜索索引:
FT.CREATE product_idx ON JSON PREFIX 1 product: SCHEMA $.name AS name TEXT $.brand AS brand TEXT $.price AS price NUMERIC $.stock AS stock NUMERIC搜索品牌和价格范围:
FT.SEARCH product_idx "@brand:logitech @price:[0 200]" LIMIT 0 10这个查询相当于“找到 logitech 品牌下 200 元以内的商品”,返回结果里除了文档 id 还会带上索引字段,因为默认返回是没有RETURN限制的。如果只需要 id 列表,可以加RETURN 0。这里有经验的细节:索引字段一旦建好,后续写入的文档也会同步进索引,所以在数据已经存在的情况下创建索引也能覆盖存量数据,不一定非要先建索引再写数据。
3.4 时序命令和时间窗口聚合演示
继续在同一个 Redis 里模拟设备上报。创建两组标签不同的序列:
TS.CREATE device:001:temp RETENTION 86400000 LABELS type temp device 001 TS.CREATE device:002:temp RETENTION 86400000 LABELS type temp device 002写入一批数据:
TS.ADD device:001:temp 1700000000000 20.5 TS.ADD device:001:temp 1700003600000 21.8 TS.ADD device:002:temp 1700000000000 19.2 TS.ADD device:002:temp 1700003600000 23.1按标签批量查询两个设备在最近一天内的小时平均温度:
TS.MRANGE 1699990000000 1700010000000 FILTER type=temp AGGREGATION avg 3600000这里FILTER type=temp会匹配所有type=temp的序列,AGGREGATION avg 3600000按 1 小时窗口求均值。响应里的时间戳是对齐降采样桶的起始时间,前端图表直接用就行。如果你查看数据精度发现问题,可以调整降采样的时间桶大小,比如改成 10 分钟就把最后参数改成600000。
4. 典型业务场景:三步把 Redis Stack 用起来
4.1 电商场景:商品详情缓存与站内搜索
很多电商项目的商品详情页,热点数据集中在少数爆品上,用户访问量大,而且运营会频繁调整价格和库存。传统 String 序列化做法容易产生“改了数据库却忘了更新缓存”的问题。用 RedisJSON 可以这样优化:
商品主数据写入 RedisJSON 后,价格变更时只改一个路径:
JSON.SET product:1001 $.price 89.9前端详情页读取时,用JSON.GET一次性取出渲染要用的字段,减少后端拼装。搜索能力用 RediSearch 补充。这个组合方案最大的好处是:商品搜索和详情缓存用的是同一份数据,而且都跑在同一个 Redis 实例上,少了一个 Elasticsearch 的同步链路。对于数据量在几百万级、搜索要求没那么严格的业务,这套方案完全够用。
要注意的点是:商品搜索涉及相关性排序时,RediSearch 的SORTBY只能针对数值字段,文本相关性排序能接近但做不到 Elasticsearch 那么细。真正需要复杂打分和定制分词的业务,还是得老老实实上 ES。
4.2 物联网场景:设备状态的时序入库和分钟级聚合
物联网设备上报的数据通常是“设备 ID + 指标 + 时间戳 + 数值”,处理这种写入量级,普通 Redis 需要每个指标一个 Key,再手动处理过期时间,很麻烦。用 RedisTimeSeries 可以做到按设备建模、自动过期。常见做法是每个设备的每个指标建一个序列,给序列打上device和metric标签:
TS.CREATE device:002:humidity RETENTION 604800000 LABELS type humidity device 002上报端直接TS.ADD,查询端做聚合。如果有告警规则,可以用命令拿到最新值判断是否超限:
TS.GET device:002:humidity监控面板需要 5 分钟平均曲线,就指定AGGREGATION avg 300000。这套方案比自建 MongoDB 存储更轻,而且指标数据天然按时间过期,不会越积越多。它的限制也很明显:不适合复杂分析,比如多表 Join、自定义聚合函数,这些还是交给专业的时序数据库来做。
4.3 实时反作弊场景:布隆过滤器挡缓存穿透
用一个读者的实际例子:活动页有大量恶意请求直接查询不存在的用户 ID,导致每次都打到数据库。传统做法是先查缓存,没命中再查数据库,恶意请求会让缓存形同虚设。用 RedisBloom 加一层拦截就能解决。在部署时提前把有效用户 ID 批量加进过滤器:
BF.RESERVE valid_users 0.001 10000000 BF.MADD valid_users user:10001 user:10002 user:10003查询时先BF.EXISTS,如果返回 0 就直接拒绝,不需要再走后面的查询链路。注意布隆过滤器删除元素非常困难,不适合用来做“可撤销”的黑名单,如果业务需要删除某个 ID,就要换方案或者定期重建过滤器。
这个场景特别能说明 Redis Stack 的价值:你会下意识地觉得“布隆过滤器自己写一个也不难”,但自己做要考虑位数组大小、哈希函数选择、多实例同步,这些都是坑。
5. 常见问题与排查经验:我踩过的十个坑
5.1 命令提示 unknown command,模块没加载?
如果你敲JSON.SET或FT.CREATE报错unknown command,十有八九是模块没加载。先执行:
MODULE LIST看看模块列表里有没有对应模块。没有的话,要么是版本不对,要么是加载配置没生效。手动加载可以这样:
MODULE LOAD /path/to/redisjson.so如果是容器部署,请确认拉的是redis-stack-server而不是原版redis。这个问题很常见,不少人只是按普通 Redis 的习惯装了个原生镜像,然后到处找 JSON 模块,最后发现服务里根本没有。
5.2 RediSearch 查询结果不完整或者查不到
最常见的原因有三个。
第一,默认LIMIT只有 10 条,查询结果看起来“不完整”,实际是没加分页参数。显式加LIMIT 0 100就好。
第二,索引字段路径和 JSON 实际结构不匹配。比如 JSONPath 写的是$.name,但文档实际结构是{"data":{"name":"xxx"}},那就要写$.data.name。建议先用小样本数据测试索引能命中再铺开。
第三,前缀不匹配。索引里PREFIX 1 product:只匹配以这个前缀开头的 key,如果你的商品 key 命名是goods:1001,就会搜不到。
5.3 中文搜索效果不理想怎么办
RediSearch 默认的分词器对空格和常见分隔符比较友好,中文这种没有空格的语言会被当成一整个 token,导致“搜索关键词无法命中”。实际项目里常见的处理手段是:业务层先在代码里做中文分词,把分词结果存到独立的字段,再索引这个字段。比如商品名称分词后得到一个name_seg字段,搜索时匹配这个字段返回商品。虽然听起来有点土,但稳定可控,而且不会受 Redis 版本升级影响。
5.4 内存增长太快,怎么控制
RedisJSON 和 RediSearch 都会显著增加内存占用。JSON 因为索引和存储是双份,搜索索引会额外消耗内存。做好三件事可以控制增长:一是maxmemory和maxmemory-policy按照业务容量设置好;二是清洗不用的 Key;三是做容量规划,先把抽样数据跑一周,统计 used_memory 的增长曲线再上线。不要凭感觉认为“Redis 反正快,随便塞”,内存一旦打满,淘汰策略会误伤 JSON 数据,造成不可预期的问题。
5.5 客户端兼容性
Redis Stack 的模块命令它本身未改 Redis 通信协议,原来的 redis-py、Jedis 这些客户端连接之后不受影响。只有当你发出一条模块命令时,服务端才会按模块逻辑响应。所以兼容性问题不大,但如果用的是很老的客户端库,有时候命令的 RESP 解析会有问题,建议升级到维护中的版本。官方同时提供的Redis.OM这类高层面客户端,可以用对象方式操作 JSON 和搜索,写业务代码时更省事,不过也会屏蔽一些底层细节,排查时还是建议直接用redis-cli复现命令。
5.6 索引同步的时机问题
RediSearch 在事务提交时同步更新索引,简单理解就是写数据后立刻能查到。但如果你用阻塞命令、管道或者批量脚本一次性写入大量数据,索引构建的 CPU 开销会叠加,表现为写入量上不去。大批量导数据时可以先不加索引,导入完成再统一FT.CREATE,或者分批提交。用FT.INFO可以查看索引当前状态和内存占用,性能问题排查第一步就是看它。
6. 最后的实践总结:值得不值得把业务迁过来
从我个人使用情况看,Redis Stack 并不是要替代专业的数据库,它更适合中大型项目里那些“不想为一个小需求多引一个中间件”的时刻。商品详情缓存用 RedisJSON、列表搜索用 RediSearch、监控数据存 RedisTimeSeries、风控挡穿透用 RedisBloom,四件事在同一个实例里完成,这个集成度确实很香。
如果团队已在使用原生 Redis,迁移成本也不高,命令兼容、端口不变、客户端不用改,只需要更换服务端版本即可。我建议先在非核心业务上试点,比如一个搜索模块或者一个数据上报模块,跑通之后再扩大范围。多试错几次,踩过几个坑,你会对 Stack 的边界感把握得更准确。毕竟最怕的不是用错工具,而是把工具用在不该用的地方。