
1. 为什么 AI Agent 一上并发就崩从一次线上事故说起去年冬天我帮一个团队救火他们的 AI Agent 服务白天还跑得好好的晚上做了一波推广QPS 从个位数飙到两百多整个服务在十分钟内雪崩。日志里刷得最多的一句话是redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException紧接着就是上游大模型接口被限流、任务队列堆积、用户端转圈圈。事后复盘问题根本不在模型本身而在于他们把 Redis 当成了一个随手用用的附属品既没有做缓存分层也没有做并发保护甚至连键的过期策略都是拍脑袋定的。这件事让我意识到AI Agent 和传统 CRUD 服务在缓存这件事上的诉求完全不同。传统业务缓存的多是数据库查询结果读多写少、结构稳定而 AI Agent 缓存的东西五花八门——对话上下文、工具调用结果、向量检索的中间态、模型响应的分片、限流令牌、分布式锁、任务状态机。这些东西有的要求强一致有的可以容忍短暂脏读有的体积巨大有的生命周期只有几秒。你要是用一套统一的缓存策略去套迟早出事。这篇内容我想把AI Agent Redis 缓存这件事从头到尾讲透。不管你是刚接触 AI Agent 开发、准备用 Python 或 Java 搭一个能扛住真实流量的智能体服务还是已经在线上跑着但总觉得缓存这块心里没底我都建议你把这几个章节看完。我会讲清楚缓存该放在哪一层、键怎么设计、并发怎么扛、失效怎么治、线上怎么排查全部是我自己踩过坑之后沉淀下来的做法能直接抄作业。2. AI Agent 的缓存版图到底哪些东西值得进 Redis2.1 先搞清楚 Agent 的请求链路长什么样一个典型的 AI Agent 请求从用户输入到最终返回中间要经过好几个阶段。用户发一句话进来服务先做意图识别然后决定要不要调用工具调用工具可能要查数据库、调外部 API、做向量检索拿到结果之后再拼装成 Prompt 送给大模型模型流式返回内容服务边收边推给前端最后把整轮对话落库。这条链路上每一个环节都可能成为性能瓶颈也每一个环节都可能适合做缓存。我见过很多团队一上来就把缓存等同于缓存大模型响应这其实是最粗放的做法。大模型响应缓存命中率往往不高因为用户问的问题稍微换个说法语义相同但字符串不同精确匹配就失效了。真正高价值的缓存点反而在那些重复度高、计算贵、变化慢的环节。比如工具调用的结果、向量检索的候选集、用户会话的元信息、限流计数、任务幂等键。所以第一步不是急着写代码而是先把你的 Agent 链路画出来标出每个节点的三个属性调用频率、单次耗时、结果可复用程度。频率高、耗时长、复用度高的节点就是缓存的第一优先级。这个判断过程比任何技术选型都重要选错了缓存点后面做得再精致也是白费。2.2 五类典型缓存对象与它们的脾气我把 AI Agent 里常见的缓存对象归成五类每一类的特性差异很大处理方式也完全不同。第一类是会话上下文。它记录用户和 Agent 的多轮交互历史特点是读写都频繁、体积随轮次增长、有明确的会话生命周期。这类数据我一般用 Redis 的 Hash 结构存一个会话一个 key字段是轮次编号同时设置一个滑动过期时间比如每次写入都刷新 TTL 到 30 分钟。这样既不占内存太久又能保证活跃会话不丢。第二类是工具调用结果。比如查天气、查汇率、查订单状态这类结果往往在短时间内是稳定的缓存价值极高。键的设计要把工具名和参数指纹拼进去值直接存序列化后的结果TTL 根据业务时效性定天气可以 10 分钟汇率可以 1 分钟订单状态可能只能 5 秒甚至不缓存。第三类是向量检索候选集。RAG 场景里同一个 query 反复检索同一批文档是很常见的把 top-k 的文档 ID 和分数缓存下来能省掉大量向量库查询。这类缓存体积中等TTL 可以稍长但要注意文档更新时的失效问题。第四类是限流与并发控制令牌。这是 AI Agent 扛并发的关键。大模型接口通常有 QPS 限制你必须用 Redis 做全局限流否则多个实例一起打过去直接被上游封。用 Redis 的原子计数或者令牌桶实现键按模型维度划分。第五类是任务状态与幂等键。Agent 执行长任务时用户可能重复提交或者服务重启后要恢复状态这时候需要一个地方记录这个任务已经处理过了。用 Redis 的 SETNX 做幂等用 Hash 存任务进度都是很成熟的做法。缓存对象推荐结构典型 TTL一致性要求主要价值会话上下文Hash30 分钟滑动最终一致减少落库读写工具调用结果String5 秒到 10 分钟弱一致省外部调用向量检索候选String/List10 分钟到 1 小时最终一致省向量查询限流令牌String 计数秒级强一致保护上游任务状态Hash SETNX小时级强一致幂等与恢复这张表我建议你直接贴到团队文档里每次新增缓存点的时候对照一下先想清楚它属于哪一类再决定结构和 TTL。很多线上事故就是因为把弱一致的数据当强一致用或者把强一致的数据随便设了个过期时间。2.3 哪些东西千万别往 Redis 里塞有该缓存的就有不该缓存的。我踩过最大的坑是把大模型的完整响应体直接塞进 Redis一条响应动辄几十 KB高峰期每秒几百条内存涨得飞快还触发了 Redis 的淘汰策略把真正重要的会话数据给挤掉了。后来改成只缓存响应摘要和关键字段完整内容落对象存储问题才解决。另外超高频变化的中间态也不适合进 Redis。比如流式输出过程中每个 token 的状态这种东西变化太快写 Redis 的开销比收益还大放在进程内存里就够了。还有强事务性的数据比如涉及金额扣减的账务记录Redis 的持久化机制决定了它不适合做唯一真相来源该落库还得落库Redis 只能做加速层。判断标准很简单如果这份数据丢了业务能不能从别的地方恢复能恢复的放心缓存不能恢复的老老实实落库Redis 只做副本。3. 键设计与数据结构选型细节决定线上稳不稳3.1 键命名规范别让半年后的自己骂人我见过最离谱的键名是user_123_data既不知道是哪个业务也不知道存的是什么更不知道什么时候过期。半年后要清理没人敢动。键名这件事看着小实际上是缓存治理的地基。我的做法是强制三段式业务前缀 对象类型 唯一标识。比如agent:session:{userId}:{sessionId}、agent:tool:weather:{cityHash}、agent:ratelimit:{modelName}:{minute}。前缀统一用agent:开头方便用SCAN批量管理也方便在监控里按前缀统计内存占用。中间段标明对象类型一眼能看出结构。最后一段是唯一标识如果是复杂参数先做哈希再拼进去避免键过长。注意Redis 的键不是越短越好可读性和可管理性比省那几个字节重要得多。但也不能无限长超过 128 字节的键在集群模式下会有额外的哈希开销复杂参数一定要先哈希。3.2 String、Hash、List、ZSet 到底怎么选选错数据结构是新手最容易犯的错。我总结了一套判断逻辑基本能覆盖 Agent 场景的九成需求。String适合存单个值比如工具调用结果、限流计数、幂等标记。它的优势是操作简单、内存开销小。但要注意如果值很大比如超过 10KB就要考虑压缩或者拆分否则单次网络传输就会拖慢响应。Hash适合存对象比如会话上下文、任务状态。它的好处是可以只更新某个字段不用整体读写。会话上下文用 Hash 存每轮对话只HSET一个新字段比每次读出来改完再写回去高效得多。但 Hash 的字段数量不宜过多超过几千个字段的 Hash 在扩容时会阻塞会话轮次特别多的时候要考虑分片。List适合做队列比如待处理的任务、消息流。Agent 的异步任务队列可以用 List 的LPUSH和BRPOP实现简单可靠。但 List 不支持按位置随机访问也不支持去重如果需要这些能力就得换结构。ZSet适合带权重的排序场景比如按时间排序的会话列表、按分数排序的检索候选。它的范围查询能力很强ZRANGEBYSCORE可以轻松取出某个时间段的数据。场景推荐结构关键命令避坑点工具结果缓存StringSET/GET大值要压缩会话上下文HashHSET/HGETALL字段数别太多异步任务队列ListLPUSH/BRPOP注意阻塞超时排序候选集ZSetZADD/ZRANGE分数设计要合理限流计数StringINCR/EXPIRE必须原子操作3.3 序列化方式JSON 不是唯一答案值怎么序列化这件事直接影响性能和兼容性。JSON 可读性好、跨语言方便但体积大、解析慢。Protobuf 和 MessagePack 体积小、速度快但可读性差调试的时候得专门工具。我的经验是调试阶段用 JSON压测确认瓶颈后再换二进制格式。还有一个容易被忽略的点是序列化版本管理。你今天用 JSON 存了一个对象明天给对象加了个字段老数据反序列化的时候可能就报错了。解决办法是在值里带一个版本号反序列化时按版本走不同的解析逻辑。这个习惯在长期维护的项目里能救命。Java 生态里还有个经典问题Spring 默认用 JDK 序列化存进去的东西带一堆类元信息体积大还容易因为类变更导致反序列化失败。换成 Jackson 或者 Protobuf 序列化体积能小一半以上。Python 这边相对简单但也要注意pickle的安全问题永远不要反序列化不可信来源的 pickle 数据。4. 扛并发的核心限流、锁与缓存击穿防护4.1 用 Redis 做全局限流保护大模型接口AI Agent 扛并发的第一道防线就是限流。大模型接口通常按 QPS 或者 TPM 计费超了要么被拒要么被罚钱。多个服务实例各自限流是没用的必须有一个全局的计数器这就是 Redis 的用武之地。最简单的实现是固定窗口计数键是agent:ratelimit:{model}:{当前分钟}每次请求前INCR如果返回值超过阈值就拒绝同时给键设置 60 秒过期。这个方案实现简单但有个边界问题——窗口切换的瞬间可能放过两倍流量。比如阈值是 100第 59 秒来了 100 个请求第 60 秒又来了 100 个实际两秒内过了 200 个。更平滑的方案是滑动窗口或者令牌桶。滑动窗口用 ZSet 存请求时间戳每次请求前清理过期的时间戳再计数精度高但内存开销大。令牌桶用两个键分别存令牌数和上次刷新时间通过 Lua 脚本保证原子性是工业界最常用的方案。-- 令牌桶限流 Lua 脚本 -- KEYS[1]: 令牌数键 KEYS[2]: 上次刷新时间键 -- ARGV[1]: 桶容量 ARGV[2]: 每秒补充速率 ARGV[3]: 当前时间戳(秒) local capacity tonumber(ARGV[1]) local rate tonumber(ARGV[2]) local now tonumber(ARGV[3]) local tokens tonumber(redis.call(GET, KEYS[1]) or capacity) local last tonumber(redis.call(GET, KEYS[2]) or now) local delta math.max(0, now - last) * rate tokens math.min(capacity, tokens delta) if tokens 1 then redis.call(SET, KEYS[2], now) return 0 end tokens tokens - 1 redis.call(SET, KEYS[1], tokens) redis.call(SET, KEYS[2], now) return 1这段脚本我用了很久实测在单实例每秒几千次调用的压力下依然稳定。关键点是所有判断和更新都在 Lua 里完成避免读-判断-写之间的竞态。你要是分成多条命令发高并发下必然超发。4.2 分布式锁Agent 任务幂等的守护者Agent 执行长任务时用户重复点击、消息重投、服务重试都可能导致同一个任务被执行多次。这时候需要分布式锁来保证同一时刻只有一个执行者。Redis 做分布式锁的经典方案是SET key value NX PX timeoutvalue 用一个唯一标识比如 UUID释放锁的时候要先校验 value 再删除防止误删别人的锁。这个校验加删除必须用 Lua 脚本保证原子性否则还是会有竞态。import uuid import redis def acquire_lock(client, key, ttl_ms30000): token str(uuid.uuid4()) ok client.set(key, token, nxTrue, pxttl_ms) return token if ok else None def release_lock(client, key, token): lua if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) else return 0 end client.eval(lua, 1, key, token)注意锁的过期时间一定要设而且要比任务最长执行时间略长。我见过有人不设过期结果服务崩溃后锁永远不释放整个任务队列卡死。但也不能设太长否则任务失败后要等很久才能重试。折中方案是设一个合理值同时用看门狗机制在任务执行期间定期续期。4.3 缓存击穿、穿透、雪崩三个必须防的坑这三个词听着像玄学其实都是很具体的问题。缓存击穿是指某个热点键过期瞬间大量请求同时打到后端。比如某个热门工具的结果缓存刚过期几百个请求同时发现缓存没了一起去调外部接口直接把接口打挂。解决办法是加互斥锁只让一个请求去回源其他请求等待或者返回旧值。缓存穿透是指查询一个根本不存在的键缓存里没有后端也没有每次请求都穿透到后端。恶意攻击或者参数错误都可能造成这种情况。解决办法是缓存空值即使查不到也存一个短 TTL 的空标记或者用布隆过滤器提前拦截。缓存雪崩是指大量键在同一时刻集中过期导致后端压力骤增。解决办法是给 TTL 加随机扰动比如本来设 10 分钟实际设成 10 分钟加上 0 到 2 分钟的随机值让过期时间分散开。问题触发条件后果解决方案击穿热点键过期后端瞬时压力互斥锁 逻辑过期穿透查询不存在的数据后端被无效请求打满缓存空值 布隆过滤器雪崩大量键同时过期后端整体过载TTL 随机化 多级缓存这三个防护措施我在每个 Agent 项目里都会加上成本很低但关键时刻能救命。尤其是 TTL 随机化一行代码的事却能避免很多半夜被叫起来处理故障的夜晚。5. 从零搭一套可用的缓存层完整实操流程5.1 环境准备与 Redis 部署要点先说部署。本地开发用 Docker 起一个单实例就够了命令很简单docker run -d --name agent-redis \ -p 6379:6379 \ -v /data/redis:/data \ redis:7-alpine \ redis-server --appendonly yes --maxmemory 2gb --maxmemory-policy allkeys-lru这里有几个参数值得说。appendonly yes开启 AOF 持久化保证重启后数据不丢太多。maxmemory限制内存上限防止 Redis 把机器内存吃光。maxmemory-policy allkeys-lru是淘汰策略内存满了优先淘汰最近最少使用的键。但要注意如果你有不能丢的数据就不能用allkeys-lru得用volatile-lru只淘汰设了过期时间的键。生产环境建议至少主从架构主库写、从库读配合哨兵做故障转移。如果数据量大、QPS 高就上集群模式分片。但集群模式有个限制涉及多键的操作必须保证这些键在同一个槽位否则会报错。设计键名的时候可以用哈希标签{}把相关键强制分到同一个槽比如agent:session:{123}:context和agent:session:{123}:state就会落在同一个节点。5.2 客户端选型与连接池配置Python 这边主流是redis-pyJava 这边是 Lettuce 或者 Jedis。选哪个不是重点重点是连接池配置。我见过太多项目用默认配置结果高并发下连接不够用请求排队等到超时。连接池的核心参数是最大连接数、最大空闲连接数、连接超时和读写超时。最大连接数要根据你的并发量估算经验公式是峰值QPS × 平均耗时(秒) × 1.5。比如峰值 500 QPS平均每次操作 2 毫秒那大概需要500 × 0.002 × 1.5 ≈ 2个连接但实际要考虑突发流量一般设 20 到 50 比较稳妥。import redis pool redis.ConnectionPool( hostlocalhost, port6379, max_connections50, socket_timeout2, socket_connect_timeout1, retry_on_timeoutTrue, health_check_interval30 ) client redis.Redis(connection_poolpool)socket_timeout设 2 秒是个经验值太短容易误判超时太长会拖垮整个请求链路。health_check_interval定期做健康检查能及时发现死连接。retry_on_timeout在超时时自动重试一次对偶发的网络抖动很有用但要注意重试会带来幂等性问题写操作要谨慎开启。5.3 缓存读写模板一套能复用的代码骨架我把缓存读写抽象成一个模板核心逻辑是先查缓存命中就返回没命中就回源回源后写缓存。听起来简单但要做好需要处理几个细节空值缓存、互斥回源、TTL 随机化。import json import random import time def get_with_cache(client, key, loader, ttl600, null_ttl60): raw client.get(key) if raw is not None: if raw b__NULL__: return None return json.loads(raw) # 回源 value loader() if value is None: client.set(key, __NULL__, exnull_ttl) return None # TTL 随机化防止雪崩 jitter random.randint(0, max(1, ttl // 5)) client.set(key, json.dumps(value), exttl jitter) return value这个模板覆盖了空值缓存和 TTL 随机化但没加互斥锁。互斥锁适合热点键场景普通键加上反而增加复杂度。判断标准是如果这个键回源的代价很高或者并发访问很集中就加锁否则不加。5.4 会话上下文的具体实现会话上下文是 Agent 里最核心的缓存对象我单独拿出来讲。用 Hash 存键是agent:session:{sessionId}字段是轮次编号值是这一轮的对话内容。每次新对话进来先HGETALL拿到历史拼装 Prompt处理完再HSET新的一轮同时刷新过期时间。def append_turn(client, session_id, turn_index, content, ttl1800): key fagent:session:{session_id} pipe client.pipeline() pipe.hset(key, str(turn_index), json.dumps(content)) pipe.expire(key, ttl) pipe.execute() def get_history(client, session_id, max_turns20): key fagent:session:{session_id} data client.hgetall(key) turns sorted(data.items(), keylambda x: int(x[0])) return [json.loads(v) for _, v in turns[-max_turns:]]用 pipeline 把HSET和EXPIRE打包发送减少网络往返。max_turns限制取多少轮历史防止上下文过长导致 Prompt 超限。这个限制很关键我见过有人把几十轮对话全塞进 Prompt结果 token 费用爆炸模型还因为上下文太长而变傻。提示会话数据如果很重要建议同时落一份到数据库Redis 只做加速。Redis 重启或者淘汰都可能丢数据不能作为唯一存储。6. 线上问题排查实录那些文档里不会写的坑6.1 超时问题从现象到根因的排查路径RedisCommandTimeoutException是 AI Agent 线上最常见的报错。看到这个报错先别急着调大超时时间要按顺序排查几个可能。第一看 Redis 本身的负载。用INFO命令看instantaneous_ops_per_sec和used_memory如果 QPS 很高或者内存接近上限说明 Redis 本身压力大。这时候要么扩容要么优化键的设计减少操作次数。第二看有没有慢命令。SLOWLOG GET 10能列出最近的慢查询。常见的慢命令有KEYS *、大 Hash 的HGETALL、大 ZSet 的ZRANGE。这些命令在数据量大时会阻塞 Redis 单线程导致其他请求排队。解决办法是用SCAN替代KEYS用HSCAN分批取 Hash用ZRANGE时限制范围。第三看网络和连接池。如果 Redis 负载正常但客户端还是超时可能是连接池不够用请求在排队。看客户端的连接池监控如果活跃连接数一直贴着上限就该调大了。第四看是否有大键。用redis-cli --bigkeys扫描找出体积异常的键。大键的读写会占用大量网络带宽和内存是性能杀手。发现大键后要拆分比如一个存了几万条消息的 List拆成多个小 List。6.2 内存告警淘汰策略选错的血泪教训有一次线上 Redis 内存突然涨到上限然后开始疯狂淘汰键把用户的会话数据全清了用户投诉说聊到一半历史没了。排查发现是淘汰策略设成了allkeys-lru而会话数据和其他缓存数据混在一起被无差别淘汰了。正确的做法是按数据重要性分开存储。重要的会话数据用一个独立的 Redis 实例或者独立的数据库编号淘汰策略设成noeviction或者volatile-lru只淘汰设了过期时间的键。不重要的缓存数据用另一个实例随便淘汰。这样即使缓存被清空核心数据也不受影响。如果资源有限只能用一套 Redis那就给所有键都设过期时间用volatile-lru并且把重要数据的 TTL 设长一点降低被淘汰的概率。但这是权宜之计长期还是建议物理隔离。6.3 常见问题速查表现象可能原因排查命令解决方向命令超时慢命令阻塞SLOWLOG GET替换慢命令内存告警大键或数据堆积--bigkeys拆分大键缓存命中率低键设计不合理INFO stats优化键与 TTL数据不一致更新时没删缓存业务日志先删缓存再更新库连接数打满连接池太小或泄漏CLIENT LIST调大池或修泄漏主从延迟写入量过大INFO replication扩容或读写分离这张表我贴在工位上出问题的时候对着查能省不少时间。尤其是数据不一致这一条九成的情况都是更新数据库后忘了删缓存或者删缓存和更新数据库的顺序搞反了。正确顺序是先更新数据库再删除缓存虽然理论上还有极小的不一致窗口但比先删缓存再更新库要安全得多。6.4 几个我踩过的独家坑第一个坑是用KEYS做模糊查询。开发阶段数据少KEYS agent:session:*秒回上线后数据量一大这个命令直接把 Redis 阻塞好几秒所有请求超时。后来全部改成SCAN游标遍历虽然代码复杂点但不会阻塞。第二个坑是在事务里做复杂逻辑。Redis 的MULTI/EXEC事务不支持回滚也不支持条件判断我一开始想用它做检查再更新结果发现根本做不到。后来改用 Lua 脚本才真正实现了原子性的条件操作。第三个坑是忽略了大 Key 的删除开销。删除一个存了几万条数据的 HashDEL命令会阻塞 Redis 好几秒。正确做法是用UNLINK异步删除或者用HSCAN分批删字段。这个坑很隐蔽因为平时删小键根本感觉不到。第四个坑是TTL 设成了 0 或者负数。EXPIRE key 0会立即删除键EXPIRE key -1也是。我有次计算 TTL 的时候用了当前时间减去过期时间结果算出来是负数缓存刚写进去就被删了排查了半天才发现是符号搞反了。7. 缓存治理与长期维护让系统跑得久一点7.1 监控指标哪些数字必须盯着缓存上线不是终点得持续盯着几个关键指标。命中率是最直观的低于 80% 就说明缓存设计有问题要么键设计不合理要么 TTL 太短。内存使用率要控制在 70% 以下留出余量应对突发。慢查询数量持续增长说明有慢命令在拖后腿。连接数接近上限说明连接池要调大或者有泄漏。这些指标建议接入监控系统设好告警阈值。命中率跌破 70%、内存超过 80%、慢查询每分钟超过 10 条都该触发告警。别等到用户投诉了才发现问题那时候已经晚了。7.2 缓存预热与降级方案服务刚启动的时候缓存是空的如果流量直接打进来会有一波回源高峰。解决办法是缓存预热在服务启动后、接收流量前主动把热点数据加载进缓存。预热的数据来源可以是历史访问日志也可以是配置好的热点列表。另一个必须准备的是降级方案。Redis 挂了怎么办不能整个服务跟着挂。我的做法是给缓存操作包一层Redis 不可用时直接跳过缓存走回源同时打日志告警。虽然性能会下降但至少服务还能用。这个降级逻辑一定要提前写好并测试别等真出事了才临时加。7.3 键的清理与生命周期管理缓存数据会越积越多定期清理是必须的。但清理不能乱来要有策略。我的做法是给所有键都设过期时间让 Redis 自动清理大部分。对于没有自然过期时间的键比如某些状态标记写一个定时任务定期扫描清理。清理的时候用SCAN而不是KEYS分批处理每批之间 sleep 一小会儿避免给 Redis 造成压力。清理前先在测试环境验证确认不会误删重要数据。我见过有人写清理脚本的时候正则写错把生产环境的会话数据全删了教训惨痛。7.4 版本升级与数据迁移Redis 版本升级或者架构调整时数据迁移是个麻烦事。如果只是小版本升级通常兼容直接替换即可。如果是大版本升级或者从单机迁到集群就要考虑数据迁移方案。简单的做法是双写新老两套 Redis 同时写读的时候先读新的读不到再读老的等数据自然过期后下线老实例。复杂的做法是用专门的迁移工具但要注意集群模式下槽位的变化。不管哪种方案都要先在测试环境演练一遍确认数据一致性和性能都达标再上生产。8. 一些关于 AI Agent 缓存的个人体会写了这么多最后分享几点我自己的感受。AI Agent 这个领域变化很快模型在迭代框架在更新但缓存这件事的底层逻辑其实很稳定——想清楚数据的特性选对结构和策略做好并发保护剩下的就是持续观察和调整。我见过太多团队在缓存上栽跟头不是因为技术多难而是因为一开始就没想清楚。急着上线随手加个缓存结果键名混乱、TTL 乱设、没有监控出了问题只能靠猜。其实只要在动手前花半小时把缓存对象梳理清楚把键名规范定下来把限流和降级方案想好后面能省掉无数个加班的夜晚。还有一点别迷信最佳实践。别人的方案是别人的场景下总结出来的你的业务特性、流量模型、数据规模都不一样。多压测、多看监控、多复盘从自己的数据里找答案比抄任何方案都靠谱。Redis 只是个工具用得好不好取决于你对业务的理解有多深。