新闻详情

新闻详情

首页 / 资讯中心 / 详情

Redis接入AI检索链路:从向量存储到语义缓存的完整实践

发布时间:2026/9/30 9:59:58来源:尧图网络
Redis接入AI检索链路:从向量存储到语义缓存的完整实践
最近一周我干了一件事把团队内部知识库问答的架构从“大模型产物”改成了“检索结果优先”。改动里最显眼的一步是把 Redis 从原来的会话缓存位置挪到了 AI 检索链路的核心位置上。群里有同事开玩笑说Redis 正式接入 AI 了——这句话其实放在现在这个时间点已经不算玩梗。向量写入、相似度召回、语义缓存、会话状态四个关键环节全部跑在 Redis 上这是真实项目里能做出来的架构不是实验室演示。这篇文章就是这次接地的完整过程记录。我会把为什么选 Redis 当向量检索载体、环境怎么搭、索引怎么建、语义缓存怎么省掉一半大模型调用、上线一周踩了哪些坑全部摊开写清楚。如果你正好也想把 AI 应用和 Redis 打通这篇可以直接当参考。1. Redis 从“缓存中间层”走到“AI 检索主链路”是一次刚需驱动的转身1.1 先说一下触发我做这件事的业务不适感团队的知识库问答服务跑了大半年最初的架构很常规用户提问系统把问题转成向量去专门部署的向量数据库里召回几条候选文档然后把这些候选塞进提示词交给大模型生成回答。这套链路没大毛病但运营一段时间后有两件事让我越来越难受。第一件事是重复开销太夸张。知识库里的热点问题非常集中比如“报销流程是什么”“如何申请远程办公权限”这类问题被反复提问语义几乎一模一样。但我们的代码没有做任何缓存每次提问都老老实实跑一整套 embedding 加向量检索加大模型生成。一次两次没关系一个月下来账单上大模型调用费用涨得让人肉疼。后来我统计了一下差不多有百分之三四十的请求问的问题和之前已经处理过的问题语义距离非常近完全没必要重新生成。第二件事是数据散。知识库文档的切片、用户会话状态、临时缓存的检索结果分散在好几个存储里。每次排查一个问题不得不在不同系统里跳来跳去。我当时的想法很简单如果向量检索和语义缓存能和 Redis 放在一起一个地方管状态一个地方管新旧数据运维心智会轻很多。1.2 AI 场景里 Redis 承担起的三个角色这次重构之后Redis 在 AI 应用里承担的角色其实可以拆成三个这三个角色也是目前 Redis 官方和社区推动“Redis for AI”的主要方向。第一个角色是向量存储与召回。把文档切片转成 embedding 之后向量本身以二进制形式写进 Redis Hash再通过 RediSearch 模块的向量索引做近邻检索。这个过程替代了之前单独的向量数据库Redis 同时还是一个持久化存储不用额外同步数据。第二个角色是语义缓存。这是省钱最直接的一层。每次用户提问先拿问题的向量去 Redis 里做一次 KNN 查询如果能在历史缓存里找到一个语义距离足够近的问题就直接把上一次的答案返回不再调用大模型。这样做的理论依据很简单一个问题只要语义足够接近答案基本是可以复用的。第三个角色是会话状态和上下文管理。AI 应用几乎离不开会话状态用户的对话上下文、知识库检索到的临时结果、生成过程里的中间信息都可以放进 Redis。相比 MySQL 或者普通文件存储Redis 的 TTL 机制天然适合这类“只活几分钟”的数据。这三个角色加在一起Redis 不再只是 AI 应用后面的一个加速小配件它已经进入主链路直接决定请求的走法和成本。1.3 什么样的项目适合立刻往这个方向迁移我明确不建议为了迁移而迁移。如果团队已经在用 Redis而且业务场景是知识库问答、商品搜索、日志语义分类、代码检索这类内容召回那这套方案非常合适。尤其是“召回结果相对稳定、重复提问多”的业务语义缓存能带来立竿见影的效果。反过来如果向量数据规模已经到几千万甚至上亿级别而且对召回延迟敏感度极高、检索时需要非常复杂的多字段过滤那还是应该考虑专门的向量数据库。Redis 的定位是“够用、好用、省心”不是“堆到极致”。这不是 Redis 的短板是选型时的基本判断。2. 向量检索为什么不能直接套关系库算法层面的基本盘2.1 精确相等搜索和近似最近邻的差距不是一点半点传统关系型数据库的索引本质上是为“精确匹配”和“范围查询”设计的。B 树把数据组织成有序结构查 id100走索引直接落位查 age BETWEEN 18 AND 30也能按序扫描。这种数据结构对排好序的数值型字段非常舒服。但向量数据完全不同。一个 embedding 模型生成的向量可能是 768 维、1024 维甚至 1536 维每个维度是一个浮点数。我们希望找到的是“距离这个向量最近的 N 个向量”而不是某个精确等于某值的记录。在高维空间里两个向量之间是否相似取决于它们整体方向的接近程度这没法用 B 树的下标去表达。说得更直白一点关系库擅长回答“哪个订单金额正好是一百元”而向量检索回答的是“哪几个商品的描述风格和这个问题最像”。这两种查询的目标和数据结构完全不同硬塞进关系库只能全表扫一遍然后逐一算距离数据量一大就完蛋。2.2 相似度度量怎么选余弦、内积还是欧氏距离向量检索的相似度计算常见三种余弦相似度、内积、欧氏距离。具体选哪个取决于 embedding 模型训练时的度量方式。我自己用到的规则是如果 embedding 模型做的是文本语义向量优先选余弦相似度因为它只关心方向不关心向量长度如果向量本身已经做了归一化内积和余弦在数学上是等价的可以把内积当作默认项因为计算更快。欧氏距离适合那些“向量各维度绝对差有业务含义”的场景比如坐标类数据。度量方式适合场景Redis 里的距离语义COSINE文本语义、内容相似度距离 1 - 余弦相似度越小越相似IP归一化后的向量、粗排距离和相似度正相关越大越相似L2坐标、图像特征等欧氏距离越小越相似在 Redis 的向量索引里KNN 默认返回的是距离值而不是相似度值这一点特别容易让人踩坑。我用的是 COSINE所以看着返回的 distance 值越小代表越相似而不是越大越好。第一次写语义缓存的人常常在这里弄反导致缓存全部失效或者命中错乱。2.3 Redis 为什么在这个场景里够用FLAT 和 HNSW 两条路Redis 的 RediSearch 模块为向量索引提供了两种算法FLAT 和 HNSW。FLAT 是暴力搜索也就是把所有向量从头到尾算一遍距离结果最准确但数据量大时耗时会线性上涨。它适合数据量在十万级别以内的场景。HNSW 则是用多层图结构做近似最近邻搜索牺牲一点准确率换取速度百万级数据也扛得住。我这次用的就是 HNSW。参数上重点关注 M 和 EF_CONSTRUCTIONM 控制每个节点的最大连接数M 越大召回质量越好但内存占用越高EF_CONSTRUCTION 控制建图时的候选集大小越大建索引越慢但图质量越高。实际项目里我先把 M 设成 40、EF_CONSTRUCTION 设成 200跑完一批真实查询效果不错后面没有再调。至于为什么不用专门的向量数据库这是个非常现实的选型问题。我们团队人员有限不想再维护一套独立集群Redis 本来就在线RAM 也够向量数据量目前只有百万级。在这个量级上专门为向量数据库再引入一套分布式系统管理成本和收益是不成正比的。什么时候需要换呢当向量量级进一步涨、过滤条件变得很复杂或者并发打到很高的时候再考虑。现在这一步Redis 完全兜得住。3. 带 AI 能力的 Redis 实例Docker 搭建与基础验证3.1 选 redis-stack 而不是原生 redis如果你打算让 Redis 参与 AI 检索第一件事就是别用裸的官方 redis 镜像。原生 Redis 本身没有向量检索能力所谓的“Redis 支持 AI”是依托在 Redis Stack 里的模块实现的主要是 RediSearch 和 RedisJSON 这两个模块。这里稍微展开一下。RediSearch 是 Redis 的搜索引擎模块负责建立倒排索引、向量索引处理 KNN 查询和混合过滤RedisJSON 负责让 JSON 字段在 Redis 里也能被原生读写和索引。AI 场景里我主要用 RediSearchRedisJSON 在个别需要存复杂结构的场景里也派上了用场。所以环境准备这一步我直接用了 redis/redis-stack-server 这个镜像。它把 Redis 主进程和 Search、JSON 等模块打到一个容器里启动就带能力不需要手动加载模块。3.2 docker-compose 拉起主从持久化别漏掉很多人本地测试就直接docker run redis跑完再关数据全丢。AI 项目里向量数据重建成本很高RDB 或 AOF 持久化一定要一开始就打开。当时我的 docker-compose 大概是这样的version: 3.8 services: redis-master: image: redis/redis-stack-server:latest container_name: redis-master ports: - 6379:6379 volumes: - redis-master-data:/data command: redis-server --appendonly yes --save 60 1000 redis-replica: image: redis/redis-stack-server:latest container_name: redis-replica ports: - 6380:6379 volumes: - redis-replica-data:/data command: redis-server --slaveof redis-master 6379 --appendonly yes depends_on: - redis-master volumes: redis-master-data: redis-replica-data:master 负责读写replica 负责实时从 master 同步这样主节点挂掉时至少有一份热数据可以顶上。切片切出来的向量数据几百 GB 到 TB 级别时AOF 重写和 RDB 备份策略要单独仔细设计但项目初期我建议先保证这两点appendonly 开启防止分钟级数据丢失定期 RDB 备份防止整个文件系统出问题。3.3 验证模块和数据类型先确认 FT 命令真的存在容器起来之后先别急着写代码。用 redis-cli 进去做两个基本检查。redis-cli -p 6379 # 检查模块 127.0.0.1:6379 FT._LIST 1) idx:doc # 确认 Redis 本体版本 127.0.0.1:6379 INFO SERVER如果 FT._LIST 返回空列表或者命令不存在说明镜像不对或者模块没加载。这时候你在代码里建索引会直接报错越早发现越好。另外这个阶段我重新过了一遍 Redis 的基本数据类型。很多新人会以为 AI 项目里只需要 string 和 hash其实远不止数据类型在 AI 项目里的用途STRING缓存大模型响应、限流计数、存储简单配置HASH最核心的数据载体一条文档切片就是一组字段LIST异步任务队列、消息暂存SET去重、标签集合ZSET排行榜、按分数排序的召回结果STREAM消费组模式下的日志流、事件流AI 应用的数据需求很杂一个 Redis 会把六种类型全部用上这并不奇怪。3.4 连接工具本地调试我用 Another Redis Desktop Manager命令行用多了效率不高查询和修改数据都靠肉眼盯命令行太痛苦。可视化连接工具我这边用的比较多的是 Another Redis Desktop Manager也就是常说的 ARDM。连上之后能看到所有 key按前缀筛选还能直接查看 hash 内部字段。调试向量数据时最有用的一点是能看到字段里的二进制长度确认 embedding 到底写没写进去。如果你更喜欢官方风格Redis Insight 也完全可以功能更全但偶尔有点重。两者选一个顺手的就行重点是连接参数的配置host、port、密码别漏了 TLS 配置公司网络环境里常常会启用。4. 把知识库问答完整跑通索引、检索、语义缓存一条龙4.1 文档切片与 embedding 写入先讲流程。知识库里的文档不可能整篇塞进一个向量太长会把语义稀释。我的切片策略是按固定长度加重叠窗口切分比如每 500 个字符一段重叠 50 个字符避免语义在切片边界被切断。切完之后每段内容调用 embedding 模型转成向量。我这里用的是 768 维的文本向量模型。转出来的向量是一个 numpy 数组直接放进 Redis 是不行的必须先转成小端字节序的 float32 字节串再写入。import numpy as np import redis r redis.Redis(hostlocalhost, port6379, decode_responsesFalse) def embed_text(text): # 调用 embedding 模型返回 np.float32 数组 ... vector embed_text(报销流程有哪些步骤).astype(np.float32) r.hset( doc:001:chunk:003, mapping{ chunk_id: 001-003, category: finance, content: 报销流程包括提交、审批、打款三个阶段, embedding: vector.tobytes(), }, )这里有个坑decode_responses 一定别设成 True。向量是二进制字节串如果客户端解码成字符串写入的向量会被破坏后面索引直接失效。写入批量数据时不要一条条 hset 硬写性能上不去。我一般用 pipeline一次打包几百条写命令再统一执行写入速度能快好几倍。数据量大时还可以考虑 Redis 的批量插入工具但 pipeline 对百万级数据完全够用。4.2 创建向量索引参数看着唬人理解后很简单写入的数据要能被近邻搜索必须先建索引。用 RediSearch 的 FT.CREATE核心语句如下FT.CREATE idx:doc ON HASH PREFIX 1 doc: SCHEMA chunk_id TEXT category TAG content TEXT embedding VECTOR HNSW 14 TYPE FLOAT32 DIM 768 DISTANCE_METRIC COSINE M 40 EF_CONSTRUCTION 200一行一行拆开解释。ON HASH 表示索引建在 Hash 类型上PREFIX 1 doc: 表示只索引 key 以 doc: 开头的 hash。SCHEMA 下面每一行定义一个字段TEXT 表示支持全文字段搜索TAG 表示做精确标签过滤最后的 embedding 字段用 VECTOR 关键字声明为向量字段。VECTOR HNSW 14 这里的 14 是后面参数的数量包括 TYPE、DIM、DISTANCE_METRIC、M、EF_CONSTRUCTION 这些。TYPE FLOAT32 和 DIM 768 必须和写入的向量完全一致DISTANCE_METRIC COSINE 对应我前面说的余弦距离。如果模型换成 1536 维这里的 DIM 必须同步改。建索引是一个阻塞操作吗数据量小的时候基本无感但如果是几百万向量FT.CREATE 需要一些时间。好在项目里我都是数据写完之后再建索引不频繁变动。4.3 检索流程实现KNN 查询和返回打分检索时拿用户的问题向量去索引里找最近的几条示例代码from redis.commands.search import Query question_vec embed_text(报销怎么走流程).astype(np.float32).tobytes() q ( Query(*[KNN 5 embedding $vec AS vector_score]) .params({vec: question_vec}) .return_field(content) .return_field(category) .return_field(vector_score) .sort_by(vector_score) .dialect(2) ) results r.ft(idx:doc).search(q) for doc in results.docs: print(doc.category, doc.vector_score, doc.content)注意几个细节。KNN 5 表示召回 5 条最近邻AS vector_score 是给距离结果起个别名方便排序和输出dialect 2 必须开KNN 查询依赖新版查询语法。如果要在召回时加过滤条件比如只从 finance 分类里检索可以在查询前面组合标签条件。具体语法在不同版本里略有差异建议以你实际模块版本的官方示例为准但思路是一致的先限定过滤范围再做近邻检索避免每次全库暴力扫。4.4 语义缓存是当期省钱的功臣整个项目里我最推荐先动手的不是向量检索反而是语义缓存。原因很简单它见效最快也最直接。原来每次提问都调用大模型接入语义缓存之后重复问题直接命中缓存不再产生大模型费用。实现逻辑我用文字描述一遍你在 Redis 里完全按这个思路做即可。用户提问时先对问题做 embedding。拿这个向量在“问题缓存索引”里做一次 KNN 查询找最近的几条历史问题。如果最近的距离小于阈值说明缓存命中直接返回这个历史问题对应的答案。如果距离大于阈值说明这是一个新问题才调用大模型生成答案然后把“问题向量、原始问题文本、答案文本”写回 Redis。代码结构大概是def get_answer(question): qvec embed_text(question).astype(np.float32).tobytes() q ( Query(*[KNN 3 embedding $vec AS vector_score]) .params({vec: qvec}) .return_field(answer) .return_field(vector_score) .dialect(2) ) cache_hits r.ft(idx:cache).search(q) if cache_hits.docs and float(cache_hits.docs[0].vector_score) 0.10: return cache_hits.docs[0].answer answer call_llm(question) r.hset( cache: str(uuid4()), mapping{ question: question, answer: answer, embedding: qvec, }, ) return answer这里 0.10 是我在余弦距离语义下调出来的阈值意思是“历史问题和当前问题的余弦相似度在 0.9 以上就算同一问题”。阈值别拍脑袋定最好拿几百条真实问题样本先测一遍看哪些是真正重复提问哪些是只是长得像而已。阈值调太小会疯狂刷缓存调太大又会让问题之间的微小差异被吞掉这个平衡值得花时间做。接入语义缓存一周之后团队统计到大模型调用量降了接近四成响应延迟也大幅下降。因为命中的请求完全不经过模型Redis 单次 KNN 查询只要几毫秒比一次大模型调用快一个数量级。5. 上线一周后这些坑才真正开始出现5.1 序列化和内存比预想更容易爆第一个坑不是功能层面的是内存。项目刚上线时我拿一万多条文档测试内存占用毫无压力后来全量切进去几个百万级向量一写入内存瞬间吃紧。算一下就知道原因768 维的 float32 向量每一条就是 768 乘以 4 字节约等于 3KB乘以一百万条就是 3GB。而且这还只是纯向量数据还没算 Hash 本身的元数据、索引结构、HNSW 图的内存开销。HNSW 的内存占用通常是原始向量的 1.5 到 2 倍左右所以百万条向量最终占个七八 GB 很常见。内存治理方面我做了三件事。第一控制 maxmemory policy不要盲目用 allkeys-lru因为向量缓存和文档主数据都是不能被随便淘汰的淘汰之后检索结果直接缺数据。我选择的是不设全局淘汰而是给会话类缓存单独设置 TTL让它们自然过期。第二关闭浏览器端无用的序列化所有 embedding 直接用二进制存储不用 pickle、不用 base64能用 tobytes 就 tobytes。第三监控 RSS 内存而不是只看 used_memory因为 HNSW 在底层分配的内存有时候不会马上反映在 used_memory 里。5.2 多 worker 并发写带来的文档错乱第二个坑和并发相关。团队里跑着好几个数据处理 worker它们同时切分文档、生成向量、写 Redis。结果就出现了同一个文档切片的记录被两个 worker 同时写入后写覆盖先写字段混了比如分类是 A 文档的正文是 B 文档的。这个问题本质上是分布式并发写入处理好坏的关键是加锁。Redis 分布式锁的实现其实不复杂核心就是 SET 命令带 NX 和 EXSET lock:doc:001 worker_1 NX EX 30只有 worker 拿到锁之后才允许写入这个文档相关的切片拿不到锁就跳过或者等待重试。释放锁的时候注意不要直接 DEL要用 Lua 脚本先校验持有者if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这样处理的原理是防止“锁已经过期其他 worker 拿到新锁但旧的 worker 还在执行任务最后把别人的锁误删掉”。一把锁带 owner 标识并且只能由 owner 释放这个问题就不会出现。5.3 慢查询和连接管理看似简单其实影响很大向量查询本身快但如果你用的是 FLAT 算法或者没给索引加过滤条件导致全库扫描慢查询很快就来。建议从一开始就打开慢日志监控。SLOWLOG GET 10能看到哪些查询拖慢了整体延迟。最常见的两个慢查询原因一个是查询时没有正确使用向量索引兜底走了全量扫描另一个是客户端连接没有复用每次请求新建连接握手开销在全链路延迟里占比很夸张。连接管理我这里特别提一句。AI 服务是典型的高并发场景连接池一定要开而且连接池大小要根据 QPS 做压测。我当时把连接池大小从 20 调到了 50P99 延迟掉了一截代价是 Redis 端连接数多了但这对 Redis 来说完全不是问题。5.4 embedding 模型换过以后索引直接变玄学最后一个坑比较隐蔽。有次我们想升级 embedding 模型觉得新模型效果更好。模型换完之后没有重新清洗旧的向量数据结果新旧向量混在一起。因为两个模型的向量空间完全不对齐新模型生成的向量去旧索引里做近邻检索召回结果简直像是在随机挑文档。这个问题没有增量修复的办法只能重建。后面我形成了一套固定流程embedding 模型有任何版本变化就同步换一个 key 前缀比如从 doc:2024: 换成 doc:2025:同时新建一套索引让新旧数据物理隔离。等新数据全部写进去、链路验证没问题再批量删除旧前缀的数据。这样最稳不会出现“检索一半新一半旧”的混沌状态。还有一个细节是语义缓存也会跟着失效。换了 embedding 模型之后缓存索引里的向量全是旧模型生成的必须一并清空重建否则缓存命中逻辑会把“语义上不相关”的问题当成同一个问题返回那比没有缓存还要糟糕。其实这套架构跑了一周多我最大的感受不是 Redis 变强了而是 AI 应用的落地方式越来越“具体”了。以前说起 AI 总觉得要上全套重型基础设施实际动手之后发现把已有的成熟存储用好反而更快更稳。如果让我给一条最直接的建议我会说先从语义缓存接起。它是整个链路里投入最小、收益最显眼的一块而且不需要动你已有的检索架构。等缓存跑顺了再逐步把向量检索也并进来Redis 一步一步走到主链路心态和排障成本的曲线都会平缓很多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Win10自动更新关闭真相:服务管控+策略锁定+行为监控三重方案 2026/9/30 10:44:33

