“Redis 已正式接入 AI !”这句话我在好几个技术群里都看到过转发。老实说,刚看到时我并没有太当回事,因为 Redis 想往 AI 上靠已经不是第一天了,社区里早就有人做向量检索插件、语义缓存中间件,各种“野路子”我都跑过。但真正把官方镜像拉下来、把客户端连上去、把一条带 embedding 的请求跑通以后,我明显感觉这次不一样:它不再是“外挂一个模块”,而是把 AI 工作负载当成一等公民来设计了。
这篇文章我不打算做新闻转述,也不想堆概念,就站在一个实际用过、踩过坑、又把这套东西往生产环境里搬过的人的角度,把 Redis 接入 AI 这件事拆开讲。适合谁看?两类人:一类是后端同学,你已经很熟 Redis 的缓存用法,但不知道 RAG、Agent 记忆、语义缓存到底该怎么落地;另一类是算法或全栈同学,你每天都在调大模型接口,但面对“上下文放哪、历史怎么存、并发怎么锁”这类工程问题时缺乏手感。看完你应该对整体方案、关键命令、数据模型设计、常见故障都有个清晰的认识。
1. 这个标题背后,Redis 到底改了什么
1.1 从“缓存数据库”到“AI 数据中间件”
过去我们提起 Redis,第一反应就是:缓存、计数器、分布式锁、排行榜、消息队列。它就像一个极其好用的内存工具箱,什么都能存一点,但没人指望它承担复杂的计算。AI 应用接入之后,情况变了。
大模型本身是无状态的,它不记得你昨天问过什么,也不知道你的知识库里有什么。所以任何 AI 应用都需要在模型外面挂一套“状态层”和“知识层”:用户会话记录、历史消息摘要、知识库切片、向量索引、语义缓存、任务锁。这些东西有两个共同点:一是延迟要求极高,模型调用本身已经要几百毫秒甚至几秒,数据层再慢就彻底没法用;二是数据结构五花八门,既有简单的 KV,又有需要相似性检索的高维向量。
Redis 进入 AI 领域,本质上就是把这两件事同时接住了。官方在 Redis 8 里把向量数据库相关的检索能力、语义缓存相关的流程,以及流式数据的处理能力直接纳入主线,而不是让用户在外部插件里折腾。对一个后端开发者来说,最直观的变化是:过去我要搭一套 RAG,得同时维护 Elasticsearch、向量库、MySQL、Redis 四套系统,现在相当一部分场景可以只用 Redis 打底,把链路缩短一大截。
1.2 AI 工作负载需要哪些数据能力
我把 AI 场景里最常出现的数据需求列一下,你会发现每条都和 Redis 的强项对得上:
- 高频读取的配置和上下文:模型 temperature、system prompt、用户偏好,这类数据要毫秒级读取。
- 会话历史:用户和 AI 多轮对话记录,需要按时间追加、定期裁剪。
- 知识库切片:文档被切分成若干 chunk,每个 chunk 有文本、元数据和 embedding 向量。
- 向量相似性搜索:用户问题向量化之后,找出最相关的 top-K 切片,送进模型上下文。
- 语义缓存:相同或相近的问题不必重复请求大模型,直接从缓存返回,省时省钱。
- 执行状态和幂等控制:Agent 任务可能并发触发,需要确保一次任务不会被重复执行,避免重复扣费或者重复写库。
这六类需求,在 Redis 里分别对应 String/Hash、Stream、Hash+Vector、索引检索、Hash+TTL、分布式锁。注意,它们不是六个独立项目,而是同一套 Redis 实例里不同类型的 key,用命名空间分开即可。这也是 Redis 做 AI 中间件的核心优势:一套基础组件,解决整个数据层的问题,运维成本远低于“每个需求引入一个中间件”。
1.3 三种常见的 Redis+AI 组合模式
根据我接触过的项目,Redis 参与 AI 工程的姿势基本可以归纳成三种:
第一种是知识库底座。不管你是给内部文档做问答,还是做行业客服机器人,都会涉及把文档切片、向量化、存进去,再在查询时做相似性检索。这是最标准的一种用法,也是 Redis 对标专业向量数据库的主要场景。
第二种是对话状态中心。AI 应用是典型的多轮交互,尤其 Agent 场景里还会涉及到多个子任务协同,状态必须集中管理,不能散落在各个进程里。用 Redis 存会话、存中间结果、存任务进度,天然跨实例共享。
第三种是模型调用的“减负层”。大模型接口有延迟有钱的问题,于是我们把常见问题、固定话术、重复性任务的结果缓存起来。Redis 在这里承担语义缓存,用向量相似度判断“这个问题之前是不是答过”。缓存命中一次,省下的可能就是几百毫秒和一笔 token 费用。
理解这三种模式以后,接下来所有实操环节都有抓手了。你在做方案设计时,先想清楚自己属于哪一种,然后再决定 key 怎么设计、数据怎么组织。
2. 动手前的基础:环境、客户端与序列化
2.1 用 Docker Compose 部署主从环境
很多教程会让你直接docker run -d redis,本地测测没问题,但一旦你要把数据接入 AI 应用,我建议起步就按主从来部署。为什么呢?AI 场景里 Redis 不只是缓存,它存着向量索引、会话记录,挂了损失很大。主从至少能让你在单点故障时有个底。
我自己常用的一个 docker-compose 配置大概是这样的:
version: "3.8" services: redis-master: image: redis/redis-stack:latest container_name: redis-master ports: - "6379:6379" - "8001:8001" command: ["redis-server", "--appendonly", "yes", "--requirepass", "yourpass"] volumes: - master-data:/data redis-replica: image: redis/redis-stack:latest container_name: redis-replica ports: - "6380:6379" command: ["redis-server", "--slaveof", "redis-master", "6379", "--requirepass", "yourpass", "--masterauth", "yourpass"] depends_on: - redis-master volumes: - replica-data:/data volumes: master-data: replica-data:几点说明:image 用的是redis/redis-stack,因为向量检索能力在 Stack 版本里开箱即用,不需要额外装模块。appendonly yes开启 AOF 持久化,避免实例重启后向量索引和会话数据全丢。从节点通过--slaveof挂到主节点后面,生产环境建议换成哨兵或者直接上 Redis Cluster,这里先保证你本地能跑通主从复制。
从节点能不能查?能,但要注意:Redis 主从默认情况下从节点是只读的,向量检索这类读操作走从节点没问题;写操作比如建立索引、写入 embedding,必须走主节点。客户端配置里要把读写分离的意图表达清楚。
2.2 连接工具和可视化客户端的选择
命令行redis-cli当然是最底层的工具,但接 AI 场景时数据往往是一大段 JSON、一串 float 数组,在命令行里肉眼检查非常痛苦。我建议至少装一个可视化客户端。
我用得比较多的是 Another Redis Desktop Manager,简称 ARDM。跨平台、免费、能看 TTL、能直接执行命令、能查看 key 的序列化格式。新版还有树形展示和命令监控面板,排查线上问题的时候特别好用。另外 RedisInsight 是官方出的,功能更全,能看到向量索引的统计信息、内存分析、慢查询日志,debug 向量检索时非常有价值。
排查向量问题时,我会在 RedisInsight 里直接输FT.SEARCH命令看返回结果,再对比用 Python 客户端拿到的结果,这样能快速定位问题到底出在命令参数还是序列化上。
2.3 数据序列化:先统一再接入
这是我最想强调的一个点,也是很多项目接 AI 后翻车的第一现场。Redis 本身不管你存进去的是什么,它记住的是字节。你用 Java 写了个对象,用 Jackson 序列化成 JSON 存进去;又用 Python 读出来,期望它自动反序列化成 dict,结果拿到一串字符串,得自己 parse。这种跨语言、跨框架的序列化不一致,在普通缓存场景里问题不大,在 AI 场景里会让你很崩溃。
我推荐两条规则:
一是通用格式统一用 JSON 或 MessagePack。只要没有极致性能要求,JSON 足够,可调试性强。二是向量字段要做专门编码。一个 1536 维的 float 数组转成 JSON 字符串,体积非常大,而且每次序列化反序列化都有 CPU 开销。更优做法是把它转成 bytes,用np.save或者简单的 struct pack 处理。向量数据在 Redis 里通常不是给你肉眼看的,它的目标是快速相似度计算。
另外,如果你的业务以 Java 为主,RedisTemplate 的序列化器一定要显式指定。很多人用默认的 JdkSerializationRedisSerializer,存进去的 key 会变成\xAC\xED...开头的一串乱码,存字符串还好,存对象再取出来就是 ClassCastException 重灾区。我通常用GenericJackson2JsonRedisSerializer做 value,用 StringRedisSerializer 做 key,这样至少跨语言读取时能看懂。
3. 把 Redis 接入 AI 的四个核心实操
3.1 向量检索与知识库问答:语义搜索落地
先看核心场景:知识库问答。你有一堆 PDF、Markdown、网页内容,要做一个“基于文档的问答机器人”。传统关键词搜索搞不定同义改写,必须用向量检索。
流程是:文档切块 -> 调用 embedding 接口变成向量 -> 写入 Redis -> 用户提问 -> 问题向量化 -> 在 Redis 里查相似向量 -> 把命中的原文和相似度分数交给大模型 -> 让模型基于这些片段回答。
写入这一步,我用的命令类似:
# 创建向量索引 FT.CREATE idx_docs ON HASH PREFIX 1 doc:emb: \ SCHEMA doc_id TAG \ title TEXT \ content TEXT \ embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE这个命令的意思是:让 Redis 为 key 前缀是doc:emb:的 Hash 类型建一个索引,其中embedding字段被解释为 HNSW 算法的向量,维度 1536,距离度量用余弦相似度。如果你的 embedding 模型输出的是 768 维或 1024 维,把 DIM 改成对应数字即可。
写入一条带 embedding 的记录,Python 伪代码大概是:
import numpy as np import redis from redis.commands.search.field import VectorField, TextField, TagField from redis.commands.search.indexDefinition import IndexDefinition r = redis.Redis(host='localhost', port=6379, username='default', password='yourpass', decode_responses=False) def add_doc(doc_id, title, content, embedding_vector): key = f"doc:emb:{doc_id}" value = { "doc_id": doc_id, "title": title, "content": content, "embedding": np.float32(embedding_vector).tobytes(), } r.hset(key, mapping=value) # 查询 from redis.commands.search.query import Query q = Query("*") .sort_by("embedding_score") .add_return_field("embedding_score") .return_fields("doc_id", "title", "content") .dialect(2) # 实际执行 KNN 查询 q = Query(f"*=>[KNN 5 @embedding $vec as embedding_score]").return_fields("doc_id", "title", "content", "embedding_score").dialect(2) res = r.ft("idx_docs").search(q, query_params={"vec": np.float32(user_vector).tobytes()})有几个细节必须提醒:
- 写入向量时一定要转成
float32的 bytes 再写,不要直接塞一个 Python list 进去,不要存 JSON 字符串。Redis 索引建立的时候是按固定字节取数的,格式不对会直接报错。 - 查询返回的相似度得分,由于 HNSW 的实现,分数是负数越接近 0 表示越相似,不同距离度量之间不能直接横向比较。
- 索引创建后,如果数据量大了,加字段或者改维度都比较麻烦,通常需要删索引重建。所以上线前要想清楚维度、距离度量和字段列表。
- 实际场景中,
content如果很长,建议拆成两个 key 或用单独的字段冗余,避免每次检索都拉大文本,浪费网络和内存。
3.2 语义缓存:给 LLM 调用省点钱
LLM 调用贵不贵?取决于你的频率。即便按比较便宜的模型计算,百万 token 也要几十块钱,如果你的业务是生产环境高频调用,一个月烧掉几万块非常正常。我在给一个客服项目做优化时,就发现实际请求里有大量重复问题,只是用户说法不一样,比如“怎么退款”和“退款流程是什么”其实是同一个意图。
这时候语义缓存就非常有价值。它不是传统 KV 缓存那样要求 key 完全相等,而是先把你当前的问题向量化,去 Redis 里做相似检索,如果找到相似度超过阈值的历史问答,就直接返回历史答案,不再调用大模型。
实现思路:
def get_cached_answer(r, question_embedding, threshold=0.92): q = Query("*=>[KNN 1 @embedding $vec as score]").return_fields("answer", "score").dialect(2) res = r.ft("idx_cache").search(q, query_params={"vec": question_embedding.tobytes()}) if not res.docs: return None score = float(res.docs[0].score) if score <= -threshold: # cosine 相似度越高,这个分越接近 -1 return res.docs[0].answer return None语义缓存的阈值调节是个手艺活。设高了,命中率太低,缓存形同虚设;设低了,语义不相关的问题可能被误判为相同,返回错误答案。我的建议是先上日志,抽样统计相似度分布,再根据业务容忍度选阈值。客服场景我一般从 0.92 开始调;如果是技术文档问答,话语相对固定,可以放到 0.88。
还有一点,语义缓存一定要设置 TTL。LLM 的答案不是永久有效的,政策、价格、活动都会变。给每条缓存记录设置可配置的过期时间,比如 24 小时或 7 天。同时你还需要一个“失效主动更新”机制:当后台文档更新时,删除相关的缓存 key。这就牵扯到你得在每个知识库文档的 key 上记录关联问题,维护一张映射关系。
3.3 Agent 记忆与会话状态:用 Stream 管好上下文
做 Agent 应用的人都会遇到一个尴尬:模型上下文窗口有限,多轮对话往下走,之前的内容要么被截断,要么被粗粒度摘要。这个状态放哪里?很多人一开始放在应用内存里,一重启全没了,多实例部署还会出现用户请求被负载均衡到不同节点,上下文不连续。
Redis 的 Stream 数据结构非常适合做会话日志。它天然支持追加、按时间范围读取、分组消费,比 List 做消息队列更顺手,也比把所有历史存成一个 JSON 字段更灵活。
基本写法:
# 追加一条用户消息 XADD chat:session_1001 * role user content "你好,我想了解退款政策" # 追加一条 AI 回复 XADD chat:session_1001 * role assistant content "您好,退款政策如下..." # 读取最近 20 条 XLEN chat:session_1001 XRANGE chat:session_1001 - + COUNT 20应用侧用 Stream 时,我建议给每个会话 key 设置 TTL,比如 48 小时或 7 天。不设置的话,用户量一大,内存直接爆掉。有些团队担心设置 TTL 会把重要记忆删了,我的方案是:当 Stream 的最后一笔写入临近过期时,把它做一次摘要并转存到一个summary:{session_id}的 Hash 里,这样长对话可以被压缩成少量 token 给模型初始化,原始明细定期淘汰。
再进一步,做 Agent 的时候,你不仅要存用户和 AI 的对话,还要存工具调用的中间过程,比如任务 ID、调用参数、返回结果。这些过程数据可以写成带有task_id字段的 Stream 消息,配合 Redis 的消费者组来实现多实例协作。一个任务被多个 Agent 实例拉起来跑,需要避免重复处理,这就引出了文章后面要说的分布式锁。
3.4 分布式锁:并发任务幂等性的兜底
AI 任务通常不是单一的“输入-输出”,它会有很多副作用:写库、发消息、调第三方接口。一旦重复执行,轻则浪费 token,重则产生脏数据、重复扣款。分布式锁在这里是必需品。
Redis 分布式锁的老祖宗是SET key value NX PX 30000。命令本身不复杂,但坑很多。我挑几个我实际踩过的说:
第一个坑是锁的 key 设计。很多人的锁 key 是lock:task:{id},这没问题,但 value 一定不能用固定字符串。我建议用一个全局唯一的 requestId,释放锁时先对比 value 再删,防止“自己释放了别人的锁”。虽然这句话在文档里被说了无数次,但确实是一直有人犯。
第二个坑是锁超时时间不好定。AI 任务没法掐表预算,有的任务几秒钟,有的要几分钟。锁时间设短了,任务没跑完锁就自动过期,另一个实例进来重复执行;设长了,一旦持有锁的进程挂掉,其他实例要等很久。我的做法是:加一个看门狗机制,用定时任务在锁即将过期时续期,任务完成时显式释放。如果没有能力做看门狗,至少要在锁的 value 里带上任务开始时间,并在业务逻辑里做最终幂等校验。
第三个坑是 RedLock 的争论。网上关于 RedLock 的讨论很多,站在工程实践角度,我的建议是:如果你的业务在单 Redis 主从架构里,分布式锁够用;如果锁的是“扣费”这类强一致操作,那就不只是 Redis 层的问题了,必须把幂等表、数据库唯一约束、状态机一起设计。Redis 锁只能防并发,不能防业务错误。
放一段简单的锁代码思路:
import time import uuid import redis r = redis.Redis(host='localhost', port=6379, password='yourpass') def acquire_lock(lock_key, timeout_ms=30000): request_id = str(uuid.uuid4()) ok = r.set(lock_key, request_id, nx=True, px=timeout_ms) if ok: return Lock(lock_key, request_id) return None class Lock: def __init__(self, key, request_id): self.key = key self.request_id = request_id def release(self): # Lua 脚本原子地判断 value 再删除,防止误删 script = ''' if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end ''' r.eval(script, 1, self.key, self.request_id)4. 缓存治理与集群:从单机到生产
4.1 缓存治理的三个“穿透”场景
把 Redis 接进 AI 系统之后,缓存治理的重要性不降反升。因为 AI 应用的缓存不仅有常规接口数据,还有向量索引、语义缓存、Token 计数,任何一层崩溃都可能引发连锁反应。
第一个要防的是缓存穿透。用户疯狂问一个知识库里没有答案的问题,语义缓存每次都不命中,每次都去调大模型,账单开始飙升。我的解决办法是:对答案为空的问题也缓存一条带特殊标记的记录,TTL 设短一点,比如 10 分钟。同时在上游加一个接口层面的限流,把恶意/刷量请求挡在 Redis 之前。
第二个是缓存击穿。某个热点 key 在过期的一瞬间被大量请求同时访问,所有请求都穿透到下游大模型,瞬间 QPS 爆掉。这时候用互斥锁重建缓存,或者用逻辑过期时间配合后台异步刷新,都比裸设 TTL 靠谱。
第三个是缓存雪崩。大量 key 在同一时间段过期,导致一波集中穿透。解决办法很简单:TTL 不是固定值,而是基础时间加上一个随机偏移量。我在项目里通常写成base_ttl + random.randint(0, 300)秒,打散过期时间。
另外还有内存治理。向量数据是内存大户,1536 维的 float32 向量,一条就是 6KB,十万条就是 600MB。所以在做容量规划时,要单独估算向量 key 的内存占用,不能让它们和其他缓存共用一块内存而互相挤兑。Redis 8 支持键空间隔离吗?目前主要策略还是从服务层面拆分,比如单独部署一个 Redis 实例专门放向量数据。
4.2 Redis 集群部署要考虑的事
单机 Redis 扛不住生产,上集群是迟早的事。但集群部署对 AI 场景有几个直接影响。
第一个是 key 分布。Redis Cluster 按 key 的 hash slot 分布数据,主从模式则可以使用任意 key。如果你像我们项目那样,把知识库的所有 chunk 都写在doc:emb:前缀下,在 Cluster 模式下它们可能会被分散到不同节点。检索时如果走集群,那就要用FT._LIST确认每个节点上的索引,或者直接用开启集群支持后的统一响应。我建议在 Cluster 模式测试前做一次“小规模验证”,确定返回的 top-K 结果的跨节点聚合行为符合预期。
第二个是读写拓扑。主从模式下,从节点负责读,主节点负责写,而向量索引的写入和检索如何分流?答案是:索引只建在主节点上,从节点同步数据的同时会同步索引结构。检索可以走从节点,但刚写入的数据可能因为复制延迟不被立即检索到。如果你做的是“文档上传立即问”的场景,要注意这个最终一致性问题。解决办法是在业务上牺牲一点实时性,或者写入后强制读主节点。
第三个是哨兵与 Cluster 的选择。我的经验:中小规模、主从加哨兵足够,配置简单,故障自动切换;大规模、数据量达到 GB 级且需要水平扩展,上 Cluster。但 Cluster 的运维复杂度高,迁移槽位、滚动升级都要做得很熟练才行。
4.3 数据类型选型的经验表
为了让大家少踩坑,我把 AI 场景中常用的数据组织方式整理成一个选型参考:
| 业务需求 | 推荐类型 | 使用要点 |
|---|---|---|
| 用户画像、配置、单条上下文 | Hash | 字段可单独更新,序列化友好 |
| 知识库切片 + 向量 | Hash + Vector | 前缀统一,建索引时指定 SCHEMA |
| 对话历史、任务日志 | Stream | 追加写入,按时间查询,配合 TTL |
| 排行榜、热点问题 TopN | ZSet | 分值即热度,适合推荐场景 |
| 语义缓存 Q&A | Hash + Vector | 答案字段存文本,嵌入向量存 bytes |
| 任务锁、幂等控制 | String + NX | value 唯一,释放用 Lua 原子操作 |
5. 踩坑手记:超时、丢数、向量失效
5.1 Lettuce 的 Command timed out
项目里遇到最多的报错就是这句:
redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException这个错误从 Lettuce 客户端视角看,是某个命令在指定时间内没等到响应。我排查过几次,常见原因有四种:
一是 Redis 服务端真的有问题。比如有个超大 key,HGETALL返回了几 MB 数据,网络传输时间超过了客户端超时设置。AI 场景里最容易出现的是把大向量或者大 JSON 一次性写入,我就遇到过有人把一个 5MB 的 JSON 塞进 Redis,然后下游读取超时。
二是连接池耗尽。Lettuce 本身是异步多路复用的,但如果你配置了连接池,并发高时连接全部被占用,新请求排队,排队时间加上执行时间一起超时。解决办法是合理设置maxTotal、maxIdle、minIdle,同时给命令设置独立的 timeout。
三是阻塞命令。BLPOP、BRPOP、XAUTOCLAIM这类阻塞命令,如果在业务线程里跑,会把线程长时间卡住,导致其他命令拿不到连接。
四是网络抖动和跨机房调用。如果 Redis 和 AI 服务不在同一个可用区,网络 RTT 本身就高,超时阈值默认 2 秒很容易被打穿。我的建议是:核心链路尽量同机房部署,超时时间按 P99 延迟的 3 倍来设,避免盲目调大。
排查方法也别乱猜,用redis-cli --latency先看网络延迟,再用SLOWLOG GET 10看慢命令。很多超时都是慢命令造成的,把慢命令列表拉出来,问题基本就显形了。
5.2 重启后数据消失:持久化配置经验
有位同事问我:“Redis 不是说数据在内存里吗,怎么重启之后向量索引全没了?”这其实不是 bug,是持久化配置问题。
Redis 默认配置下,持久化级别并不高。RDB 是定时快照,可能丢最后一次快照之后的数据;AOF 是追加日志,配合appendfsync everysec最多丢一秒数据。向量索引和知识库属于重建成本很高的数据,我建议开启 AOF 并且定期做 RDB 备份。
配置参考:
appendonly yes appendfsync everysec save 900 1 save 300 10 save 60 10000注意一个细节:如果是 Cluster 模式,建议关闭 AOF 自动重写时可能出现的长时间阻塞,或错开业务高峰。向量数据量大时,BGSAVE和BGREWRITEAOF都会占用大量内存和 CPU,我踩过一次生产故障,就是在高峰期触发了快照,导致命令延迟飙到秒级。后来我把自动快照时间往后调,改成业务低峰期手动执行SAVE,情况立刻好转。
5.3 向量搜索结果很差的排查路径
向量检索效果差,先别怪模型。按照这个顺序排查:
第一,确认查询向量和文档向量来自同一个模型。不同模型的向量空间不兼容,直接用余弦相似度比较基本等于瞎猜。
第二,确认向量维度匹配。你建索引写了 DIM 1536,结果查询时传了一串 1024 维,Redis 要么报错,要么检索结果毫无意义。
第三,确认向量做了归一化。HNSW 用余弦距离时,向量方向是核心,模长不重要。如果你的 embedding 结果模长和分布差异很大,可以先归一化再写进去,检索稳定性会好很多。
第四,确认 top-K 和阈值设置合理。K 设太大,低相关片段被塞进上下文,反而干扰模型回答;K 设太小,关键信息被漏掉。我一般根据文档切块大小来定,每块 500 字左右,K 取 3 到 5。
第五,确认数据覆盖和切块质量。有时候不是检索的问题,是文档切块太碎了。一句话一个 chunk,向量信息量不够,相似度自然低。我建议切块按“段落完整性优先”,每块保留足够上下文,必要时加 20% 重叠。
5.4 运维侧的小经验
再补几条运维层面的经验,都是真金白银换来的。
一是监控命中率。语义缓存命中率低于 20% 说明阈值太高或者用户问题太分散,缓存基本白搭;命中率高于 95% 又可能说明业务太固定,不需要大模型。要找到自己的合理区间。
二是控制向量 key 的无上限增长。很多人写完向量就不管了,内存一天比一天大。我建议每个文档批次写入时记录批次号,并定期清理不再使用的批次。
三是备份恢复演练。向量索引重建可不像普通缓存那样自动回源,它依赖外部 embedding 接口。如果不小心把 Redis 数据清空,你要重新调用 embedding 接口给全部文档算一遍向量,这个成本可能因为接口限流而花掉大半天。定期做恢复演练,能提前发现备份不可用的问题。
6. 最后说一点个人体会
项目做多了之后,我最大的感受是:AI 工程里真正难的不是模型本身,而是围绕模型的那一层工程底座。Redis 接入 AI,并不是让你丢掉以前学的缓存、数据结构、集群知识去学一套新东西,而是把它已有的能力重新组合,服务于一套新的工作负载。向量检索、语义缓存、流式会话、分布式锁,每一个单独拎出来都是 Redis 圈子里很老的话题,但放在 AI 链路里,它们焕发了新的生命力。
如果你现在正打算给 AI 应用加一层 Redis,我的建议很简单:先不要追求最新特性,先把最朴素的事情做好,规划好 key 前缀、序列化方案、TTL 和内存预算。等这些基本功扎实了,再上向量检索和语义缓存。别一上来就铺很大的摊子,出问题时你会被数据模型设计、内存瓶颈、跨语言序列化、复制延迟这些问题同时夹击。
如果你已经在用 Redis 做 AI,欢迎多交流。毕竟这个方向变化太快,今天我和你说 HNSW 参数怎么调,可能过两个月官方又出了更顺手的方案。但架构思维是相通的:数据层永远是 AI 应用最坚实的底盘。