☰
Redis 在 AI 应用中的实战:缓存、限流、向量检索与避坑指南
2026/10/3 23:43:27 网站建设 项目流程

Redis 这个名字,做后端的基本都绕不开。缓存、分布式锁、消息队列、排行榜、限流,几乎每个项目里都能看到它的身影。但最近圈子里讨论得比较多的一个话题是:Redis 跟 AI 的结合。乍一听好像有点违和——一个内存数据库,跟大模型、AI Agent 能扯上什么关系?我一开始也是这么想的,直到自己在几个项目里真的把 Redis 用在了 AI 相关的场景上,才发现这个组合比想象中要自然得多。

这篇文章不打算泛泛地聊"Redis 接入 AI"这个概念,而是从我实际踩过的坑出发,把 Redis 在 AI 应用里到底扮演什么角色、怎么用、哪些地方容易翻车,一条一条拆开讲。不管你是刚接触 Redis 的新手,还是已经用了几年但没想过把它跟 AI 场景结合的老手,应该都能从里面找到一些能直接拿去用的东西。

1. Redis 在 AI 应用里到底承担什么角色

1.1 为什么 AI 场景会需要 Redis

先说一个最直观的场景。你做了一个基于大模型的对话应用,用户每次发消息,你都需要把之前的对话历史带上,否则模型就"失忆"了。这个对话历史存哪里?存数据库当然可以,但每次请求都要查一次库,延迟摆在那里。而且对话上下文这种东西,天然就是"临时性"的——用户可能聊了十轮就不聊了,这些数据没必要永久保存。

Redis 在这里的优势就出来了:内存读写,延迟在亚毫秒级别;自带 TTL,可以给每个会话设置过期时间;支持 List、Hash、Sorted Set 等多种数据结构,能灵活地组织对话历史。我实测下来,用 Redis 存对话上下文,相比每次查 MySQL,首字节响应时间能省下几十毫秒。单次看起来不多,但对话应用是高频交互,累积起来用户体验的差距很明显。

除了对话上下文,AI 应用里还有几个地方特别适合 Redis:

  • 推理结果的缓存:同样的 prompt 如果短时间内被多次请求,没必要每次都调用模型。把结果缓存起来,命中就直接返回。这个在客服机器人、FAQ 类应用里效果特别明显。
  • 限流与配额:AI 接口通常按 token 计费,你需要控制每个用户的调用频率和用量。Redis 的计数器加过期时间,是实现限流最顺手的方式。
  • 任务队列:批量推理、异步生成图片、文档向量化这些耗时操作,用 Redis 做队列来削峰填谷。
  • 向量检索的辅助索引:虽然专门的向量数据库更专业,但在数据量不大、要求不高的场景下,Redis 也能承担一部分相似度检索的活儿。

1.2 Redis 做 AI 中间件的核心优势

很多人一提到 AI 基础设施,第一反应是向量数据库、GPU 集群、推理框架。这些当然重要,但 Redis 的定位不一样——它更像是 AI 应用里的"神经中枢",负责快速调度和状态管理。

我总结下来,Redis 在 AI 场景里的核心优势有三个:

第一是速度。AI 应用对延迟极其敏感。用户等模型生成已经要等好几秒了,如果中间还要因为查缓存、查会话状态再额外等几百毫秒,体验就会很割裂。Redis 的内存特性决定了它在这方面的表现是其他存储方案很难比的。

第二是数据结构的灵活性。这一点经常被低估。比如你要实现一个"最近 N 轮对话"的功能,用 List 的LPUSH+LTRIM两行命令就搞定了。你要做用户调用量的滑动窗口限流,用 Sorted Set 按时间戳存请求记录,然后ZRANGEBYSCORE一查就出来了。这些操作如果用关系型数据库来做,SQL 会复杂得多,性能也差得远。

第三是生态成熟。Redis 的客户端几乎覆盖了所有主流语言,Python、Java、Go、Node.js 都有非常成熟的库。你做 AI 应用大概率用的是 Python,redis-py这个库用起来非常顺手,跟 LangChain、FastAPI 这些框架的集成也很自然。

1.3 一个典型的 AI 对话应用架构