Win10自动更新关闭真相:服务管控+策略锁定+行为监控三重方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
FPGA跨时钟域处理全解析:亚稳态、同步器与异步FIFO实战 2026/9/30 10:44:32

FPGA跨时钟域处理全解析:亚稳态、同步器与异步FIFO实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
上科大信息学院VDIC保研夏令营:流程、面试与导师选择复盘 2026/9/30 10:44:30

上科大信息学院VDIC保研夏令营:流程、面试与导师选择复盘

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
某省建筑监管平台params逆向 2026/9/30 10:44:29

某省建筑监管平台params逆向

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
ZYNQ嵌入式Linux移植实战:从FSBL到根文件系统的传统方式全流程 2026/9/30 10:44:28

ZYNQ嵌入式Linux移植实战:从FSBL到根文件系统的传统方式全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Java面试实战:Spring Boot、微服务与SSE流式输出全攻略 2026/9/30 10:44:20

Java面试实战:Spring Boot、微服务与SSE流式输出全攻略

最近帮几位朋友做了一轮大厂Java面试的模拟复盘,发现一个明显变化:面试官已经不满足于“背八股”了。Spring Boot、微服务依然是必考底盘,但AI技术相关的追问越来越多,经常一开口就是“你项目里大模型回答是怎么流式渲染的”“客户…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