很多人一聊到缓存选型,张口就是“Redis功能多,Memcached性能好、更简单”。我过去几年在好几个项目里来回切换过这两种组件,也被问过无数次“到底选哪个”。说实话,这个问题的答案在2025年已经和十年前不太一样了。Redis从一个纯粹的缓存演化成了数据结构服务器,而Memcached依然坚持着“极简缓存”的路线,两者早就不是一个维度的东西了。这篇内容我会把数据结构、持久化、高可用、内存管理、性能模型、分布式玩法全部摊开来讲,顺便把大家搜得最多的分布式锁、缓存治理、序列化、哨兵模式与集群模式这些关联知识点也串进去,争取一篇看完,你心里就有明确答案。
1. 为什么到了现在,Redis和Memcached依然会被放在一起比
1.1 两个项目的出身和设计目标完全不同
先说历史。Memcached诞生于2003年,当时是为了解决LiveJournal这类动态网站的数据库压力。它的设计目标极其纯粹:做一个分布式的内存键值缓存系统,把数据库查询结果、页面片段、Session这类数据短期存放在内存里,减少数据库的重复访问。它的核心操作就是set、get、delete,存储结构简单到不能再简单——就是一个字符串键值对,value是字符串或者二进制数据。
Redis在2009年出现,最初的设计者Salvatore Sanfilippo(网名antirez)想做的是一个更高级的缓存和存储系统。它的出发点是:除了缓存,我还想把数据真正地存起来,并且希望操作数据的方式能更接近“数据结构”,而不是简单的字符串。于是Redis从一开始就支持list、set、hash、zset这些数据类型,后来又加入了持久化、复制、事务、Lua脚本、发布订阅等能力,慢慢从“缓存”长成了“数据中间件”。
这个出身差异决定了今天所有的争议:你要是只把它俩当“缓存”用,那Memcached完全够用;你要是想用缓存做更多的事情——比如排行榜、计数、分布式锁、消息队列——那Memcached根本给不了你这些能力。
1.2 热词里那些高频搜索,其实都是Redis的场景
顺便看看搜索热度就知道大家真正关心什么:redis数据类型、redis分布式锁、redis持久化、redis集群、哨兵模式和集群模式的区别、redis缓存治理、redis序列化、redis Desktop Manager、docker安装redis主从。
这些关键词里没有一个和Memcached有直接关系。也就是说,绝大多数人在真实项目里遇到的痛点——怎么部署Redis、怎么给Redis做高可用、怎么用Redis实现分布式锁、怎么处理缓存穿透和缓存击穿——都是Redis生态里的问题。这不是说Memcached不行,而是它根本没有这些能力,所以大家也不会去搜它的对应方案。
所以说到底,这篇文章虽然是做全面对比,但“对比”只是切入点,落到实际操作层面,绝大部分内容都必须围绕Redis展开。因为它的复杂度和能力边界,决定了你需要在部署、使用和维护上花更多心思。
2. 数据结构:Memcached输掉的第一个回合
2.1 存储模型的根本差异:字符串 vs 五大数据类型
Memcached的存储模型可以总结为一句话:全世界的key-value都是字符串。value最大默认1MB(1.4.x之后支持二进制协议,也可以通过编译调整到更大),但本质上它不知道value内部长什么样,也不会帮你做任何数据处理。你想存一个列表,只能在客户端自己做序列化,把整个数组变成一个JSON字符串或者二进制串塞进去。你想给某个字段加一,也要先get出来,改完,再set回去——这个操作连“原子性”都谈不上。
Redis则完全不同。它内置了String、Hash、List、Set、ZSet五种基础类型,以及后续加入的Bitmap(位图)、HyperLogLog(基数统计)、GEO(地理坐标)、Stream(消息流)、JSON(通过模块支持)等扩展类型。每一种类型都有配套的操作命令。
举个最直观的例子。排行榜这个需求,用Redis的ZSet实现,只要一行命令:
ZADD leaderboard 100 user:001然后取前十名:
ZREVRANGE leaderboard 0 9 WITHSCORES整个过程是原子的、有序的、可排序的,还能O(log n)地取任意排名区间。如果换成Memcached,你需要在客户端维护整个排行榜数组,每次更新都要把整个序列化后的榜单重新写入缓存,并发时还要考虑更新顺序,做完这一步你就明白了:不是Memcached不能用,是它根本没有“数据结构”这层语义,所有逻辑都被迫推到应用层。
2.2 数据操作的原子性:为什么INCR会成为分水岭
Memcached其实也提供了一个原子操作:incr/decr,可以对存储的数字字符串做原子增减。很多人在对比时忽略了这一点,但它在计数器场景里很有价值。不过它的限制也很明显:你只能对一个value做加减,不能把这个加减操作和别的数据操作组合起来。
Redis同样有INCR、DECR、INCRBY,用法几乎一样:
INCR counter但Redis更强的是,它可以把多个操作打包成一个事务或者一段Lua脚本来执行,保证整个过程原子完成。Redis分布式锁最主流的实现方式就是利用SET命令的NX和EX参数:
SET lock_key unique_value NX EX 10这一条命令同时完成了“不存在才设置”和“设置过期时间”两个动作,是原子性的。用Memcached实现分布式锁也有社区方案,核心思路是用add命令(只有key不存在时才存储成功)加上过期时间,但Memcached的过期时间到达后是惰性删除还是主动清理,在分布式锁这种讲究“绝对可靠”的场景里,边界情况会把你折磨死。
2.3 序列化问题:Redis社区为什么有那么多序列化框架
搜索词里有“redis序列化”,说明这是很多人在实际使用中才遇见的坑。Redis本身存的就是字节流,理论上你直接使用String类型往里面塞JSON就行,但如果你想把一个对象同时按照它的字段去查询、更新,那字符串就做不到了。比如一个用户对象有姓名、年龄、积分三个字段,用String可能存整个JSON,但用Hash可以分别存三个field:
HSET user:1001 name "张三" age 28 score 500 HINCRBY user:1001 score 10HINCRBY直接对Hash中的某个字段做原子加10,而String类型做不到这种字段级别的更新。这也是为什么Spring Data Redis里会有Jackson2JsonRedisSerializer、GenericJackson2JsonRedisSerializer、Kryo、Protostuff这些序列化方案。选择哪种序列化方案,本质上取决于你准备用Redis的哪些数据结构。如果是做缓存,用String+JSON就足够;如果是做数据结构操作,那么序列化方案必须支持对象到Hash、Set、ZSet这些类型的映射,序列化器选错了,后续业务写起来会非常别扭。
这里给大家一个亲测稳定的组合建议:纯缓存场景,value统一用JSON字符串,key按业务前缀+冒号+业务ID来组织,序列化器选择StringRedisSerializer,不要图省事用JdkSerializationRedisSerializer——那玩意把对象变成一串带包名类名的二进制,占空间不说,一旦你升级了实体类的包名或者字段,老数据全部反序列化失败。需要操作数据结构的场景,把实体类拆成Hash字段,然后配合Jackson的GenericJackson2JsonRedisSerializer做value反序列化,注意不要对Hash整体做JDK序列化存储。
3. 持久化与高可用:缓存重启之后,数据还在吗
3.1 Redis持久化的两种方案:RDB和AOF
这是Redis和Memcached最本质的区别之一。Memcached完全没有持久化能力,数据全部存在内存里,重启进程、宕机、断电,缓存里的数据全部清空。这在早期缓存场景里是可以接受的——缓存本来就是要容忍丢失的。但电商购物车、Feed流、排行榜、在线状态这类数据,如果仅仅因为缓存重启就全部清空,后端数据库会被瞬间冲垮,这就是所谓的缓存雪崩的一种来源。
Redis提供了两种持久化机制。
RDB(Redis DataBase)是快照持久化,在默认配置下,Redis会fork一个子进程,把内存里的数据全量写入一个二进制dump.rdb文件。这种方式恢复速度快、文件紧凑,适合做备份和灾难恢复。缺点是两次快照之间的数据可能会丢失。
AOF(Append Only File)是追加日志持久化,每次写命令都会追加到aof文件中,重启时通过重放日志恢复数据。AOF提供了三种同步策略:always(每条命令都同步刷盘,最安全但性能最差)、everysec(每秒刷一次盘,最多丢一秒数据,性能均衡)、no(交由操作系统刷盘)。生产环境主流方案是everysec,配合RDB定期快照做冷备。
实战里还有一个常见的运维动作:让RDB和AOF同时开启。Redis加载数据时会优先使用AOF,因为AOF保存的数据更完整。同时定期把RDB文件备份到远程存储,用于极端情况下的恢复——比如AOF文件损坏或者整个机器磁盘故障。
3.2 主从复制的基本原理:从节点到底在干什么
Redis的主从复制(replication)是它的高可用基础。一个主节点(master)可以挂多个从节点(slave),从节点会实时同步主节点上的数据变更。同步过程大致分两个阶段:
第一阶段是全量同步。从节点发送PSYNC命令给主节点,主节点fork子进程生成RDB快照,发给从节点,从节点加载完RDB之后,主节点会把快照生成期间产生的新写命令通过复制积压缓冲区(repl_backlog)继续发给从节点。
第二阶段是增量同步。全量完成之后,主节点会持续把新的写命令推送给从节点,从节点执行同样的命令来保持数据一致。这个过程通过网络连接完成,属于异步复制,所以从节点理论上存在短暂的数据滞后,这也是Redis实际使用中需要注意的:读写分离时,刚写入主库的数据立刻去从库读,可能读不到。
从节点的价值不只是备份,它还可以承担读流量。我见过很多团队把缓存流量全部打到主节点上,其实如果读多写少,完全可以把读请求分流到从节点,大幅降低主节点压力。但一定要接受“从节点数据可能有秒级延迟”这个现实,不适合对一致性要求极高的场景。
3.3 哨兵模式与集群模式的区别:这是被搜烂了的问题
很多人分不清Sentinel(哨兵模式)和Cluster(集群模式),这里我一次性讲清楚。
哨兵模式解决的是高可用问题,不解决容量扩展问题。架构上是一主多从,Sentinel进程监控主从节点的健康状态,当主节点挂了,Sentinel会从从节点里选择一个晋升为新的主节点,完成故障转移。业务上还是通过客户端连接到一个逻辑上的主节点,客户端需要配合Sentinel协议去感知主节点变更。哨兵模式对客户端是基本透明的,只要配置上Sentinel地址,客户端就能动态获取当前主节点。它能承载的数据量上限,依然是单机内存的大小,因为只有一个主节点承载写入。
集群模式解决的是容量扩展问题,同时也兼具高可用能力。Redis Cluster把数据按照哈希槽(hash slot)进行分片,固定16384个槽,通过CRC16算法计算key的哈希值,再对16384取模,决定这个key落在哪个节点上。每个节点负责一部分槽,槽可以在节点之间迁移,实现扩缩容。集群模式下,每个主节点可以挂从节点,主节点挂了之后,从节点自动晋升,所以它天然包含了哨兵的功能。
从使用角度来说:数据量预估在单机内存能扛住的范围,比如几十GB以内,优先考虑哨兵模式,简单可靠。一旦数据量到了上百GB,单机内存装不下,或者写入并发已经压垮单节点CPU,就需要上集群模式。集群模式带来的限制也要提前知道:多键操作(MGET、MSET、事务)在不同的槽上会失败,除非这些key都显式地加上相同的hash tag,比如用大括号把同一个标识符包进去:
MGET {user:1001}:orders {user:1001}:cart这种写法可以把多个key映射到同一个槽,从而支持批量操作。但代价是这些key的访问会打到同一台机器上,热点问题需要自行评估。
3.4 Memcached的高可用,只能靠客户端自己搞定
Memcached本身没有主从、没有哨兵、没有集群。它只有一个简单的“分布式”方式:客户端基于一致性哈希(consistent hashing)把key分散到多台Memcached节点上。某个节点挂了,一致性哈希只会影响到部分key的缓存命中,但是没有任何自动故障转移——你需要自己实现一个节点状态监测,然后在客户端把挂掉的节点摘掉。
在Memcached的生态里,有一种间接的高可用思路:做两套Memcached池,读多写少场景下,悲观地同时写两份缓存,读的时候取一份,如果读不到再去另一份。这是很原始的双写策略,和Redis的自动主从复制差了一个时代。所以结论很简单:你在搜索词里能看到“redis哨兵模式和集群模式的区别”,却永远也搜不到“memcached哨兵模式”,因为Memcached根本没有这个概念。
4. 线程模型与性能:Memcached并没有传说中那么快
4.1 Memcached的多线程与Redis的单线程:性能取决于瓶颈
有一个非常流传的误解:Memcached是多线程,所以比单线程的Redis快。这个说法严格来讲要拆开看。
Memcached从1.4.x开始使用了多线程架构,主线程接受网络连接,worker线程处理请求。多线程的好处在于它能够充分利用多核CPU,在请求量大到把CPU打满的场景下,Memcached的扩展性确实强。但Memcached的多线程并不是无锁的——它内部仍然有锁竞争,比如全局的LRU锁在某些操作下会成为瓶颈。
Redis在很长一段时间里以单线程闻名。6.0之前,Redis的网络IO和处理都是由单个事件循环线程完成的,单线程模型最大的优势是没有锁竞争、没有上下文切换开销,加上纯内存操作很快,单线程足以跑到10万+QPS。单线程的瓶颈其实在网络IO上,而不是指令执行上。Redis 6.0引入了多线程IO,把socket读写的部分给到多个线程,但命令执行还是单线程。这意味着Redis的核心语义没有变:所有命令执行是严格串行的,不需要担心并发修改数据的问题。
4.2 实测场景下的差距:小数据Redis占优,大数据两者接近
落实到实际性能对比,我过去在多个项目里做过压测,给你一个比较直观的参考(数值因机器和安装配置而不同,但相对关系比较稳定):
表格如下:
| 对比项 | Redis | Memcached |
|---|---|---|
| 单线程读性能(value=1KB) | 10万+ QPS | 10万+ QPS |
| 多核扩展能力 | 受限于单线程命令执行 | 网络IO多线程,CPU多的机器优势明显 |
| 复杂数据结构操作 | 支持,Lua脚本可组合原子操作 | 不支持,只能客户端处理 |
| 大数据value(100KB以上) | 性能下滑明显,序列化开销大 | 性能相对稳定,直接存取字节流 |
| 持久化性能损失 | AOF everysec约损失5%-20% | 无持久化,零损失 |
| 内存碎片率 | 相对较高,取决于数据结构和分配器 | 使用slab allocation,碎片控制更好 |
从这张表可以看出一条基本结论:如果你的场景极其简单——就是set一个很大的value然后get它,且你只需要纯粹的缓存,不需要持久化,不需要数据结构,那么Memcached在超大value场景下和CPU核数特别多的时候确实有优势。但大部分互联网业务里,Redis凭借多种数据结构和原子操作,可以在一个组件里省掉大量应用层代码,进而节省的是研发和维护成本。
4.3 内存管理:slab分配 vs 多种内存策略
Memcached使用slab allocation机制,把内存划分成多个slab class,每个class内部包含若干个固定大小的chunk。当你set一个key-value时,Memcached会找一个大小刚好能放下它的chunk来存储。这个机制的优点是内存碎片少,缺点是内存利用率取决于数据大小分布——如果你的数据大小和chunk大小不匹配,会出现浪费。比如value都是1.5KB,但slab class里只有2KB的chunk,每个value就要浪费0.5KB。所以使用Memcached时,合理规划value大小分布非常重要,不然内存看似够用,实际存不了多少数据。
Redis的内存管理则更灵活,它使用jemalloc分配器,处理多种大小分配效率较好。Redis 4.0之后还引入了内存碎片整理(active defrag),在后台自动整理碎片,减少内存浪费。另外Redis支持为数据设置精确的过期策略,过期键有三种清理方式:惰性删除(访问时发现过期就删除)、定期删除(后台周期扫描部分过期键)、以及内存淘汰策略(maxmemory-policy)。Redis的淘汰策略有八种,常用的有allkeys-lru、volatile-lru、allkeys-lfu等,而且可以在不重启的情况下动态调整:
CONFIG SET maxmemory-policy allkeys-lruMemcached的过期键处理方式和淘汰机制则相对简单,通过LRU近似算法,在内存不足时淘汰最近最少使用的item。这里有一个特殊情况要提醒:Memcached在内存不足时不会拒绝写入,而是直接淘汰旧数据,所以如果你的Memcached被存满了,你可能会发现缓存里的数据频繁失效,而且没有日志提示——排查命中率下降时,第一个要查的就是是否发生了淘汰。
5. 分布式场景:缓存治理、分布式锁与缓存一致性
5.1 缓存治理:穿透、击穿、雪崩,以及Redis的应对办法
搜索热度里“redis缓存治理”排得很高,这说明很多人已经被缓存故障折腾过了。如果你用Redis做业务缓存,最常遇到的三个问题是:
缓存穿透:请求查询一个根本不存在的数据,缓存里没有,数据库里也没有,每次请求都会穿过缓存打到数据库。高并发下,这会让数据库承受大量无效请求。解决办法有三个:缓存空值(即使查不到也把空结果缓存几十秒)、布隆过滤器(Bloom Filter)预判key是否存在、以及参数校验把明显非法的请求挡在外面。
缓存击穿:某个key的过期时间到了,但同时有大量请求访问这个key,请求全部被打到数据库。解决办法是用分布式锁控制只有一个请求去数据库拉数据,其他请求等待。更简单点的做法是使用“逻辑过期”方案:value里额外存一个逻辑过期时间,主动刷新后台线程,过期了也不立即删除,先返回旧数据再异步更新。
缓存雪崩:大量key同时过期,或者Redis实例整体不可用,导致大量请求直接打到数据库。对策是多管齐下:过期时间加随机偏移量让过期时刻分散开;Redis高可用靠哨兵或集群保证;应用侧做熔断和降级;极端情况使用本地缓存兜底。
这里我想多说一句:以上这些治理手段有一个前提,就是你想清楚了哪些数据该进缓存、哪些不该进。缓存不是越多越好,把数据库中所有查询结果都塞进Redis,只会让一致性维护变得非常痛苦。通常建议缓存那些读多写少、实时性要求不高的数据,比如配置类数据、商品详情、用户画像、排行榜。写多的数据(库存、余额)尽量避免直接缓存,直接用数据库事务+Redis分布式锁来控制并发。
5.2 分布式锁:Redis实现的细节与坑
分布式锁是Redis应用最广的进阶功能之一。标准实现方式如下:
加锁:
SET lock:order:1001 550e8400-e29b-41d4-a716-446655440000 NX PX 30000锁的value要保证唯一(通常用UUID),这样释放锁的时候才能确认是自己加的锁。释放锁时必须使用Lua脚本来保证“判断+删除”的原子性:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end如果不加这个判断而直接delete,可能会出现一个隐患:线程A持锁超时后锁自然过期,线程B重新获得同一把锁,然后A执行完业务,直接把B的锁给删了,导致临界区并发进入。这个坑非常经典,很多新手踩过。
再进一步,单台Redis的分布式锁在极端情况下可能不满足“绝对可靠”,因为Redis的锁是“异步复制,主节点挂掉就丢”。为此Redis官方给出了Redlock算法——向N个独立部署的Redis节点依次加锁,超过半数成功才算加锁成功。但Redlock在分布式系统领域有争议,包括Martin Kleppmann等人都发表过论文批判它的时钟假设和GC停顿问题。实践中的建议是:如果你的业务能容忍极端情况下锁失效(大部分业务其实可以),单节点Redis锁就够用;如果要求绝对可靠,请直接使用ZooKeeper或etcd,不要用Redis。
5.3 序列化与连接工具的选型
刚才提到序列化,再说说客户端和可视化工具。用Redis的日常工作离不开好的连接工具,搜索热度里“另一个redis桌面管理器”被反复搜索,很多人不知道如何选择。
如果你用的是Windows,比较省心的选择是使用开源的Another Redis Desktop Manager,它打包了Windows可执行文件,直接点击安装即可。它支持批量操作、连接多个Redis实例、查看内存分析报告。Mac用户也有对应版本,支持macOS M系列芯片。此外,Redis官方也有一个免费的RedisInsight,不仅能看到key,还能做命令行CLI、慢日志分析、内存分析甚至发布订阅调试,如果追求官方稳定性,优先用RedisInsight。
命令行方面,无论你装的是Linux版本还是Windows版本,redis-cli都是必须熟练掌握的。常用命令:
redis-cli -h 127.0.0.1 -p 6379 -a yourpassword redis-cli --scan --pattern "user:*" redis-cli --latency redis-cli --bigkeys--bigkeys能帮你扫描出占用内存最大的key,这在排查大key导致阻塞时非常有用。大key的问题在Redis里很常见:一个几MB的String或者几十万元的Hash,在操作时阻塞事件循环,影响所有其他请求。遇到这种问题,手段包括拆分大key、用Hash替代String、以及必要时用scan分批处理。
5.4 缓存与数据库的一致性:先更新数据库再删缓存
最后必须聊一个所有用缓存的人都会纠结的问题:缓存和数据库的一致性问题。这不是Redis或Memcached特有的,但用Redis时大家期望值更高,所以矛盾更明显。
常用做法有两种:先更新数据库,再删除缓存;先删缓存,再更新数据库。实际项目里更推荐“先更新数据库,再删除缓存”,理由是删缓存这个操作的代价比更新缓存小,而且如果更新缓存并发写同一个key,容易出现旧数据覆盖新数据的时序问题。删缓存则不存在这个问题,因为下一次读时会重新加载数据库数据。
但是“先更新数据库再删缓存”也有一个经典问题:更新数据库成功之后,删除缓存失败怎么办?解决办法包括重试机制(删除失败放进消息队列,异步重试)、订阅数据库binlog(如Canal)同步删除缓存、以及设置较短的过期时间作为最终兜底。不要指望任何一种方案能做到强一致,缓存本身面向的是最终一致,你要做的只是缩小不一致的时间窗口。
6. 选型决策:这不该是一个二选一的问题
6.1 什么情况下你真的可以考虑Memcached
尽管上面说了很多Redis的优势,但Memcached依然有它的位置,我尽量客观地说。
如果你的项目是纯缓存场景,且满足以下条件,用Memcached完全没有问题,甚至更省心:第一,你只需要get/set/delete,不需要任何数据结构操作;第二,value比较大(比如几十KB到1MB),主要做静态内容或序列化后的Feed缓存;第三,团队对运维复杂度敏感,不想管理持久化、主从、哨兵、集群这些概念;第四,数据可以完全容忍丢失,重启后直接回源数据库做重建。
这个场景在广告系统、静态页面缓存、CDN回源缓存、Session存储里依然存在。另一点是内存效率:如果你的value大小分布比较均匀,Memcached的slab分配器效率可能更高。而且Memcached是多线程,在几十核的机器上,纯缓存协议的处理能力有数量级上的优势,吞吐量上限比Redis单线程模型高不少。
6.2 什么情况闭眼选Redis
反过来说,只要满足下面任何一个条件,就别纠结了,直接选Redis:
需要List、Set、Hash、ZSet等数据结构做业务; 需要持久化,即使是缓存也希望重启能恢复; 需要分布式锁、消息队列、排行榜、限流、UV统计、地理位置等功能; 需要主从复制、哨兵、集群做高可用和扩展; 技术栈里已经有Spring Data Redis等成熟客户端,团队对Redis更熟悉。
实际环境里,90%以上的团队最终都会选Redis,不是因为从众,而是因为它的功能密度让你可以在同一个基础设施上解决缓存、分布式锁、计数、排行榜、限流、幂等等一堆问题。维护一个组件和散布四五个专用组件,运维成本完全不是一个量级。
6.3 从部署落地的角度看,Redis的安装与运维其实也很简单
很多团队不敢选Redis是怕运维复杂。其实日常部署并没有想象中难。Linux系统下最标准的安装流程:
# 下载源码编译安装 wget https://download.redis.io/releases/redis-7.2.4.tar.gz tar xzf redis-7.2.4.tar.gz cd redis-7.2.4 make make install生产环境建议给Redis设置密码、关闭危险命令、启用AOF并配置好内存上限:
requirepass your_strong_password rename-command FLUSHALL "" maxmemory 8gb maxmemory-policy volatile-lru appendonly yes appendfsync everysecDocker方式更简单,特别是搭主从的时候,写一个docker-compose文件就能把主节点和两个从节点拉起来:
services: redis-master: image: redis:7.2 command: redis-server --requirepass 123456 --appendonly yes ports: - "6379:6379" redis-slave1: image: redis:7.2 command: redis-server --slaveof redis-master 6379 --masterauth 123456 depends_on: - redis-master ports: - "6380:6379" redis-slave2: image: redis:7.2 command: redis-server --slaveof redis-master 6379 --masterauth 123456 depends_on: - redis-master ports: - "6381:6379"Windows上的安装也简单,直接下载Redis的Windows压缩包,解压后运行redis-server.exe,或者注册成Windows服务,启动时不需要每次手动点开:
redis-server --service-install redis.windows.conf --loglevel verbose redis-server --service-start设置密码通过配置文件或命令行都可以,配置项还是上面那几个。这些细节如果你踩过坑就会明白,真正消耗时间的不是装起来,而是后续的高可用和生产环境故障演练。
7. 踩坑与经验:我换掉Memcached之后遇到的三个真实问题
7.1 大key阻塞:缓存从没告诉过你这个风险
把Memcached换到Redis之后,我遇到的第一个深坑是“大key阻塞”。Memcached的value非常大时(比如1MB),你的get操作本身也是耗时的,但它多线程处理,一个线程慢最多影响这个请求自己。Redis不同,它是单线程执行命令,如果你不小心存了一个10MB的value,一个get操作就要花几十毫秒,而在这几十毫秒里,所有其他请求全部排队。后果就是Redis的吞吐量骤降,主线程阻塞,从节点复制也出现延迟。
排查方法很简单,刚才提到过,用redis-cli --bigkeys定期扫描,把大key消灭在萌芽状态。或者用命令查看单个key的大小:
MEMORY USAGE user:1001这个命令不会把value取出来,而是通过内存分配器计算占用空间,非常适合排查。修复策略是拆分:一个大的Hash拆成多个小Hash,或者用List分段存储,尽量让单个key的操作时间控制在1毫秒以内。
7.2 缓存穿透压垮数据库:空值缓存要加短TTL
另一个坑是缓存穿透。当时我们有一个根据手机号查用户信息的接口,因为接口被脚本刷请求,打的全是无手机号对应不存在的用户。数据库一时间只剩几千QPS就被打满了。后来发现是每次请求都穿透到数据库查询,而数据库里没数据,所以我们也没有缓存任何东西。解决办法很简单:凡是数据库查询为空的key,也写进Redis,设置一个很短的过期时间,比如5分钟。这样即使被疯狂刷,数据库也只会每个key每5分钟扛一次请求,压力降低了好几个数量级。再加上接口层面做了限流和参数校验,把非法参数提前拒绝掉,现在这套系统再也没被穿透打垮过。
7.3 Redis的持久化导致进程启动变慢:RDB和AOF的取舍
最后说说持久化带来的麻烦。我们有一台Redis节点保存了近20GB的数据,重启一次要加载好几GB的RDB文件,耗时接近一分钟。新版本Redis支持RDB和AOF混合持久化(aof-use-rdb-preamble yes),RDB作为基础快照,后续增量用AOF记录,重启加载速度快,数据安全也更好。另外,一定要设置合理的save策略,避免频繁生成RDB文件导致磁盘IO抖动。高并发写入场景下同时开RDB和AOF,必须关注子进程fork带来的内存开销,建议给系统预留足够内存,否则fork瞬间可能触发OOM。
这些经验都是在踩过坑之后才总结出来的,文章看得再多,不如自己把主从复制、哨兵切换、持久化恢复这些操作完整演练一遍。缓存组件看起来简单,实际生产里对上容量规划、故障演练、监控告警,每一步都可能成为事故点。
8. 一句话建议
回到最初的问题——Redis还是Memcached?如果你只想要一个非常简单的缓存,而且能接受数据全部丢失、不做高可用、不做复杂操作,Memcached依然可以胜任。但凡你有一点想用缓存做更多事情的想法,Redis都是更合理的选择。而且今天Redis的周边生态、资料、可视化工具、云服务托管都远比Memcached丰富,招人和排查问题的成本也低得多。选型不是追新,是减少你未来一年运维和开发的麻烦。至少在我负责过的项目里,切到Redis之后,我没有后悔过一次。