· Jessy Huang · Engineering  · 29 min read

缓存不是 Redis:从 Go 后端到 AI Agent,业务系统里的缓存体系怎么设计

用业务场景讲清 Go 本地缓存、Redis、Prompt Caching、RAG 缓存、MQ、MySQL 一致性和游戏状态缓存。

用业务场景讲清 Go 本地缓存、Redis、Prompt Caching、RAG 缓存、MQ、MySQL 一致性和游戏状态缓存。
业务缓存系统分层图

很多后端系统的性能优化,都是从一句非常熟悉的话开始的:

这个接口有点慢,加个 Redis 吧。

这句话不能说错。

但它也很容易把人带沟里。因为业务要解决的问题从来不是“怎么用 Redis”,而是:

怎么让系统更快、更稳、更便宜、出问题还能恢复。

Redis 只是工具箱里最常用的那把扳手。
有时候你需要的是 Go 进程内缓存,有时候是 singleflight,有时候是 Eino / LangChain 的 memory 和 checkpoint,有时候是 OpenAI / Anthropic 的 Prompt Caching,有时候是向量库,有时候是 MQ,有时候老老实实让 MySQL 当裁判。

如果所有问题都用 Redis 解决,最后 Redis 会变成一个又当收银员、又当仓库、又当会计、又当消防员的同事。这样的同事一般不是全能,是快离职了。

所以这篇文章换个角度讲缓存:

我们不是为了使用 Redis 才设计缓存,而是为了业务目标选择合适的缓存层。


一、先画地图:缓存系统不是单层结构

真实业务里,一个请求往往会穿过很多层缓存。

比如一个 AI Agent 请求:

  1. 浏览器里保留了当前会话 UI 状态
  2. Go 服务用本地缓存挡住热点配置
  3. Agent 框架保存 checkpoint,方便中断恢复
  4. Redis 记录任务状态、限流、工具结果
  5. RAG 系统缓存文件切片和 embedding
  6. 模型厂商命中 Prompt Cache
  7. MySQL 保存最终任务结果和账单
  8. MQ 异步发通知、写审计、刷新下游缓存

如果你只问“Redis 怎么用”,就像只盯着厨房里的锅,却忘了还有冰箱、案板、菜单、收银系统和消防通道。

更合理的思考方式是:

层级负责什么典型工具命中指标主要风险
客户端缓存UI 状态
静态资源
棋盘本地状态
Browser / App Memory
HTTP Cache
LocalStorage / IndexedDB
本地命中率
页面加载时间
交互延迟
旧状态
越权数据
缓存未及时失效
Go 本地缓存进程内热点
配置缓存
请求合并 / 回源保护
map + mutex
sync.Map
LRU
singleflight
local hit
本地命中率
回源次数
多实例不一致
进程重启丢失
过期策略不清晰
框架缓存Agent Memory
Checkpoint
Retriever 状态
多轮会话状态
Eino
LangChain
LangGraph
resume rate
memory hit
checkpoint 恢复率
存储后端不可靠
状态恢复失败
上下文污染
Redis 服务缓存热数据
分布式锁
限流
排行榜
任务状态
Redis String
Hash
ZSet
Stream
Lua Script
Redis hit rate
P99 延迟
慢查询
内存使用率
热 key
缓存雪崩
缓存击穿
Redis 故障
RAG 缓存文件切片
Embedding
检索结果
上下文包复用
Redis
向量库
对象存储
Embedding Cache
chunk hit
embedding hit
retrieval hit
索引版本不一致
权限范围缓存错误
旧向量未失效
Prompt Cache模型输入前缀
工具定义
系统提示词
长上下文稳定部分
OpenAI
Anthropic
模型厂商缓存能力
cached tokens
cache read tokens
prefix hit rate
只缓存稳定前缀
不缓存业务结果
动态内容影响命中
MQ / 事件缓存异步任务
削峰填谷
事件分发
缓存失效通知
Kafka
RabbitMQ
RocketMQ
Redis Streams
backlog
消费延迟
失败率
死信数量
重复消费
消息乱序
消息积压
消费者失败
数据库最终事实
交易数据
审计记录
强一致数据
MySQL
PostgreSQL
事务成功率
查询延迟
慢 SQL
锁等待
把缓存误当真相
缓存覆盖真实数据
主从延迟

这张表的重点不是“工具很多”,而是:

每一层缓存都有自己的命中定义、失效方式和责任边界。

Redis 很重要,但它不是整套缓存系统的名字。


二、Go 本身能解决什么:不要每个热点都先冲向 Redis

很多 Go 服务的缓存问题,第一层其实不需要 Redis。

比如:

  • 配置每 30 秒刷新一次
  • 某个 feature flag 每个请求都要读
  • 同一个热点 key 在一瞬间被 1000 个 goroutine 同时回源
  • 单实例内重复计算一个昂贵结果

这些可以先在 Go 进程内解决。

1. 本地缓存适合什么

本地缓存适合:

  • 数据很小
  • 可以短暂不一致
  • 每个实例都能自己刷新
  • 丢了可以重建
  • 访问频率极高

比如系统配置、静态字典、短期权限模板。

它的好处是没有网络往返。Redis 再快,也要过网络;Go 本地 map 命中就是进程内完成。

但本地缓存也有边界:

  • 多实例之间不一致
  • 无法全局限流
  • 无法做跨实例锁
  • 进程重启就丢
  • 容量和淘汰策略要自己控制

所以本地缓存常常是 Redis 前面的一层保护,而不是 Redis 的替代品。

请求
  -> Go 本地缓存
  -> Redis
  -> MySQL / 外部服务

2. singleflight 解决什么

缓存击穿时,最怕的不是一次 miss,而是同一个 key 同时 miss。

比如首页推荐配置过期,1000 个请求同时发现 Redis 没有,然后一起冲 MySQL。数据库看到这阵仗,大概会想:“我只是个数据库,不是演唱会安保。”

Go 里可以用 singleflight 合并同一个 key 的回源:

var group singleflight.Group

func GetProduct(ctx context.Context, id int64) (*Product, error) {
	key := fmt.Sprintf("product:%d", id)

	if p, ok := localCache.Get(key); ok {
		return p, nil
	}

	v, err, _ := group.Do(key, func() (any, error) {
		if p, ok := redisGetProduct(ctx, key); ok {
			localCache.Set(key, p, 3*time.Second)
			return p, nil
		}

		p, err := mysqlGetProduct(ctx, id)
		if err != nil {
			return nil, err
		}
		redisSetProduct(ctx, key, p, jitter(10*time.Minute))
		localCache.Set(key, p, 3*time.Second)
		return p, nil
	})
	if err != nil {
		return nil, err
	}
	return v.(*Product), nil
}

这里的设计点是:

  • 本地缓存挡住极短时间的热点重复读
  • Redis 承接跨实例共享缓存
  • singleflight 合并单实例内同 key 回源
  • MySQL 只在必要时被访问
  • TTL 加抖动,避免集体过期

这就是“为了业务使用工具”,不是“为了 Redis 使用 Redis”。


三、AI Agent:Prompt Cache、Agent Cache、Redis Cache 不是一回事

AI Agent 系统特别容易把缓存概念搅在一起。

比如你做一个“代码仓库分析 Agent”:

  • system prompt 很长
  • tools 很多
  • RAG 会塞一堆文件片段
  • Agent 会调用 GitHub、CI、日志系统
  • 用户可能重复问类似问题
  • 任务可能跑一半中断

这时候至少有三种缓存:

缓存谁提供缓存内容命中方式解决的问题
Prompt CacheOpenAI / Anthropic模型输入前缀、工具定义、长上下文prefix / breakpoint降低模型 prefill 成本和延迟
Agent CacheEino / LangChain / 应用checkpoint、memory、工具结果runID、tool args hash、semantic key恢复执行、减少重复工具调用
Redis Cache应用基础设施状态、限流、幂等、任务进度Redis key跨实例共享热状态

1. Provider Prompt Cache 怎么用

OpenAI Prompt Caching 的关键点:

  • 对近期模型自动生效
  • 1024 tokens 以上开始有缓存收益
  • 命中要求完全相同的 prompt 前缀
  • 静态内容放前面,动态内容放后面
  • usage 里看 cached_tokens
  • prompt_cache_key 可以帮助相同前缀请求路由到更容易命中的位置

Anthropic Prompt Caching 的关键点:

  • 可以用 top-level cache_control 自动缓存
  • 也可以给内容块设置 explicit cache breakpoints
  • prefix 包括 tools、system、messages
  • 最多 4 个 cache breakpoints
  • usage 里区分 cache_read_input_tokenscache_creation_input_tokensinput_tokens