我拿自己做过的一个项目举例,说一下 Redis 在整体架构里的位置。

用户请求进来之后,先经过一层网关做鉴权和限流,限流用的就是 Redis 的计数器。通过之后,业务层从 Redis 里取出这个用户的对话历史,拼装成完整的 prompt 发给大模型。模型返回结果后,把新的对话轮次写回 Redis,同时更新 TTL。如果这个 prompt 之前被问过且缓存没过期,直接返回缓存结果,连模型都不用调。

整个流程里,Redis 承担了限流、会话管理、结果缓存三个职责。数据库只在需要持久化重要数据(比如用户付费记录、长期记忆)的时候才介入。这样设计的好处是,绝大部分请求路径上只有 Redis 和模型两个环节,链路短,延迟低。

注意:对话历史不要无限增长。我见过有项目把用户所有历史对话都塞进 prompt,结果 token 消耗爆炸,成本飙升。正确做法是只保留最近 N 轮,或者做摘要压缩。

2. 用 Redis 管理对话上下文的具体做法

2.1 数据结构选型:List 还是 Hash

存对话历史,最常见的两种选择是 List 和 Hash。我一开始用的是 List,每条消息作为一个元素,LPUSH进去,读的时候LRANGE出来。这种方式简单直接,适合"只关心最近几轮"的场景。

但后来遇到一个问题:我需要给每条消息打标签,比如区分是用户发的还是模型发的,还要记录时间戳和 token 数量。用 List 的话,每个元素就得存一个 JSON 字符串,读出来还要反序列化,稍微有点别扭。

换成 Hash 之后就舒服多了。用HSET存,field 是消息 ID,value 是消息内容加元数据。读取的时候HGETALL一次性拿出来。Hash 的好处是单个字段可以独立更新,比如我只想更新某条消息的状态,不用把整个列表重写。

具体怎么选,我的经验是:

场景推荐结构理由
只存纯文本对话,按顺序读取List操作简单,LPUSH+LTRIM天然支持滑动窗口
消息带元数据,需要单独更新字段Hash字段级操作,不用整体重写
需要按时间范围查询Sorted Setscore 存时间戳,支持范围查询
对话轮次少、结构固定String + JSON最简单,一次GET/SET搞定

我现在的做法是混合使用:对话主体用 List 存,保证顺序和滑动窗口;每条消息的元数据单独用一个 Hash 存,key 是消息 ID。这样两边各取所长。

2.2 会话过期与内存控制

对话数据是典型的"热数据",大部分会话在几小时后就没人碰了。如果不设过期,Redis 内存会被慢慢吃满。我的做法是给每个会话的 key 设置 TTL,比如 2 小时。每次用户有新消息进来,就刷新一次 TTL。

这里有个细节要注意:Redis 的 TTL 刷新是EXPIRE命令,但如果你用的是SET带EX参数,每次都会重置。我建议用EXPIRE单独刷新,逻辑更清晰。

内存控制还有一招是设置maxmemory-policy。对于纯缓存用途的 Redis 实例,我一般设成allkeys-lru,让 Redis 自动淘汰最久未使用的 key。但如果是存会话这种不能随便丢的数据,就要用volatile-lru,只淘汰设了过期时间的 key。

# 查看当前内存策略 redis-cli CONFIG GET maxmemory-policy # 设置为只淘汰带过期时间的 key redis-cli CONFIG SET maxmemory-policy volatile-lru

提示:生产环境不要用noeviction,除非你确定内存永远够用。我踩过一次坑,内存满了之后所有写操作都报错,服务直接不可用。

2.3 对话历史的序列化方案

存进 Redis 的数据需要序列化。JSON 是最通用的选择,可读性好,调试方便。但 JSON 有个问题:体积大,序列化/反序列化有开销。对于 token 敏感的场景,可以考虑 MessagePack 或者 Protobuf。

我实测过一组数据:同样一段 500 字的对话历史,JSON 序列化后约 1.2KB,MessagePack 约 0.8KB,Protobuf 约 0.6KB。差距不算特别大,但如果你的 QPS 很高,累积起来的内存和带宽节省还是很可观的。

不过我的建议是:除非你确实遇到了性能瓶颈,否则优先用 JSON。可读性带来的调试便利,在开发阶段价值很高。等真的需要优化了再换。

