
要说后端技术圈里争执最久、也最容易在架构评审时被翻出来问的问题Redis 和 Memcached 绝对排得进前三。几乎所有负责过线上缓存系统的工程师都经历过“为什么当初不用 Memcached”的灵魂拷问。很多文章把结论简化成一句“Redis 数据类型更多、支持持久化所以 Redis 更强”但真实情况远没有这么简单。这两套系统从出生背景、内存分配策略到高可用方案走的是完全不同的两条路。这篇文章把我多年维护 Redis 多套集群和规模化 Memcached 集群的实际经验整理出来面向后端开发、运维工程师以及准备系统设计面试的读者希望能帮你建立一套真正能用于选型判断的底层框架而不是停留在背面试题层面。1. 先看本质两个缓存系统出生的年代与设计哲学1.1 两个项目的出发点完全不同Memcached 诞生于 2003 年由 Brad Fitzpatrick 为 LiveJournal 开发。那个年代的 Web 应用面临的最大问题是动态页面请求直接把数据库压垮数据库成了整个系统的瓶颈。Memcached 要解决的是“如何用内存扛住海量读请求”这个问题它的核心逻辑很简单把数据库查询结果或页面片段缓存到内存里key-value 形式存调用方读缓存比查数据库快几个数量级。数据源头始终在数据库缓存只是复制品。Redis 诞生于 2009 年作者 Salvatore Sanfilippo 最初的目标是做一个实时 Web 日志分析器。他发现那个场景需要一种比数据库更快、又能保存复杂数据结构的存储于是 Redis 从诞生起就带着“数据结构服务器”的基因。官方文档对 Redis 的定义从来不是“cache”而是 Data Structure Server它要做的是把数据放在内存里同时提供持久化、复制、事务、复杂查询能力一出身就是半个数据库。这两个出发点决定了它们后续所有功能取舍。Memcached 解决的问题是“数据库扛不住读”它断然不需要持久化因为数据丢了可以从数据库重新加载。Redis 解决的问题是“我想在内存中以合适的数据结构保存业务数据同时担心进程重启后数据全没”于是持久化、主从复制、哨兵、集群都是顺理成章的长出来的能力。1.2 设计哲学差异带来的连锁反应Memcached 的设计哲学可以概括为“服务端极简复杂性交给客户端”。它只负责管理内存、维护 key、执行过期和 LRU 淘汰至于数据落在哪个节点、节点挂了怎么办、key 怎么分布全部由客户端的一致性哈希算法解决。好处是服务端无比稳定、协议极简、几乎没什么可配置项坏处是到了大规模场景客户端要做的事情非常多。Redis 的设计哲学则相反它把分布式系统的复杂性主动揽到自己身上。持久化、主从同步、哨兵故障转移、Cluster 分片、Lua 脚本、事务全部由服务端直接提供。这种设计让使用方很方便但代价是部署架构和故障诊断复杂度明显更高。Redis 的配置文件动辄几百行BeMaster 节点、Sentinel 节点、Cluster 槽位迁移这些都是需要认真理解的概念。我用一个生活化的类比来说Memcached 是一把用起来顺手、几乎不会出问题的锤子Redis 则是一整套电动工具箱。锤子能敲钉子但不能钻孔、不能拧螺丝工具箱什么都能干但前提是你知道选哪个头、怎么维护这套工具。理解了这层定位差异很多具体功能上的对比就说得通了。2. 数据结构与功能对比一个工具箱 vs 一把锤子2.1 Redis 的数据结构从 String 到 StreamsRedis 最直观的优势是数据类型丰富。除了基础的 String 键值对它还有 Hash、List、Set、ZSet有序集合、Bitmap、HyperLogLog、Geo、Stream。每一种结构都对应着真实的业务场景。Hash 适合存对象比如用户信息、商品信息。字段增减比整体序列化更灵活。List 适合做消息队列、最新列表。LPUSH BRPOP 可以实现可靠的生产消费模型。Set 适合做标签系统、交集并集运算。ZSet 适合排行榜每个成员带一个 score天然支持按分数排序。Bitmap 适合在线状态、签到统计亿级用户也就占用十几MB内存。HyperLogLog 适合 UV 统计误差很小内存占用固定。Stream 是 Redis 5.0 引入的专业消息队列结构支持消费者组、ack 确认比 List 做队列更严谨。这些数据类型不是摆设。我做过一次具体的改造有一张用户信息表业务方最初用 8 个 String key 分别存用户的昵称、头像、等级、金币、积分、注册时间等字段100 万用户就要 800 万个 key内存占用非常大。改成 Hash 之后一个用户一个 key一个 Hash 里存 8 个字段key 数量降为 100 万整体内存消耗下降一半以上。原因在于每个 String key 都需要独立的字典条目、类型对象、SDS 字符串头而 Hash 在字段少时内部用紧凑的 ziplist 编码把多个字段打包存储省掉了大量对象头开销。这就是数据类型带来的实际价值不是炫技。2.2 Memcached 的字符串与 CAS 机制Memcached 的 value 是一个统一的字节数组客户端可以往里面塞序列化后的对象、JSON、页面片段任何二进制都能存。它内部不做解释只负责存取。绝大部分业务场景里Memcached 的使用方式就是 GET、SET、DELETE加上一个过期时间。但 Memcached 有一个很容易被忽略的亮点CASCompare And Set。它通过 gets 命令拿回数据的 cas token修改时用 cas 命令带着 token 一起提交服务端会校验 token 有没有变化没变才允许写入否则返回 EXISTS。这是标准的乐观锁并发控制在多个客户端同时写同一个 key 的场景里非常实用能防止后写入的请求覆盖掉其他人更新过的值。在 Redis 还停留在“简单 SETNX 实现分布式锁”的年代Memcached 的 CAS 已经是可用的并发控制方案了。Memcached 单 value 体积默认上限 1MB这是 Slab Allocator 最大 chunk 尺寸决定的。超过 1MB 的写入会报错虽然可以在编译期调大但对内存分配效率影响很大不建议在生产环境随便改。另外 Memcached 支持 append、prepend 对已有字符串追加也支持原子计数 incr/decr但它的 incr/decr 只针对存储在 key 中的无符号 64 位整数对内容类型有要求。从功能总量看它像个精悍的小工具比 Redis 少太多但核心场景都覆盖了。2.3 指令集与原子操作能力对比从命令维度看Redis 有几百条指令能覆盖存储、计算、发布订阅、脚本、事务等半个数据库应该具备的能力。Memcached 的指令只有四五组简单直接所以协议解析效率非常高。两者在“原子操作”上的差距是很多高并发方案的决策点。Memcached 有 incr/decr 原子计数可以撑住计数类场景Redis 在此基础上还能执行 Lua 脚本脚本里的多条命令整体原子执行中途不会被其他请求插入。比如库存扣减传统写法是先 GET 库存、在应用层减 1、再 SET 回去高并发下会超卖。用 Lua 脚本把 GET、DECR、判断逻辑写在一起一次提交到 Redis 执行就能保证扣减的原子性。这就是为什么现在做秒杀、做优惠券库存大家默认用 Redis 而不是 Memcached。Redis 还有事务MULTI/EXEC和 WATCH 命令实现的乐观锁功能覆盖了 Memcached 的 CAS。SDK 层面也提供了很多高阶封装比如 Set 结构求交集、ZSet 范围查询、Stream 消息确认这些在 Memcached 里根本不可能实现因为它的数据模型太简单了。3. 内存管理与淘汰策略聊聊资源占用与性能天花板3.1 Memcached 的 Slab Allocator避免碎片的巧妙设计Memcached 的存储引擎基于 Slab Allocator它的工作原理是先把内存划分为多个 slab class不同 slab class 里的 chunk 大小不一样。举个实际例子默认配置下 chunk 尺寸从 96 字节开始以 factor 1.25 的比例逐级增长96、120、150、187、234、292……直到上限 1MB。写入一个 value 时Memcached 会找一个刚好能装下它的 chunk 放入例如 200 字节的 value 落在 234 字节的 chunk 里剩下的 34 字节就浪费了。这种设计换来几个重要好处内存申请和释放是预分配好的不会频繁触发 malloc/free同尺寸 chunk 连续存放很难产生难以回收的内存碎片并发写入时不同 slab 之间可以独立加锁降低锁竞争。这是 Memcached 能在极端高并发下保持低延迟的关键原因之一。代价是空间浪费。我真实观察过一套 48GB 内存的 Memcached 集群业务写入的 value 大小集中在 300 到 800 字节之间实际存储的有效数据量大约到 40GB 就顶天了剩下的空间是 chunk 内部的碎片。而且如果业务里有少量超大 value 和大量小 value大 value 会占据整个 slab class小 value 也要对应地分配 chunk内存使用效率会进一步下降。Memcached 提供了 slab_reassign 选项允许在线调整 slab 分配但生产环境用得很少因为调整过程本身有风险。3.2 Redis 的分配器与 maxmemory 策略Redis 默认使用 jemalloc 内存分配器它把内存按大小分成多个 size class在处理各种不同长度的字符串时表现良好。Redis 对象本身还有 robj 结构包含类型、编码、LRU 或 LFU 信息、引用计数配合 SDSSimple Dynamic String存储字符串整体开销比 Memcached 要精细一些。但如果你把 Redis 纯粹当 String 缓存用每个 key 的元数据开销还是会比 Memcached 高。我在低价值场景测试过同样的 100 万条短字符串 KVMemcached 的 RSS 占用往往比 Redis 低 15% 到 25%这是它简洁性的物理体现。Redis 的内存淘汰策略非常灵活。maxmemory 配置内存上限maxmemory-policy 决定达到上限后的行为包括noeviction不淘汰直接返回错误。allkeys-lru从所有 key 中按近似 LRU 淘汰。volatile-lru只在设置了过期时间的 key 里淘汰。allkeys-lfu / volatile-lfuRedis 4.0 引入的 LFU 策略按访问频率淘汰。allkeys-random / volatile-random随机淘汰。注意 Redis 的 LRU 不是严格 LRU。它采用采样近似算法默认 maxmemory-samples5意思是每次随机采样 5 个 key淘汰其中最久没访问的。这个权衡是为了避免维护严格的 LRU 链表带来的内存开销和性能损失。在热点比较集中的业务里LFU 通常比 LRU 好用它能记住哪些 key 长期高频访问避免热点 key 被冷 key 挤掉。3.3 性能实测单线程 vs 多线程的真相Memcached 从诞生起就是多线程架构主线程负责网络事件循环工作线程并行处理数据操作CPU 多核能直接吃满。在纯字符串 GET/SET 场景一台配置差不多的机器上 Memcached 单节点 QPS 到 20 万甚至更高并不罕见。Redis 至今命令执行仍然是单线程6.0 之后引入的 IO 线程只是把网络读写分担到多个线程真正的命令处理还是单线程串行。这带来一个确定性的收益不存在多线程并发修改数据的锁竞争问题所有命令天然原子性能可预期。单实例 Redis QPS 通常在 6 万到 10 万之间命令越复杂越低。但 Redis 可以通过 Pipeline 把多条命令打包一次发送吞吐能成倍上涨实际压测里 Pipeline 场景单实例到 20 万以上很常见。所以性能比较不能脱离场景。如果你需要的是超高并发下海量简单字符串读写Memcached 多线程的吞吐上限和稳定性有优势。如果你的业务里有很多复杂操作Redis 的数据结构能力可以把多次网络交互合并成一次命令整体效率和可维护性反而更好。4. 持久化与数据安全缓存重启后会发生什么4.1 Memcached 为什么坚持不持久化Memcached 从头到尾没有任何持久化机制数据全在内存里进程重启一切归零。它之所以这么设计核心原因在于它的定位决定了它不需要Memcached 是“数据库之前的缓存层”原始数据在数据库或磁盘存储里缓存丢了重新读一遍数据库就能恢复缓存本身没有独立的数据价值。不做持久化反而是一种优势没有磁盘 IO没有 fork 子进程没有持久化文件需要维护服务端逻辑简单可靠。但在真实业务里“缓存丢了重新读库”这句话的成本可能被低估。如果系统 QPS 很高缓存节点重启后在几十秒甚至几分钟内大量请求会同时打到数据库直接把数据库连接池打满导致服务雪崩。我在维护大流量 Memcached 集群时通常会提前准备“缓存预热脚本”在节点重启后立刻把历史请求最多的那些 key 预先写回缓存。即使是这样依然要配合数据库连接池限流和降级开关才能保证系统稳定。4.2 Redis 的 RDB、AOF 与混合持久化Redis 有两条持久化路线。RDBRedis Database是内存全量快照。bgsave 命令 fork 一个子进程子进程把内存数据序列化到 rdb 文件主进程通过写时复制copy-on-write继续服务请求。RDB 文件加载速度快Redis 重启时直接加载 RDB 通常只需要几秒到几十秒。缺点是两次快照之间的数据会丢快照间隔越长丢得越多。AOFAppend Only File以 Redis 协议格式追加记录每次写操作。appendfsync 参数决定刷盘策略always每条写命令都 fsync最安全但性能开销最大。everysec每秒 fsync 一次最多丢一秒数据工程上最常用。no交给操作系统决定何时刷盘效率最高但可能丢多秒数据。AOF 文件会不断膨胀所以 Redis 提供 AOF 重写rewrite机制生成一个当前数据集的最小命令集替换掉历史的全部命令。Redis 4.0 以后还有混合持久化AOF 文件由“RDB 格式的二进制快照 快照之后的增量命令”组合而成既控制了文件体积又大幅提升重启加载速度。我处理过一个 6GB 的纯 AOF 文件恢复场景纯 AOF 重放要几分钟改成混合模式后加载只需要十几秒。需要注意的是AOF 重写和 RDB 快照都会在某个瞬间 fork 子进程。如果 Redis 实例内存很大fork 时主进程会卡顿几十到几百毫秒这段时间请求延迟明显上升。所以生产环境要监控 fork 耗时把自动触发时间控制在业务低谷。4.3 持久化在缓存场景里的实际意义很多人说“缓存不需要持久化”这句话要打折扣。如果 Redis 只是作为数据库前面的旁路缓存数据源明确缓存丢失后重建成本低那确实可以不持久化。但有两种情况必须开持久化第一缓存重建成本很高。比如某些数据需要聚合多张表、跑很重的计算才能得到缓存丢了之后重建一次要秒级甚至分钟级对用户体验影响大。第二Redis 已经承载了写语义。比如分布式锁、限流计数器、幂等凭证、临时授权信息这些数据既不在数据库也不在消息队列是 Redis 独立持有的业务状态。一旦 Redis 重启全丢分布式锁瞬间失效业务可能进入没有保护的状态。所以我给很多团队的建议是Redis 当缓存用时至少开启 AOF everysec 或每天一次 RDB 快照成本不高关键时刻能救命。Memcached 则把数据事务完整性交给上游数据库自己不承担任何数据恢复职责。5. 高可用与集群从单机到分布式5.1 Redis 的主从、哨兵与 ClusterRedis 的高可用体系是完整且分层的。最基础的是主从复制一个主节点挂多个从节点主节点接收写请求异步同步到从节点。从节点可以承担读流量读写分离。但主节点挂掉时从节点不会自动接管需要人工介入所以它只是数据冗余而不算高可用。主从之上是 Sentinel哨兵。Sentinel 是独立部署的进程监听主从节点的健康状况主节点故障时自动把一个从节点提升为主节点同时把变更通知给客户端。Sentinel 解决了自动故障转移的问题但整个集群仍然只有一个写入节点数据总量受单机内存限制。再往上是 Redis Cluster这是 Redis 3.0 提供的能力。它把数据分配到 16384 个哈希槽hash slot每个主节点负责一段槽位区间支持多主多从横向扩展。通过 CRC16(key) % 16384 计算 key 属于哪个槽客户端根据 cluster nodes 信息把请求路由到对应节点。扩容时槽位迁移是自动的、平滑的不要求业务停机。这里想提一句Redis 主从同步是高可用里最容易被坑的部分。全量同步时主节点要生成 RDB 快照并传给从节点如果数据量大网络带宽会被打满主节点 fork 也会造成延迟抖动。实际操作中要在集群部署前规划好网络带宽主从节点尽量同机房部署避免做跨地域同步。5.2 Memcached 的横向扩展逻辑Memcached 没有官方集群没有主从复制也没有故障转移。横向扩展完全靠客户端的一致性哈希把 key 分散到多台 Memcached 服务器。服务器之间没有任何数据通信不存在网络内协议交互每台都是独立的内存仓库这是它的设计选择。节点挂掉后一致性哈希只会让一部分 key该节点负责的哈希区间内的 key找不到缓存其他 key 仍然命中。但关键是挂掉的那个节点上的数据不会从其他节点恢复只能等客户端重新回源数据库写入。这种模式的优点是架构极简、任何一台机器挂了不会影响其他节点的读写缺点是命中率下降、数据库压力瞬时升高、不会被自动补偿。在 Facebook 的实践里Memcached 被部署成专门的缓存层配合代理层 mcrouter 做路由和故障屏蔽节点故障的流量自动转移到其他缓存节点或数据库。大厂能这么玩是因为有足够的机器冗余和成熟的运维平台中小团队如果想用 Memcached还是要自己做好 DB 保护预案。5.3 一致性哈希与虚拟节点的细节一致性哈希值得好好讲讲因为面试爱问实战也总遇到。最朴素的负载均衡是 hash(key) % NN 是节点数。问题是节点变化时N 一变几乎所有的 key 都会被映射到新的节点缓存大量失效瞬间流量打到数据库这就是典型的缓存雪崩。一致性哈希把哈希空间首尾相连成一个环范围和总哈希值空间一致比如 0 到 2^32-1。每个节点按自己的哈希值放在环上key 也计算哈希值然后沿着环顺时针找第一个节点就这样定位数据。某个节点挂了只有它到上一个节点之间的这段哈希环上的 key 会重新映射到下一个节点影响范围被限制在一小段而不是全部 key。但一致性哈希有个问题节点数量少时分布非常不均匀可能一个节点扛了 80% 的流量。解决办法是虚拟节点每个物理节点对应环上的很多虚拟位置虚拟节点越多数据分布越均匀。libketama 库默认一个物理节点对应 160 个虚拟节点实践效果很好。实际项目中要注意客户端和服务端必须采用相同的一致性哈希算法。如果 Java 客户端用 KetamaHashPython 客户端用 MurmurHash两边计算出的节点完全错位key 命中率直接归零这个坑我在混合语言团队里踩过一次排查了很久才定位到是哈希算法不一致。6. 场景化选型指南什么业务用谁6.1 优先选 Redis 的场景只要有下面任何一个诉求都应该直接选 Redis需要多种数据结构排行榜用 ZSet、用户画像用 Hash、在线状态用 Bitmap、地理位置用 Geo。需要复杂原子操作秒杀库存扣减、分布式锁、限流计数器、幂等校验这些场景需要 Lua 脚本或 Redis 原生原子命令支持。需要持久化和快速恢复缓存数据重建成本高或 Redis 本身承载了写状态。需要原生高可用和集群扩展Sentinel、Cluster 都是官方提供的成熟方案。需要发布订阅、Stream 消息队列、事务能力。举一个具体例子我之前经手的一个积分兑换系统用户每次操作需要同时更新积分余额、生成积分流水、检查是否达到兑换条件。这个逻辑如果放到应用层用多个 Redis 命令完成高并发下很难保证一致性后来把整个逻辑写进 Lua 脚本两次网络交互就完成了完全原子性能也比之前好了一个量级。这种能力只有 Redis 给得了。6.2 Memcached 仍然有优势的场景Memcached 并没有过时它在特定场景里依然有不可替代的优势。第一超高并发的纯 KV 读取。Memcached 多线程模型能充分利用多核 CPU同配置机器下每秒 GET/SET 的吞吐上限通常高于 Redis 单线程实例尤其适合读多写少的简单数据缓存。第二数据本身不需要持久化、不需要复杂结构。比如纯页面片段缓存、静态资源的元数据缓存value 就是一段已经渲染好的字符串SET 进去 GET 出来即可。第三运维成本极低。二进制部署一条命令启动没有持久化文件没有主从节点没有哨兵进程没有 Cluster 槽位配置项寥寥几个。团队基础设施不完善时需要选 AMemcached 的简单性就是它最大的保护伞。第四超低延迟场景。Memcached 协议解析简单内存分配固定某些极端延迟敏感的业务实测下来抖动更小P99 延迟更可控。6.3 混合部署的取舍现在很多中大规模系统会同时用 Redis 和 Memcached而不是二选一。我把自己的实践总结为“分工明确、各有侧重”Memcached 做最热数据的 KV 读缓存比如首页推荐结果、商品基础信息这种纯 GET 场景Redis 做分布式锁、会话、排行榜、计数器和所有需要复杂操作的业务数据结构。这种组合方案的前提是缓存治理规范化统一的缓存 SDK统一 key 命名规范统一连接池管理统一监控告警。如果团队小、人力紧张我更倾向于全部用 Redis最多把配置方向调优减少一个组件的运维复杂度。毕竟在大多数业务里硬件成本远低于人力维护成本。7. 常见问题与排查技巧实录7.1 缓存穿透、击穿、雪崩怎么破这三个问题几乎是缓存面试必问也是线上事故最常见的根源。缓存穿透是指查询一个数据库中不存在的数据。比如恶意请求不断查一个不存在的用户 ID每次都会绕过缓存直接打到数据库数据库压力瞬间升高。常用解法有两个一是对查询结果为空的 key 也缓存起来设置一个短过期时间比如 60 秒二是用布隆过滤器在查询前先判断 key 是否可能存在不存在就直接返回。布隆过滤器会有误判率但能过滤掉绝大多数无效请求。缓存击穿是指一个热点 key 在过期瞬间大量并发请求同时打到数据库。热点 key 的过期不像普通 key 那样均匀一旦失效请求全部穿透。解法有两个比较有效的一是在重建缓存时加互斥锁只允许一个请求去数据库重建数据其他请求等待二是在缓存 value 里保存逻辑过期时间请求中发现逻辑过期后立即返回旧值并触发异步线程刷新缓存。第二种方案用户体验更好也是 Caffeine、Redisson 等框架里的常见玩法。缓存雪崩是指大量 key 同时过期或者缓存节点整体宕机导致流量全部打到数据库。解决思路是Key 的过期时间加上随机值错开比如 TTL 基础时间 random(0, 300) 秒节点宕机则依赖 Sentinel 做自动故障转移同时配合限流降级保护数据库。7.2 数据一致性INCR 为什么不“准”“Redis incr 不准”这个问题经常有人遇到网上也能看到各种讨论。先说结论INCR 命令本身是原子的单线程模型下绝对不存在丢失更新。所谓的不准绝大多数情况是因为你用了 read-modify-write 模式先 GET 一个数在应用层加 1再 SET 回去。这个过程里多个线程并发执行后面的 SET 会覆盖前面的修改最终结果偏小。正确做法是直接用 INCR 或 INCRBY把整个操作交给 Redis 原子执行。还有一类问题是 incrbyfloat 做浮点累加出现精度异常。这不是 Redis 的 bug而是浮点数本身有精度损失。如果业务要求精确比如金额、积分不要用浮点用整数存“分”而不是“元”。关于分布式锁很多人会踩一个坑用 SET key value EX 10 NX 加锁后删除锁时直接 DEL结果把别人获取的锁删了。正确的做法是删除时带一个随机唯一标识用 Lua 脚本先判断 value 再删除。这也是我在生产环境里反复强调的一个细节面试官也特别爱问。7.3 连接与序列化工程师最容易翻车的地方连接管理这块Java 项目用 Jedis 时要注意连接池配置。如果不设 maxTotal默认值很保守高并发下会出现获取连接超时如果设得太大又把 Redis 打成瓶颈。常见的合理配置是 maxTotal 控制在 50 到 200 之间具体结合命令耗时和并发量压测调整。Python 的 redis-py 连接池原理类似连接要复用别每毫秒新建。序列化方面Java 原生序列化是缓存性能杀手。ObjectOutputStream 序列化后的对象体积大、结构冗余还带跨语言问题。建议用 Protostuff、Kryo 或者 JSON尽量把要缓存的实体拆成 Hash 字段而不是整对象塞成一个大 JSON。对于只存用户 ID 和分数的场景最简单的方式是直接拼接字符串比如 “user_id:score”别为了“规范”引入重量级序列化框架。日常排查时我会直接推荐 Redis Desktop Manager 或者 Another Redis Desktop Manager 看 key 分布、TTL 和空间占用。Memcached 不需要可视化工具telnet 连上去执行 stats、stats slabs、stats items 基本够用。要注意生产环境的可视化工具只做观察用别在上面乱执行高开销命令。7.4 运维级避坑速查表最后整理一张我自己长期用的问题排查表按常见现象分类方便遇到问题时快速定位。现象可能原因排查命令处理方案Redis 连接超时连接池耗尽、网络抖动、redis 配置的 maxclients 太小redis-cli info clients抓包调大连接池 maxTotal / maxclients检查网络Redis 慢查询大 key 操作、KEYS 命令、SORT、复杂集合运算SLOWLOG GET 10避免 KEYS改用 SCAN拆分大 keyRedis 内存碎片率高jemalloc 碎片、频繁写入删除大 keyINFO memory 看 mem_fragmentation_ratioratio 1.5 启用 activedefrag必要时重启Redis fork 耗时高内存大、bgsave 触发频繁INFO persistence 看 latest_fork_usec调整自动快照频率错开业务高峰Memcached 命中率突降节点重启、key 过期集中stats 看 get_hits/get_misses预热脚本跑起来TTL 加随机偏移Memcached CPU 高读流量大、chunk 分配频繁stats slabs 看 slab 使用分布按 value 大小拆成不同缓存池缓存穿透无效 key 高频请求监控 DB 慢查询空值缓存、布隆过滤器缓存击穿热点 key 过期监控热点 key互斥锁重建、逻辑过期这里特别提醒一句Redis 的 KEYS 命令在生产环境千万不要执行它会遍历整个 key 空间造成长时间阻塞。查所有 key 要用 SCAN 配合 MATCH 模式分批遍历。Memcached 也没有办法快速列出所有 key它只提供 stats 类命令需要数据巡检的话只能客户端自维护一份 key 目录。我自己做缓存选型这些年最大的体会是不要被“功能多就强”这种表层对比带偏要看这套系统在你业务里的运维成本、故障模式和恢复路径分别是什么。MEMCACHED 简单所以可靠Redis 复杂所以全能各有各的位置。想清楚最坏情况下谁更可控再去做决策而不是答辩的时候抛出“面试里常规答案是选 Redis”就完事。缓存不是一个孤立组件它上面接的是业务流量下面垫的是数据库选型错了最先遭殃的就是数据库。希望这篇整理能帮你在下一次架构评审里把 Redis 和 Memcached 的问题聊透、选对。