运维知识
悠悠
2026年3月19日

缓存选型踩坑记:Redis 和 Memcached 到底该怎么选?

上周三凌晨两点,我被一个电话吵醒。电话那头是开发小哥,声音有点抖:「哥,线上缓存挂了,用户登录全部失败。」

我脑子瞬间清醒了一半,爬起来开电脑一看,监控图上那条线直接躺平了。Memcached 集群某个节点内存爆了,触发淘汰策略,连带着把会话数据全给清了。那一晚我算是彻底明白了,缓存选型这事儿,真不是随便拍脑袋就能定的。

今天就想跟大家聊聊 Redis 和 Memcached 这两个老家伙,到底有什么区别,实际生产里该怎么选。网上文章一大堆,但很多都是照本宣科,我尽量用实际踩过的坑来聊。

先说数据结构这事儿

Memcached 这东西,设计思路特别纯粹,就是 key-value 存储。你存什么,它就拿什么,不会问你太多问题。代码写起来也简单:

import memcache

mc = memcache.Client(['127.0.0.1:11211'])
mc.set('user:1001', '{"name": "张三", "age": 28}')
data = mc.get('user:1001')

看着挺清爽对吧。但问题也在这儿,你想存个列表怎么办?想存个哈希怎么办?只能自己序列化,存成字符串。取出来的时候再反序列化。

有一次我们做商品库存管理,每个商品有多个规格,每个规格又有不同的库存数量。用 Memcached 的话,我得把整个结构序列化存进去,每次修改某个规格库存,都得把整个对象取出来,改完再存回去。高并发的时候,这操作简直是灾难。

后来换到 Redis 就舒服多了:

import redis

r = redis.Redis(host='127.0.0.1', port=6379)

# 用 hash 结构存商品规格
r.hset('product:1001:stock', 'size:M', 100)
r.hset('product:1001:stock', 'size:L', 50)
r.hset('product:1001:stock', 'size:XL', 30)

# 修改单个规格库存,不用动其他数据
r.hincrby('product:1001:stock', 'size:M', -1)

# 直接拿某个规格
stock = r.hget('product:1001:stock', 'size:M')

Redis 支持的数据结构多了去了,字符串、哈希、列表、集合、有序集合,还有位图、HyperLogLog 这些高级货。有时候想想,这玩意儿哪是缓存啊,简直就是个多功能工具箱。

持久化,这个差别太大了

Memcached 有个特点,数据全在内存里,重启就没了。听起来挺极端,但人家设计思路就是这样——缓存嘛,本来就可以丢。

可实际生产里,有些数据你真不敢让它丢。比如用户会话、购物车、还有一些配置信息。有一次公司做活动,预热阶段把活动配置缓存在 Memcached 里,结果半夜机器重启,配置全没了,第二天活动直接开天窗。

Redis 就不一样,它支持持久化。两种方式:

RDB 快照:隔一段时间把内存数据快照到磁盘

# redis.conf 配置
save 900 1      # 900 秒内至少 1 个 key 变化就保存
save 300 10     # 300 秒内至少 10 个 key 变化就保存
save 60 10000   # 60 秒内至少 10000 个 key 变化就保存

AOF 日志:把每个写操作都记下来,重启时重放

appendonly yes
appendfsync everysec  # 每秒同步一次,性能和安全性折中

我一般建议生产环境两个都开着。RDB 做备份恢复快,AOF 保证数据不丢。虽然会牺牲点性能,但比起数据丢失的损失,这点性能真不算什么。

当然也不是说 Memcached 就不能用。有些场景数据丢了就丢了,比如页面缓存、临时计算结果。这种用 Memcached 反而更合适,毕竟少了很多持久化的开销。

内存管理,这里面的坑不少

说到内存管理,这两个家伙的思路完全不一样。

Memcached 用的是 slab 分配机制。它预先分配好不同大小的内存块,数据来了就找合适大小的块放进去。好处是内存碎片少,分配速度快。但问题也很明显——如果你存的数据大小参差不齐,内存浪费会比较严重。

有次我们存用户信息,有的用户数据几百字节,有的几十字节。Memcached 按最大那块分配,结果内存利用率连 60% 都不到。监控看着内存还剩好多,实际已经存不进新数据了。

Redis 的内存管理更灵活一些,它会根据数据实际大小动态分配。而且可以配置内存淘汰策略:

# 内存满了之后怎么处理
maxmemory 2gb
maxmemory-policy allkeys-lru  # 淘汰最近最少使用的 key

淘汰策略有好几种:

  • volatile-lru:只淘汰有过期时间的 key
  • allkeys-lru:所有 key 里淘汰最近最少使用的
  • volatile-ttl:淘汰即将过期的
  • noeviction:不淘汰,写操作直接报错

这个配置得根据业务来。我们有个场景是用户会话缓存,必须保证在线用户的数据不丢,就用了 volatile-lru,给会话设置过期时间,优先淘汰这些。