所以 Agent prompt 应该这样组织:

稳定工具定义
稳定系统指令
稳定输出格式
相对稳定的 RAG 背景材料
动态用户问题
动态工具返回

不要把时间戳、随机 request id、用户临时参数塞在最前面。否则每次前缀都变,Prompt Cache 就像门口的熟客通道,每次你都换脸进门。

2. Agent 自己怎么缓存

Provider cache 只解决模型前缀,不解决业务过程。

Agent 自己要缓存:

  • 工具调用结果
  • checkpoint
  • 中间状态
  • 会话摘要
  • 用户额度
  • 外部 API 返回

Eino 的 Runner 支持 CheckPointStore,示例里用内存 store,但真实服务可以用 Redis 这类分布式 store。LangChain / LangGraph 则有 memory、semantic search、retriever 这类能力,可以把相关记忆或文档片段检索出来拼进 prompt。

Redis 里的 key 可以这样设计:

agent:checkpoint:{tenant}:{run_id}
agent:task:{task_id}:status
agent:tool:{tenant}:{tool}:{args_hash}:{permission_version}
agent:quota:{tenant}:{user_id}:{yyyymmdd}
agent:summary:{session_id}:{summary_version}

解决方案要点:

  1. args_hash 必须基于规范化参数生成,比如 JSON canonicalize 后 hash。
  2. 缓存 key 要带租户、用户或权限版本,避免跨权限命中。
  3. 工具结果 TTL 要按数据变化速度定。CI 日志可以稍久,搜索结果要短,权限结果更谨慎。
  4. checkpoint 要有清理策略。不要让跑失败的 Agent 在 Redis 里变成考古遗址。
  5. token 账本要分开记录 provider token、embedding token、tool 调用次数、Redis 命中。
AI Agent 和 RAG 缓存链路图

四、RAG:缓存的不只是答案,而是处理链路

RAG 最容易浪费钱的地方不是最终回答,而是重复处理文档。

一个 PDF 从上传到回答,中间有很多节点:

上传文件
  -> 计算 file_hash
  -> 解析文本
  -> 切 chunk
  -> 计算 chunk_hash
  -> 调 embedding 模型
  -> 写向量库
  -> query 检索
  -> rerank
  -> 拼 context
  -> 调 LLM

如果用户重复上传同一份文档,或者同一个知识库反复构建索引,你不应该每次都重新 embedding。

1. 文件级缓存

key:

rag:file:{tenant}:{file_sha256}

value:

{
  "parse_status": "done",
  "parser_version": "pdf-v3",
  "chunker_version": "semantic-v2",
  "chunk_hashes": ["...", "..."],
  "created_at": 1782600000
}

作用:

  • 判断文件是否处理过
  • 避免重复解析
  • 关联 chunk 列表

2. Chunk 和 Embedding 缓存

embedding 缓存要带模型版本:

rag:embedding:{embedding_model}:{chunk_hash}

因为同一段文本,用不同 embedding 模型生成的向量不能混用。模型变了,旧向量不是“稍微旧一点”,而是坐标系都可能换了。

命中率可以这样算:

embedding_hit_rate = cached_chunk_count / total_chunk_count
embedding_cost_saved = cached_chunk_tokens * embedding_price_per_token

3. Retrieval 缓存

查询结果也可以短期缓存:

rag:retrieval:{tenant}:{index_version}:{query_hash}:{top_k}

但这里要小心:

  • index 更新后必须换 index_version
  • query rewrite 版本变化也要进 key
  • 权限过滤必须进 key 或在缓存后再次过滤
  • TTL 不宜太长

4. Context Pack 缓存

RAG 最后还会把检索片段拼成 prompt context。这个步骤也可能昂贵,因为要去重、排序、裁剪 token、加引用。

可以缓存:

rag:context:{retrieval_result_hash}:{template_version}:{token_budget}

这样用户连续追问同一个文档主题时,不必每轮都重新组织上下文。

但最终回答通常不建议直接缓存太久。回答和用户问题、权限、时间、模型版本、输出风格都有关,盲目缓存很容易省了钱,亏了信任。


五、购买链路:缓存负责快,数据库负责真,MQ 负责排队

购买和秒杀是缓存系统的经典战场。