Python 里用redis-py存 JSON 的典型写法:

import json import redis r = redis.Redis(host='localhost', port=6379, db=0) def save_message(session_id, message): key = f"chat:{session_id}" r.lpush(key, json.dumps(message, ensure_ascii=False)) r.ltrim(key, 0, 49) # 只保留最近 50 条 r.expire(key, 7200) # 2 小时过期 def get_history(session_id, count=10): key = f"chat:{session_id}" items = r.lrange(key, 0, count - 1) return [json.loads(item) for item in reversed(items)]

这段代码里ensure_ascii=False很重要,否则中文会被转义成\uXXXX,体积翻好几倍。

3. 缓存 AI 推理结果的门道

3.1 什么样的结果值得缓存

不是所有 AI 调用都适合缓存。我的判断标准是:输入确定性高、输出变化小、调用成本高。三个条件同时满足,才值得缓存。

举个例子,用户问"今天天气怎么样",这个结果跟时间、地点强相关,缓存了很快就过期,不值得。但如果用户问"Python 里怎么读取 JSON 文件",这个答案相对固定,缓存起来就很划算。

还有一种情况是 embedding 计算。把一段文本转成向量,这个操作是确定性的——同样的文本永远得到同样的向量。所以 embedding 结果非常适合缓存,而且缓存命中率通常很高,因为很多文本会被重复处理。

我做过一个统计,在一个文档问答系统里,embedding 缓存的命中率能达到 40% 以上。这意味着将近一半的 embedding 计算被省掉了,成本直接砍掉四成。

3.2 缓存 key 的设计

缓存 key 的设计是个技术活。最直接的做法是把整个 prompt 做 hash 当 key,但这样有个问题:prompt 里哪怕多一个空格,hash 就变了,缓存命中率会很低。

我的做法是分层设计 key:

  • 对于完全相同的请求,用 prompt 的 SHA256 做 key。
  • 对于语义相同但表述不同的请求,需要做语义归一化,比如去掉多余空格、统一大小写、提取核心意图。
  • 对于带参数的请求,把参数拼进 key 里,比如cache:translate:en-zh:{text_hash}。
import hashlib def make_cache_key(prefix, prompt, **params): normalized = prompt.strip().lower() param_str = "&".join(f"{k}={v}" for k, v in sorted(params.items())) raw = f"{normalized}|{param_str}" digest = hashlib.sha256(raw.encode()).hexdigest()[:16] return f"cache:{prefix}:{digest}"

用 SHA256 而不是 MD5,是因为 MD5 有碰撞风险,虽然概率极低,但缓存场景下碰撞会导致返回错误结果,不值得冒这个险。截取前 16 位是为了控制 key 长度,16 位十六进制有 64 位熵,实际使用中碰撞概率可以忽略。

3.3 缓存过期策略与一致性

AI 推理结果的缓存过期时间怎么定?这个没有标准答案,取决于你的业务。我的经验是:

  • 事实性问答:可以设长一点,比如 24 小时甚至 7 天。因为事实不会天天变。
  • 时效性内容:比如新闻摘要、股价分析,设短一点,几分钟到几小时。
  • 个性化推荐:基本不适合缓存,因为每个用户的结果都不一样。

一致性方面,AI 缓存比传统缓存要宽松一些。传统缓存最怕脏数据,但 AI 推理结果即使稍微过时,通常也不会造成严重后果。所以我不建议在 AI 缓存上做复杂的失效逻辑,简单粗暴地靠 TTL 过期就够了。

注意:如果模型版本更新了,旧缓存可能就不适用了。我的做法是在缓存 key 里带上模型版本号,比如cache:v2:...。模型升级时改一下版本号,旧缓存自然失效。

4. 限流、队列与 AI 任务的异步化

4.1 用 Redis 做 AI 接口限流

AI 接口的限流比普通接口更重要,因为每次调用都是真金白银。我一般会做两层限流:一层是频率限制,比如每分钟最多 10 次;另一层是配额限制,比如每天最多 1000 次。

频率限制用固定窗口计数器就够了:

def check_rate_limit(user_id, limit=10, window=60): key = f"ratelimit:{user_id}:{int(time.time()) // window}" current = r.incr(key) if current == 1: r.expire(key, window) return current <= limit

这段代码的逻辑是:把时间按窗口大小切分,每个窗口一个计数器。第一次请求时设置过期时间,后续请求累加。简单有效,缺点是窗口边界处可能有突发流量。

如果要更平滑,可以用滑动窗口,基于 Sorted Set 实现。把每次请求的时间戳作为 score 存进去,查询时统计窗口内的数量。代价是内存占用更高,因为要存每次请求的记录。

配额限制就更简单了,一个计数器加一个较长的过期时间:

def check_quota(user_id, daily_limit=1000): key = f"quota:{user_id}:{datetime.now().strftime('%Y%m%d')}" current = r.incr(key) if current == 1: r.expire(key, 86400 * 2) # 留点余量,避免跨天边界问题 return current <= daily_limit

4.2 批量推理任务的队列设计

批量推理、文档向量化、图片生成这些任务,不适合同步处理,因为耗时太长。用 Redis 做队列是常见方案。

最简单的做法是用 List 做 FIFO 队列:生产者LPUSH,消费者BRPOP。BRPOP是阻塞式的,没有任务时会挂起,不消耗 CPU。

# 生产者 def submit_task(task): r.lpush("ai:tasks", json.dumps(task)) # 消费者 def worker(): while True: _, raw = r.brpop("ai:tasks", timeout=5) if raw: task = json.loads(raw) process(task)

但这个简单方案有个问题:如果消费者处理到一半挂了,任务就丢了。要保证可靠性,得用BRPOPLPUSH(或者新版本 Redis 的BLMOVE),把任务从待处理队列移到处理中队列。处理完成后再从处理中队列删除。这样即使消费者挂了,任务还在处理中队列里,可以重新投递。

def reliable_worker(): while True: raw = r.blmove("ai:tasks", "ai:processing", timeout=5, src="LEFT", dest="RIGHT") if raw: task = json.loads(raw) try: process(task) r.lrem("ai:processing", 1, raw) except Exception as e: # 处理失败,记录日志,任务留在 processing 队列里人工介入 log_error(task, e)

4.3 任务状态追踪与结果回传

异步任务提交之后,用户需要知道任务进度和结果。我的做法是给每个任务分配一个 ID,用 Hash 存任务状态。

def submit_and_track(task): task_id = str(uuid.uuid4()) r.hset(f"task:{task_id}", mapping={ "status": "pending", "created_at": time.time(), "result": "" }) r.expire(f"task:{task_id}", 86400) task["task_id"] = task_id r.lpush("ai:tasks", json.dumps(task)) return task_id def get_task_status(task_id): return r.hgetall(f"task:{task_id}")

消费者处理完成后,更新status为completed,把结果写进result字段。前端轮询这个接口就能拿到进度。如果任务量大,还可以用 Redis 的 Pub/Sub 做实时推送,避免轮询。

5. 向量检索:Redis 能不能替代专用向量库

5.1 Redis 的向量检索能力边界

Redis 从 4.0 版本开始支持模块,其中 RediSearch 模块提供了向量相似度检索功能。这意味着你可以在 Redis 里存向量,然后做 KNN 查询。

但我要泼一盆冷水:Redis 的向量检索能力,跟专门的向量数据库(比如 Milvus、Qdrant、Weaviate)比起来,还是有明显差距的。主要体现在几个方面:

  • 索引类型:Redis 支持 FLAT 和 HNSW 两种索引。FLAT 是暴力搜索,准确但慢;HNSW 是近似搜索,快但有一定精度损失。专用向量库的索引类型更丰富,调优空间更大。
  • 数据规模:Redis 是内存数据库,向量数据全放内存里,成本很高。百万级向量还能扛,上千万级就不太现实了。
  • 过滤能力:向量检索经常需要结合元数据过滤,比如"在某个分类下找相似向量"。Redis 的过滤能力相对有限。

所以我的建议是:数据量在十万级以内、对延迟要求极高、且已经在用 Redis 的场景,可以用 Redis 做向量检索。数据量大或者对检索精度要求高的,还是老老实实上专用向量库。