性能对比,没那么简单

网上很多文章说 Memcached 性能比 Redis 好,因为它是多线程的。这个说法在早期确实成立,但现在情况变了。

Redis 6.0 之后引入了多线程 IO 处理,虽然命令执行还是单线程,但网络读写已经可以并行处理了。实际测试下来,在高并发场景下,Redis 的性能并不比 Memcached 差,很多时候还更好。

我自己在测试环境做过对比,用 sysbench 压测:

# Memcached 测试
memtier_benchmark --server=127.0.0.1:11211 --protocol=memcache \
  --clients=50 --threads=4 --requests=100000

# Redis 测试
memtier_benchmark --server=127.0.0.1:6379 --protocol=redis \
  --clients=50 --threads=4 --requests=100000

结果挺有意思。简单 get/set 操作,两者差距不大,Memcached 稍微快一点点,大概 5% 左右。但一旦涉及复杂操作,比如哈希、列表,Redis 的优势就出来了。

而且别忘了,性能不是唯一指标。有时候为了那 5% 的性能提升,牺牲掉数据结构、持久化这些功能,到底值不值,得好好算算账。

集群和高可用,这个真不能忽视

早期用 Memcached 的时候,集群是个头疼事儿。它本身不支持集群,得靠客户端做分片。

# 客户端分片示例
servers = ['192.168.1.10:11211', '192.168.1.11:11211', '192.168.1.12:11211']
mc = memcache.Client(servers)

# 客户端根据 key 的 hash 值决定存到哪台机器

这种方式问题很多。某台机器挂了,那部分数据就访问不了了。扩容的时候,大量 key 需要重新分布,缓存基本等于失效。

后来我们有个项目,Memcached 集群从 3 台扩到 5 台,缓存命中率直接从 95% 掉到 40%,数据库差点被打穿。

Redis 的集群方案就成熟多了,原生支持分片和主从复制:

# 创建 6 节点集群(3 主 3 从)
redis-cli --cluster create \
  192.168.1.10:6379 192.168.1.11:6379 192.168.1.12:6379 \
  192.168.1.13:6379 192.168.1.14:6379 192.168.1.15:6379 \
  --cluster-replicas 1

主节点挂了,从节点自动顶上来。扩容的时候,数据可以在线迁移,业务基本无感知。虽然配置起来稍微麻烦点,但长远来看省心多了。

实际选型,我的建议

说了这么多,到底该怎么选?我总结了几条经验:

选 Memcached 的场景:

  • 纯缓存,数据丢了无所谓
  • 只需要简单 key-value 存储
  • 追求极致性能,操作简单
  • 已有架构基于 Memcached,迁移成本高

选 Redis 的场景:

  • 需要持久化,数据不能丢
  • 需要复杂数据结构
  • 需要事务、发布订阅这些高级功能
  • 需要成熟的集群方案
  • 未来业务可能扩展

说实话,现在新项目我基本都直接上 Redis 了。除非是特别明确的简单缓存场景,否则 Redis 的灵活性真的香太多。而且社区活跃,文档齐全,遇到问题容易找到解决方案。

有个事儿挺有意思,我们有个老系统用 Memcached 好多年,一直挺稳定。后来想加个排行榜功能,发现 Memcached 搞不定,最后还是加了个 Redis 专门做这个。结果现在两套缓存并存,运维成本反而上去了。早知道当初直接全用 Redis,也没这些麻烦事儿。

一些实战配置建议

最后给几个实际配置建议,都是踩过坑总结出来的:

Redis 配置:

# 内存限制一定要设,防止吃光内存
maxmemory 4gb
maxmemory-policy allkeys-lru

# 持久化根据业务需求
appendonly yes
appendfsync everysec

# 连接数调大点
maxclients 10000

# 慢查询日志,方便排查
slowlog-log-slower-than 10000
slowlog-max-len 128

Memcached 配置:

# 启动参数
memcached -m 4096 -c 10240 -t 4 -p 11211

# -m 内存大小(MB)
# -c 最大连接数
# -t 线程数
# -p 端口

监控一定要做好。我见过太多缓存出问题,都是因为监控没到位,等发现的时候已经晚了。至少要把内存使用率、命中率、连接数这些指标盯紧了。


写到这儿差不多该收尾了。缓存这事儿说简单也简单,说复杂也复杂。关键还是得结合自己业务来,别盲目跟风,也别固守成规。

有个心得分享给大家:技术选型没有绝对的对错,只有适不适合。有时候看似完美的方案,到了实际生产里可能各种问题。多测试,多观察,出了问题快速响应,比什么都重要。

要是这篇文章对你有帮助,或者你也有缓存选型的踩坑经历,欢迎在评论区聊聊。大家互相学习,少走点弯路。

公众号:运维躬行录

个人博客:躬行笔记

记得关注,后续会持续分享更多运维实战经验。转发给身边需要的同事,说不定哪天就能帮上忙。咱们下期见。

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码