业务问题不是“Redis 怎么扣库存”,而是:

  1. 高峰期怎么不打爆数据库?
  2. 用户怎么不能重复买?
  3. 库存怎么不能超卖?
  4. 订单事实怎么可靠落库?
  5. 发券、通知、积分这些后续动作怎么异步处理?
  6. Redis 挂了怎么办?
MySQL Redis MQ 一致性流程图

1. 读链路:商品详情缓存

商品详情适合 cache-aside:

读商品:
  1. Go 本地缓存查前 N 秒热点
  2. Redis 查商品详情
  3. Redis miss 后查 MySQL
  4. 回填 Redis,TTL 加随机抖动

key:

product:detail:{product_id}:v{schema_version}

TTL:

  • 普通商品:5 到 30 分钟
  • 活动商品:几十秒到几分钟
  • 强运营实时变化字段:拆出来单独缓存或直接查源

为什么要 schema version?

因为缓存里的 JSON 结构迟早会变。没有版本,老缓存会像衣柜里的旧数据,平时看不见,线上突然穿出来吓人。

2. 写链路:订单事实先落库

订单、支付、库存流水这类事实,应该让 MySQL 负责。

Redis 可以做预扣和快速判断,但最终不能只靠 Redis 说“钱扣了”。

常见流程:

请求进来
  -> Redis 限流
  -> Redis SetNX 做幂等
  -> Redis 预扣库存
  -> MQ 投递创建订单事件
  -> 消费者写 MySQL 订单事务
  -> 成功后删除/刷新 Redis 商品缓存
  -> MQ 通知发券、积分、短信

关键点:

  • 幂等 key 防按钮连点和重试
  • 预扣失败直接返回库存不足
  • MQ 消费者必须幂等,因为消息可能重复
  • MySQL 订单表要有唯一约束,比如 (user_id, activity_id)
  • Redis 预扣和 MySQL 最终库存要有对账任务

3. 缓存不够时为什么要 MQ

缓存解决“快读快判定”。

MQ 解决“后续工作可靠排队”。

比如下单成功后:

  • 发优惠券
  • 发短信
  • 通知仓储
  • 写用户积分
  • 写风控事件
  • 预热推荐缓存

这些不应该都堵在用户请求里同步做。否则用户买一瓶水,要等仓库、短信、积分、风控全部开完会才拿到响应。

MQ 把这些动作拆出去。Redis 可以配合 MQ 做状态缓存、去重、短期事件索引,但不要把 Redis List 当专业 MQ 一路硬撑到业务变大。


六、Redis 和 MySQL 一致性:不要追求童话,追求可控

缓存一致性没有童话。

你能做的是把“不一致窗口”变短、变可观测、变可恢复。

1. 基础方案:写库后删缓存

更新商品:
  1. 写 MySQL
  2. 删除 Redis key

为什么不是先删缓存再写库?

因为先删缓存后,另一个请求可能马上 miss,然后读到旧 MySQL 值并回填 Redis。你刚删完,它又把旧值请回来了。

为什么不是直接更新缓存?

因为缓存经常是聚合数据。你更新了商品主表字段,但缓存里可能还包含活动价、库存、标签、推荐分。直接更新很容易漏。

2. 旧值回填怎么处理

高并发下仍可能出现:

A 读缓存 miss
A 查 MySQL 旧值
B 写 MySQL 新值
B 删除缓存
A 把旧值写回 Redis

解决组合:

  • 缓存 value 带 versionupdated_at
  • 回填前检查版本,新版本不被旧版本覆盖
  • 写后延迟双删,降低旧值残留概率
  • 用 binlog / CDC 订阅数据库变更,统一失效缓存
  • 热点 key 回源加 singleflight / mutex
  • TTL 不要过长

3. 什么时候不能靠缓存一致性

这些场景不要让 Redis 当最终裁判:

  • 支付扣款
  • 订单最终状态
  • 库存财务结算
  • 审计日志
  • 权限授权最终结果

Redis 可以做快速拦截、预判、临时状态。最终事实应该在数据库事务或专门系统里。

Redis 的 WAIT 可以提高写入复制到副本的概率,在故障转移场景更稳一点;但它不会把 Redis 变成强一致数据库。

这句话很冷静,也很重要。


七、三大缓存问题:从面试题还原成业务事故

1. 缓存穿透:不存在的数据把数据库打穿

业务现场:

攻击者或错误客户端不断请求不存在的商品 ID:

/products/999999999999

Redis 没有,MySQL 也没有。每次都查库。