5.2 用 RediSearch 做相似度检索的实操

如果你决定用 Redis 做向量检索,基本流程是这样的。首先要确保 Redis 加载了 RediSearch 模块。用 Docker 的话,直接用redis/redis-stack镜像就行。

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

然后创建索引。假设我们要存文档向量,每个向量 768 维(这是很多 embedding 模型的输出维度):

from redis import Redis from redis.commands.search.field import VectorField, TextField from redis.commands.search.indexDefinition import IndexDefinition, IndexType r = Redis(host='localhost', port=6379) schema = ( TextField("content"), VectorField( "embedding", "HNSW", { "TYPE": "FLOAT32", "DIM": 768, "DISTANCE_METRIC": "COSINE" } ) ) r.ft("idx:docs").create_index( schema, definition=IndexDefinition(prefix=["doc:"], index_type=IndexType.HASH) )

存数据的时候,向量要转成字节:

import numpy as np def store_doc(doc_id, content, embedding): vec_bytes = np.array(embedding, dtype=np.float32).tobytes() r.hset(f"doc:{doc_id}", mapping={ "content": content, "embedding": vec_bytes })

查询的时候,把查询文本的 embedding 传进去做 KNN:

from redis.commands.search.query import Query def search_similar(query_embedding, top_k=5): vec_bytes = np.array(query_embedding, dtype=np.float32).tobytes() q = Query(f"*=>[KNN {top_k} @embedding $vec AS score]") \ .sort_by("score") \ .return_fields("content", "score") \ .dialect(2) results = r.ft("idx:docs").search(q, query_params={"vec": vec_bytes}) return results.docs

5.3 什么情况下该换专用向量库

我踩过一次坑:项目初期用 Redis 做向量检索,数据量小的时候跑得很好,延迟低、部署简单。但数据涨到 200 万条之后,内存占用飙升到几十 GB,成本比用专用向量库还高,而且 HNSW 索引重建的时候会阻塞查询。

所以我的判断标准是:

维度用 Redis换专用向量库
向量数量10 万以内10 万以上
内存预算充足有限
检索精度要求一般高
是否需要复杂过滤否是
团队运维能力弱强

如果你的场景落在右边,别硬撑,早点换。迁移成本随着数据量增长是递增的。

6. 生产环境里那些容易翻车的地方

6.1 连接超时与命令超时

redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException这个报错,用 Java 的同学应该不陌生。Lettuce 是 Java 里常用的 Redis 客户端,默认命令超时是 60 秒。听起来很长,但在 AI 场景下,如果你把耗时操作直接放在 Redis 命令里,很容易触发。

我遇到过一次:用 Redis 的 Lua 脚本做批量向量计算,脚本执行时间超过了超时阈值,直接报错。后来把逻辑拆开,Lua 脚本只做简单的原子操作,复杂计算放到应用层,问题就解决了。

提示:Redis 是单线程处理命令的,任何一条慢命令都会阻塞其他请求。千万不要在 Redis 里跑耗时操作,Lua 脚本也要控制复杂度。

连接超时是另一个常见问题。默认的连接超时可能只有几秒,网络抖动一下就断了。我的做法是设置合理的连接池参数:

pool = redis.ConnectionPool( host='localhost', port=6379, max_connections=50, socket_timeout=5, socket_connect_timeout=3, retry_on_timeout=True, health_check_interval=30 )

socket_timeout设 5 秒,socket_connect_timeout设 3 秒,health_check_interval设 30 秒做健康检查。这些参数要根据实际网络环境调整,没有万能值。

6.2 大 key 与热 key 问题

AI 场景特别容易产生大 key。比如你把整个对话历史存成一个 String,聊了几百轮之后,这个 value 可能有好几 MB。读取和写入都会变慢,而且会阻塞其他请求。

我的经验是:单个 value 不要超过 10KB,集合类型的元素数量不要超过 5000。超过这个量级就要考虑拆分。

热 key 是另一个问题。比如某个热门 prompt 被大量用户同时请求,对应的缓存 key 就成了热 key,所有请求都打到同一个 Redis 节点上。如果是集群模式,这个节点的负载会明显高于其他节点。