解决方案:

  1. 参数校验:明显非法 ID 直接拒绝
  2. 缓存空值:product:null:{id},TTL 短一点
  3. Bloom filter:快速判断 ID 是否可能存在
  4. 风控限流:异常模式直接限

Redis key:

product:null:{product_id} -> "1", TTL 60s

注意:空值 TTL 不能太长。否则商品刚创建,用户还命中旧的“不存在”,运营同事会以为你在做行为艺术。

2. 缓存击穿:热点 key 过期,数据库被围观

业务现场:

一个爆款商品缓存过期,几万请求同时回源。

解决方案:

  1. 热点 key 提前预热
  2. 热点 key 逻辑过期:缓存不物理删除,后台异步刷新
  3. Go singleflight 合并单实例回源
  4. Redis 分布式锁合并跨实例回源
  5. 降级返回旧值

核心思想:

宁愿短时间返回稍旧数据,也不要让数据库当场表演原地升天。

3. 缓存雪崩:一批 key 一起过期

业务现场:

活动商品统一设置 20:00 过期。到了 20:00,缓存集体下班,数据库集体加班。

解决方案:

  • TTL 加随机抖动
  • 分批预热
  • 多级缓存
  • 限流
  • 降级
  • 核心热点永不过期 + 后台刷新
func jitter(base time.Duration) time.Duration {
	return base + time.Duration(rand.Intn(60))*time.Second
}

八、排行榜:Redis ZSet 是现场大屏,不是赛后账本

排行榜适合 Redis,尤其是 ZSet。

比如游戏积分榜、直播热度榜、活动销售榜、热门 prompt 榜。

func addScore(ctx context.Context, rdb *redis.Client, userID int64, score float64) error {
	return rdb.ZAdd(ctx, "rank:daily", redis.Z{
		Score:  score,
		Member: userID,
	}).Err()
}

但排行榜业务不是只有 ZADD

你要回答:

  • 是实时榜还是结算榜?
  • 分数能不能回滚?
  • 跨服怎么合榜?
  • 前 100 名是不是热点?
  • 历史榜保存多久?
  • 刷榜作弊怎么处理?

设计建议:

  1. Redis ZSet 维护实时榜
  2. MySQL / OLAP 存结算结果和历史榜
  3. 定时快照 Redis 榜单
  4. 大榜按赛区、活动、日期拆 key
  5. 前 N 名可做本地缓存,降低 Redis 热点
  6. 分数来源必须可追溯,不能只有 Redis 分数

key:

rank:{game}:{season}:{region}
rank_snapshot:{game}:{season}:{region}:{date}

Redis 排行榜像现场大屏。

大屏要快,但奖金要看账本。你不能对财务说:“截图在这里,发钱吧。”


九、分布式 Redis:不是为了高级,是为了坏了还能活

单机 Redis 很舒服,直到它成为单点。

为什么需要分布式 Redis?

  • 单机内存不够
  • 单机 QPS 不够
  • 单点故障不可接受
  • 需要读扩展
  • 需要自动故障转移
  • 需要横向分片

1. 主从复制

主从复制解决冗余和读扩展。

但它不等于自动 failover。master 挂了,如果没有 Sentinel 或 Cluster,客户端不会自动知道谁是新 master。

2. Sentinel

Sentinel 适合“不需要分片,但需要高可用”的场景。

它负责:

  • 监控 master 和 replica
  • 判断 master 是否不可用
  • 触发 failover
  • 推选新 master
  • 作为客户端配置发现来源

业务上要考虑:

  • failover 期间短暂不可用怎么办?
  • 客户端连接池如何刷新 master 地址?
  • 写请求失败是否重试?
  • 重试是否幂等?
  • Redis 丢失少量最近写入是否可接受?

3. Cluster

Cluster 解决的是横向扩展和分片。

数据按 slot 分散到多个 master,每个 master 可以有 replica。

它适合:

  • 数据量大
  • QPS 高
  • 单机扛不住
  • key 可以良好分布

但 Cluster 也带来复杂度:

  • multi-key 操作要考虑 hash slot
  • 热 key 仍然可能打爆单节点
  • 客户端要支持 MOVED/ASK 重定向
  • 故障恢复要关注复制延迟和持久化

4. Redis 崩了怎么恢复

恢复不是一句“重启一下”。

你需要预案:

故障系统动作业务动作
replica 崩重启后重同步读流量切走
master 崩Sentinel/Cluster failover写请求短暂重试
Redis 全挂降级到 DB / 本地缓存限流,保护数据库
数据丢失从 AOF/RDB 或对账恢复重建缓存,核对关键状态
热 key 打爆拆 key / 本地缓存 / 限流降级非核心字段

Redis 是缓存层时,核心能力是“可重建”。
如果某个 Redis key 丢了就业务不可恢复,那它可能根本不该只存在 Redis 里。


十、什么时候缓存不够,要加 MQ

缓存回答的是:

现在这个状态是什么?

MQ 回答的是:

这件事后面要让谁处理?

比如订单创建成功后:

  • 发券
  • 发短信
  • 通知仓储
  • 写积分
  • 刷新推荐缓存
  • 写审计
  • 推送风控

这些动作如果都同步做,用户会觉得买东西像在等银行放款。

MQ 的价值:

  • 削峰
  • 解耦
  • 重试
  • 下游慢不拖主链路
  • 事件广播
  • 缓存失效通知

Redis Streams 可以当中轻量事件流,适合内部 worker、消费组、简单 ack。

但如果你需要:

  • 大规模堆积
  • 多业务主题治理
  • 死信队列
  • 严格消息保留
  • 跨团队事件平台

就应该认真评估 Kafka、RabbitMQ、RocketMQ 等专业 MQ。

Redis 很好,但不要让它兼职所有中间件岗位。


十一、游戏缓存:WebSocket 棋盘和 UDP FPS 不是一种系统

棋盘游戏和 FPS 游戏缓存差异图

1. 棋盘 / 回合制游戏

比如五子棋、象棋、扑克房间。

特点:

  • 状态变化频率低
  • 顺序重要
  • 每一步可以服务端校验
  • WebSocket 很适合

缓存设计:

room:{room_id}:state        Hash/String,当前房间状态
room:{room_id}:players      Set,玩家列表
matchmaking:{mode}          ZSet/List,匹配队列
presence:{user_id}          String,在线状态 TTL
rank:{season}:{region}      ZSet,排行榜

落库:

  • 棋谱
  • 结算结果
  • 积分变动
  • 作弊审计

客户端可以缓存棋盘 UI,提升体验;但合法性要服务端判断。

客户端说“我赢了”,服务端不能直接鼓掌,还得看棋盘。

2. FPS / 实时动作游戏

FPS 是另一种生物。

特点:

  • 状态高频变化
  • 延迟极敏感
  • UDP 或基于 UDP 的协议常见
  • 服务端权威状态
  • 客户端预测、插值、校正

Redis 不适合做:

  • 每帧位置同步
  • 子弹碰撞判定
  • 实时物理状态
  • 高频输入处理

Redis 适合做:

  • 匹配
  • 房间发现
  • 在线状态
  • 排行榜
  • 战绩摘要
  • 结算任务
  • 跨服通知

FPS 的核心循环应该在游戏服务器内存里。Redis 是外围协调,不是帧同步心脏。


十二、最后给一个业务缓存设计清单

下次你想加缓存,不要先问“Redis key 怎么写”。

先问:

  1. 业务问题是什么?慢、贵、抖、不可恢复,还是数据库压力大?
  2. 这个数据是不是最终事实?
  3. 它丢了能不能重建?
  4. 它允许多久不一致?
  5. 它的命中 key 是什么?
  6. 这个 key 是否包含租户、权限、版本?
  7. TTL 怎么定?是否需要 jitter?
  8. miss 后谁回源?是否需要 singleflight / 锁?
  9. Redis 挂了怎么降级?
  10. 是否需要 MQ 承接后续动作?
  11. 是否需要记录 token、成本、命中率?
  12. 这个状态应该在客户端、Go 本地、Redis、向量库、Prompt Cache、MQ 还是数据库?

一个更成熟的缓存系统,不是 Redis 用得越多越好。

而是每一层都知道自己在解决什么:

  • Go 本地缓存解决极低延迟和单实例热点
  • Redis 解决跨实例热状态和协调
  • Prompt Cache 解决模型前缀成本
  • RAG cache 解决文档处理和检索成本
  • MQ 解决异步和削峰
  • MySQL 解决最终事实
  • 客户端缓存解决交互体验

Redis 是非常重要的一层。

但业务系统真正需要的,不是“到处加 Redis”,而是:

让每个状态待在它最应该待的位置。


References

Back to Blog