解决办法有几个:一是给热 key 加随机后缀,分散到多个 key 上;二是在应用层做本地缓存,减少对 Redis 的访问;三是用 Redis 的读写分离,把读请求分散到从节点。

import random def get_with_local_cache(key, ttl=60): # 本地缓存优先 if key in local_cache and local_cache[key][1] > time.time(): return local_cache[key][0] # 回源到 Redis value = r.get(key) local_cache[key] = (value, time.time() + ttl) return value

6.3 持久化配置与数据安全

Redis 的持久化有 RDB 和 AOF 两种方式。RDB 是快照,恢复快但可能丢数据;AOF 是日志,数据安全但文件大、恢复慢。

对于 AI 场景,我的建议是:如果 Redis 只做缓存和会话管理,可以只开 RDB,甚至不开持久化。因为缓存丢了可以重建,会话丢了用户重新登录就行。但如果 Redis 里存了任务队列这种不能丢的数据,就必须开 AOF。

AOF 的fsync策略有三种:always(每条命令都刷盘,最安全但最慢)、everysec(每秒刷盘,折中方案)、no(交给操作系统,最快但可能丢数据)。生产环境我一般用everysec,性能和安全的平衡点比较好。

# redis.conf 中的关键配置 appendonly yes appendfsync everysec save 900 1 save 300 10 save 60 10000

save那几行是 RDB 的触发条件,意思是 900 秒内至少 1 个 key 变化、300 秒内至少 10 个、60 秒内至少 10000 个,就触发快照。这几个值是 Redis 的默认配置,大多数场景下够用。

6.4 集群模式下的坑

数据量大了之后,单机 Redis 扛不住,就要上集群。Redis Cluster 把数据分片到 16384 个槽位上,每个节点负责一部分槽位。

集群模式下有几个坑要注意:

第一,多 key 操作受限。涉及多个 key 的命令(比如MGET、MSET、事务),要求所有 key 在同一个槽位上。解决办法是用 hash tag,把相关的 key 用{}包起来,强制路由到同一个槽位。比如chat:{user123}:history和chat:{user123}:meta就会在同一个槽位。

第二,Lua 脚本的 key 也要同槽位。同样的道理,脚本里访问的所有 key 必须在同一个节点上。

第三,客户端要支持集群模式。普通的 Redis 客户端连不上集群,需要用集群版的客户端,比如 Python 的redis.cluster.RedisCluster。

from redis.cluster import RedisCluster rc = RedisCluster( startup_nodes=[ {"host": "127.0.0.1", "port": 7000}, {"host": "127.0.0.1", "port": 7001}, ], decode_responses=True )

集群的部署和运维比单机复杂得多,如果数据量没到那个级别,我建议先用主从加哨兵的模式,简单可靠。

7. 一些实操中的经验碎片

7.1 本地开发环境的快速搭建

开发阶段用 Docker 起 Redis 是最省事的。一条命令搞定:

docker run -d --name redis-dev -p 6379:6379 redis:7-alpine

用alpine镜像体积小,启动快。如果需要 RediSearch 等模块,换成redis/redis-stack镜像。

macOS 上如果不想用 Docker,brew install redis然后brew services start redis也行。Windows 用户建议用 WSL2 里的 Docker,原生 Windows 版的 Redis 版本比较老,很多新特性不支持。

可视化客户端我推荐 Another Redis Desktop Manager,跨平台,支持集群,界面清爽。RedisInsight 是官方出的,功能更全但稍微重一些。

7.2 监控指标要看哪些

Redis 的监控,我重点关注这几个指标:

  • used_memory和used_memory_rss:前者是 Redis 自己统计的内存使用量,后者是操作系统看到的实际内存占用。两者差距大说明有内存碎片。
  • connected_clients:连接数。突然飙升可能是连接泄漏。
  • instantaneous_ops_per_sec:每秒操作数。用来判断负载。
  • keyspace_hits和keyspace_misses:缓存命中率。AI 缓存场景下这个指标直接反映成本节省效果。
  • evicted_keys:被淘汰的 key 数量。如果这个值持续增长,说明内存不够用了。
redis-cli INFO stats | grep -E "keyspace_hits|keyspace_misses|evicted_keys" redis-cli INFO memory | grep -E "used_memory:|used_memory_rss:"

7.3 序列化性能的实测对比

前面提到过序列化方案的选择,这里补充一组我实测的数据。测试对象是一个包含 20 轮对话的会话历史,每轮平均 100 字。

方案序列化耗时反序列化耗时数据体积
JSON0.8ms1.2ms12KB
MessagePack0.4ms0.6ms8KB
Protobuf0.3ms0.4ms6KB
Pickle0.5ms0.7ms9KB

测试环境是本地开发机,数据仅供参考。可以看到 Protobuf 在各方面都最优,但代价是需要定义 schema,灵活性差。JSON 虽然慢一点、大一点,但胜在通用和可读。我的选择是:开发阶段用 JSON,性能敏感的生产环境用 MessagePack。Protobuf 除非有强需求,否则不值得为它增加维护成本。

7.4 关于 Redis 面试题的碎碎念

热词里出现了"redis 面试题",我顺带说几句。面试里问 Redis,高频考点无非是:缓存穿透、缓存击穿、缓存雪崩、分布式锁、持久化机制、集群方案。

但我想说的是,这些八股文背得再熟,不如真正在生产环境里踩过一次坑。比如缓存雪崩,你知道要加随机过期时间,但你知道随机范围设多少合适吗?我的经验是,在基础过期时间上叠加 10% 到 20% 的随机量。比如基础 1 小时,随机范围设 6 到 12 分钟。范围太小起不到分散作用,太大又会导致缓存过早失效。

分布式锁也是,Redisson 的看门狗机制、锁续期、可重入,这些概念背起来容易,但真正理解为什么需要续期、续期失败怎么办,得自己写过一遍才清楚。

8. 把 Redis 用好的几个原则

8.1 明确 Redis 的定位

Redis 是缓存,不是数据库。这句话听起来像废话,但我见过太多项目把 Redis 当主存储用,结果一次故障数据全丢。

我的原则是:Redis 里存的数据,必须是可以从其他来源重建的。会话丢了,用户重新登录;缓存丢了,回源查数据库;队列丢了,任务重新提交。只有满足这个条件,你才能安心地用 Redis。

如果有些数据确实不能丢,那就老老实实存数据库,Redis 只做加速层。写的时候先写数据库,再更新 Redis;读的时候先读 Redis,miss 了再读数据库并回填。

8.2 给每个 key 设过期时间

这是我反复强调的一点。没有过期时间的 key 就是内存泄漏。即使是"永久"数据,也建议设一个很长的过期时间,比如 30 天,作为兜底。

我见过一个项目,用 Redis 存用户配置,从来不设过期。跑了两年之后,内存里堆了几千万个 key,其中大部分用户早就注销了。清理的时候费了很大劲。

8.3 监控和告警要跟上

Redis 出问题往往是突发的。内存满了、连接数爆了、主从断了,这些如果不监控,等用户反馈的时候已经晚了。

我的做法是至少监控三个维度:资源(内存、CPU、连接数)、性能(延迟、QPS、命中率)、可用性(主从状态、集群健康度)。告警阈值根据业务特点设定,比如内存使用率超过 80% 就告警,命中率低于 70% 就关注。

8.4 版本选择与升级策略

Redis 的版本迭代挺快的,但我不建议盲目追新。生产环境选版本,稳定优先。目前 7.x 是比较成熟的选择,6.x 也还在广泛使用。新版本的新特性,先在测试环境验证,确认没问题再上生产。

升级的时候要注意兼容性。比如 6.0 引入的多线程 IO、7.0 引入的 Function,这些新特性在旧客户端上可能不支持。升级前一定要看 changelog,做好回滚预案。

Redis 跟 AI 的结合,说到底不是什么颠覆性的技术革命,而是把已有的工具用在了新的场景里。它的价值不在于 Redis 本身有多强,而在于它恰好补上了 AI 应用在状态管理、缓存、调度这几块的需求。如果你正在做 AI 相关的项目,不妨看看哪些地方可以用 Redis 优化一下,很多时候效果比想象中明显。我自己最大的体会是:别把 Redis 当成一个简单的 key-value 存储,它的数据结构和原子操作,能解决很多看起来复杂的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询