Redis 深度指南
从底层原理到生产实践 · 面试高频考点全面覆盖
⚡ 1. Redis 为什么快
内存操作
数据存储在内存,纳秒级访问,无需磁盘 I/O
单线程模型
无锁竞争、无上下文切换、无死锁风险
I/O 多路复用
epoll/kqueue 单线程处理海量并发连接
高效数据结构
SDS、跳表、压缩列表等专为性能优化
C 语言实现
接近操作系统层面,极低运行开销
简单通信协议
RESP 协议解析快、易实现、开销小
MySQL = 去档案馆查资料:资料很全(持久化、事务、复杂查询),但每次都要走流程(磁盘 I/O、解析 SQL、锁竞争),慢但可靠。
Redis = 桌上贴的便签纸:随手就能看到(内存访问),内容精简(简单数据结构),但容量有限、可能丢失(内存限制、持久化风险)。
Redis 比 MySQL 快约 100 倍(简单 KV 操作场景)
Redis 的瓶颈不在 CPU,而在内存和网络 I/O。单线程避免了锁竞争、上下文切换的开销,配合 I/O 多路复用(epoll),一个线程就能高效处理大量并发连接。Redis 6.0+ 引入多线程仅用于网络 I/O 读写,命令执行仍然是单线程。
📑 2. 底层数据结构 面试高频!
C String
- ❌ 获取长度 O(N) — 需要遍历
- ❌ 二进制不安全 — 遇到 '\0' 截断
- ❌ 缓冲区溢出风险
- ❌ 修改字符串需频繁 realloc
SDS
- ✅ 获取长度 O(1) — 直接读 len
- ✅ 二进制安全 — 不依赖 '\0'
- ✅ 空间预分配 — 减少 realloc
- ✅ 惰性释放 — free 记录可用空间
连续内存、紧凑存储,适合小数据量。元素过多时连锁更新风险。
Redis 7.0+ 用 ListPack 替代 ZipList。ListPack 去掉了 __prev_len 字段,彻底解决了连锁更新问题(Cascade Update),内存更紧凑、性能更稳定。
跳表 = 多层有序链表。通过随机层数实现概率平衡,查找/插入/删除 O(logN),范围查询极其高效。相比红黑树,实现简单且范围查询更友好。ZSet 的底层首选。
ht[0] (旧表 size=4)
ht[1] (新表 size=8)
rehashidx 从 0 逐步推进,每次 CRUD 操作迁移一个 bucket,避免一次性迁移造成阻塞
当 used / size > load_factor(默认 1,BGSAVE 时为 5)时触发扩容。渐进式 rehash:不是一次性搬迁,而是分多次、在每次操作时顺带迁移一个 bucket,确保服务不中断。在此期间,查找会同时查 ht[0] 和 ht[1],新增只写 ht[1]。
IntSet 整数集合
- 有序、紧凑的整数数组
- 支持 int16 / int32 / int64 升级
- 元素少且都是整数时 Set 的底层
- 查找 O(logN)(二分),插入 O(N)
QuickList 快速列表
- List 的底层实现
- ZipList(或 ListPack)的双端链表
- 兼顾内存紧凑与操作效率
- 中间节点可被 LZF 压缩
| 数据类型 | 底层结构 | 适用条件 |
|---|---|---|
| String | SDS | 所有字符串场景 |
| Hash | ListPack / HashTable | field 数量少且值小 → ListPack;否则 → HashTable |
| List | QuickList (ListPack + LinkedList) | 所有 List 场景(早期用 ZipList + LinkedList) |
| Set | IntSet / HashTable | 全是整数且数量 ≤ 512 → IntSet;否则 → HashTable |
| ZSet | ListPack / SkipList + HashTable | 元素 ≤ 128 且每个 ≤ 64 字节 → ListPack;否则 → SkipList |
为什么理解底层结构对内存优化很重要?
知道 Hash 在小数据量下用 ListPack 可以指导你:将多个 key 合并为一个 Hash field,从 O(N) 个 SDS 头变为 1 个 ListPack,内存可节省 50%+。这也是 Redis Object encoding 自动转换的依据——理解阈值才能正确配置 hash-max-listpack-entries、zset-max-listpack-entries 等参数。
💾 3. 持久化机制
触发 BGSAVE
主进程 fork() 子进程,利用操作系统写时复制 (COW)
COW 机制工作
子进程访问 fork 时的内存快照;主进程修改时才复制对应页
生成 dump.rdb
子进程将内存数据以二进制压缩格式写入临时文件,完成后替换旧 RDB
加载恢复
Redis 重启时加载 dump.rdb,速度快(直接映射到内存)
Client 发送写命令
SET key value / HSET / LPUSH ...
追加到 AOF 缓冲区 (aof_buf)
命令以 RESP 协议格式写入内存缓冲区
fsync 写入磁盘
根据 appendfsync 策略决定何时刷盘
AOF 文件持久化
appendonly.aof — 记录所有写操作历史
| appendfsync 策略 | 安全性 | 性能 | 说明 |
|---|---|---|---|
always |
最高 | 最慢 | 每条命令都 fsync,最多丢一条 |
everysec |
较高 | 推荐 | 每秒 fsync 一次,最多丢 1 秒数据 |
no |
最低 | 最快 | 交给 OS 调度刷盘,可能丢较多数据 |
10,000 条命令
fork 子进程
~200 条命令
AOF Rewrite 不是读取旧文件,而是直接遍历当前内存数据库状态,用最少的命令记录。例如对同一个 key 做 1000 次 SET,只需记录最终那一条。重写期间新命令写入 AOF 重写缓冲区,最后追加到新文件。
兼顾 RDB 的加载速度和 AOF 的数据安全性
| 维度 | RDB | AOF |
|---|---|---|
| 持久化方式 | 定时快照(二进制) | 追加写命令日志(文本) |
| 数据安全性 | 可能丢失数分钟 | 最多丢 1 秒 |
| 文件大小 | 紧凑(二进制压缩) | 较大(命令日志) |
| 恢复速度 | 极快 | 较慢(回放命令) |
| 对性能影响 | fork 时短暂影响 | 持续写盘开销 |
| 适用场景 | 冷备份、灾难恢复 | 实时持久化、数据安全优先 |
RDB = 另存为 (Save As):每隔一段时间把当前文件状态完整存一份。恢复快,但可能丢两次存档之间的修改。
AOF = 修订记录 (Change History):每次修改都记一条操作日志。完整但文件大,需要定期"接受所有修订"(rewrite)来精简。
"Redis 重启后数据丢了":某项目只配了 RDB,save 间隔 900 秒,结果宕机丢了 14 分钟数据。
解决:开启 AOF(everysec)+ 混合持久化。同时注意:AOF 重写和 RDB bgsave 不要同时进行(内存翻倍风险)。
🔗 4. 高可用:哨兵与集群
读写
只读
只读
全量同步(首次):Master 生成 RDB → 发送给 Slave → Slave 加载 → 同步增量缓冲区。
增量同步(断线后):基于 repl_backlog 环形缓冲区,offset 差异部分补发。
3~5 个实例
主观下线 SDOWN
Raft 协议
提升新 Master
key → CRC16(key) % 16384 → 对应节点,支持在线迁移槽位
公式:slot = CRC16(key) & 16383。每个 Master 负责一部分槽,客户端缓存槽→节点的映射,直接定位目标节点,减少跳转。
| 维度 | Sentinel | Cluster |
|---|---|---|
| 数据量 | 单机数据量 | 支持数据分片,可存更多数据 |
| 扩展方式 | 只能读扩展(加 Slave) | 读写均可水平扩展 |
| 故障转移 | Sentinel 监控 + 自动切换 | 节点间 Gossip 协议自动转移 |
| 复杂度 | 部署简单 | 至少 6 节点(3 主 3 从) |
| 适用场景 | 数据量不大,需高可用 | 大数据量、高并发读写 |
"Master 宕机后 Sentinel 自动切换需要多久?"
取决于配置:down-after-milliseconds(默认 30s)检测下线 + 投票(~2s)+ 故障转移(~5-10s)≈ 30~40 秒。可以通过调小 down-after-milliseconds 和 failover-timeout 来加速,但太灵敏容易误判(网络抖动触发不必要的切换)。
🔒 5. 分布式锁
多个服务实例同时访问共享资源(库存扣减、订单创建、定时任务去重),需要互斥保证只有一个实例执行。单机 synchronized 只在 JVM 内有效,分布式环境下必须借助外部协调。
# 加锁(原子操作) SET lock:order:123 "unique_value" NX EX 30 # 释放锁(Lua 脚本保证原子性) if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
NX:key 不存在才设置(互斥);EX:设置过期时间(防止死锁);释放时验证 value(防止误解锁)。
Redisson 是 Java 客户端,封装了分布式锁的高级实现:
- 看门狗 (Watchdog):默认锁 30 秒,后台线程每 10 秒(1/3 过期时间)自动续期
- 业务没执行完 → 看门狗持续续期;执行完释放 → 看门狗停止
- 解决了"锁过期但任务还没完成"的核心问题
- 支持可重入锁、公平锁、读写锁、联锁 (MultiLock)
单实例锁的问题:Master 加锁后还没同步到 Slave 就宕机,Slave 上没有锁 → 两个客户端同时持有锁。Redlock 向 N 个独立实例加锁,多数成功才算成功。
"锁过期但任务还没完成":电商扣库存耗时 45 秒,锁设了 30 秒过期。第二个请求拿到了锁,两个请求同时扣库存 → 超卖。
解决:使用 Redisson 的看门狗自动续期,业务没执行完锁不会过期。关键代码:lock.lock(30, TimeUnit.SECONDS) 或不设过期时间让看门狗管理。
🔀 6. 缓存一致性问题
防止"读请求在写请求更新 DB 之前读到旧值并回填缓存"的不一致窗口。延迟时间 > 主从同步延迟 + 读业务耗时。
伪装 Slave
Kafka/RocketMQ
Canal 伪装成 MySQL Slave 订阅 Binlog,数据变更时自动触发缓存更新。解耦、可靠、无侵入业务代码,大型系统的主流方案。
| 策略 | 一致性 | 复杂度 | 适用场景 |
|---|---|---|---|
| 先更新 DB 再删除缓存 | 较高 | 低 | 大多数业务(推荐) |
| 延迟双删 | 较高 | 中 | 读多写少、容忍短暂不一致 |
| Canal 订阅 Binlog | 最高 | 高 | 大型系统、强一致性要求 |
| 设置较短 TTL | 最终一致 | 最低 | 兜底方案,任何策略都应配 |
"数据库更新了但缓存还是旧值":某商品改价后,用户看到的价格还是旧的。排查发现用了"先删缓存再更新 DB"的策略——删缓存后、DB 更新前,另一个请求读到旧 DB 值回填了缓存。
解决:改为"先更新 DB 再删缓存" + 延迟双删 + 设置 TTL 兜底。大型系统可引入 Canal。
🔥 7. BigKey 与 HotKey
📦 BigKey 大Key
定义:单个 key 的 value 过大(String > 10KB,集合元素 > 5000)
- 危害:阻塞 Redis(DEL/序列化耗时长)、网络拥堵、内存不均
- 检测:
redis-cli --bigkeys、MEMORY USAGE key、STRLEN / LLEN / HLEN / SCARD / ZCARD - 处理:
- 拆分大 Hash 为多个小 Hash
- 压缩 value(gzip/snappy)
- UNLINK 异步删除替代 DEL
- 设置合理过期时间
🔥 HotKey 热 Key
定义:被高频访问的 key(QPS 远高于其他 key)
- 危害:单个节点成为热点、CPU 飙升、甚至被打挂
- 检测:
redis-cli --hotkeys、MONITOR(慎用)、OBJECT FREQ key(需开启 maxmemory-policy 为 LFU) - 处理:
- 本地缓存(Caffeine/Guava Cache)
- 读写分离(从节点分担读)
- Key 打散:key_1, key_2 ... key_N 随机读
"一个 Hash 有 1000 万 field 导致阻塞":某系统用一个 Hash 存全量用户配置,每次 HGETALL 返回 1000 万条数据,Redis 阻塞 3 秒以上,导致整个集群超时。
拆分策略:按用户 ID 取模拆为 1024 个小 Hash(如 user:config:001 ~ user:config:1024),每个约 1 万 field。配合 HSCAN 替代 HGETALL,阻塞降到毫秒级。
🏆 8. 面试题精选 30 道
Redis 的性能瓶颈在内存和网络 I/O,不在 CPU。单线程避免了锁竞争、上下文切换和死锁问题,代码也更简洁。
Redis 6.0 引入的多线程仅用于网络 I/O 读写,命令执行仍然是单线程。通过 io-threads 配置,默认关闭。开启后可以显著提升网络 I/O 密集型场景的吞吐量。
SDS (Simple Dynamic String) 相比 C 字符串有四大优势:
1. O(1) 获取长度:SDS 自带 len 字段,C 字符串需要 O(N) 遍历
2. 二进制安全:不依赖 '\0' 判断结束,可以存储图片、视频等二进制数据
3. 防止缓冲区溢出:修改前自动检查空间,不够自动扩容
4. 空间预分配 & 惰性释放:减少内存重分配次数,提高性能
跳表是多层有序链表,通过随机层数实现概率平衡。查找时从最高层开始,逐层下降,时间复杂度 O(logN)。
Redis 选择跳表而非红黑树的原因:
1. 范围查询更友好:ZSet 的 ZRANGE/ZRANGEBYSCORE 需要范围遍历,跳表在最低层是有序链表,范围遍历天然高效
2. 实现简单:跳表代码量远小于红黑树,易于理解和维护
3. 内存灵活:可以通过调整概率参数在时间和空间之间权衡
当 HashTable 需要扩容/缩容时,不是一次性把所有 key 从 ht[0] 迁移到 ht[1],而是分多次、在每次 CRUD 操作时顺带迁移一个 bucket。
原因:如果一次性迁移百万级 key,会长时间阻塞 Redis 主线程,导致服务不可用。
渐进式 Rehash 期间的行为:
- 查找:先查 ht[0],没找到再查 ht[1]
- 新增:只写 ht[1]
- 修改/删除:先在 ht[0] 操作,然后迁移该 bucket 到 ht[1]
详见第 3 节对比表。生产环境推荐:
推荐方案:同时开启 RDB + AOF,使用混合持久化。
- RDB 用于冷备份、灾难恢复(定时生成,文件小,恢复快)
- AOF 用于实时数据保护(everysec 策略,最多丢 1 秒数据)
- 混合持久化让 AOF 重写时先用 RDB 格式记录全量数据,再追加增量 AOF,兼顾恢复速度和数据安全
Redis Cluster 将数据分为 16384 个哈希槽,每个 key 通过 CRC16(key) % 16384 计算所属槽位,每个 Master 节点负责一部分槽。
为什么是 16384 而不是 65536?
1. Gossip 协议消息大小:节点间心跳携带槽位信息 bitmap,16384 只需 2KB,65536 需要 8KB
2. 集群规模:16384 个槽建议集群不超过 1000 节点,实际足够
3. 槽位压缩:16384 的 bitmap 更紧凑,传输效率更高
基础实现:SET key value NX EX 30(NX 互斥 + EX 防死锁),释放用 Lua 脚本验证 value 后删除。
常见坑:
1. 锁过期任务未完成:用 Redisson 看门狗自动续期
2. Master 故障锁丢失:Redlock 多数派加锁
3. 误解锁别人的锁:value 用 UUID 唯一标识
4. 不可重入:用 Redisson 的 Hash 结构计数实现可重入
详见第 6 节。核心结论:
1. 先更新 DB 再删缓存(推荐 Cache Aside):一致性较高,极端情况下短暂不一致
2. 延迟双删:第一次删 + 更新 DB + 延迟后第二次删,覆盖并发读的旧值回填
3. Canal 订阅 Binlog(大型系统):解耦、可靠,但架构复杂
4. 设置 TTL(兜底):任何方案都应配 TTL,保证最终一致性
BigKey 是指单个 key 的 value 过大。标准:String > 10KB,集合类型元素 > 5000。
发现:redis-cli --bigkeys、MEMORY USAGE key、STRLEN/LLEN/HLEN/SCARD/ZCARD
处理:拆分大集合为多个小集合、压缩 value、用 UNLINK 异步删除、设置合理过期时间。
注意:不要对 BigKey 执行 DEL(同步阻塞)、HGETALL、SMEMBERS 等全量操作,使用 HSCAN/SSCAN 替代。
HotKey 是指被高频访问的 key,QPS 远高于平均水平,会导致单个节点过载。
检测:redis-cli --hotkeys(需 LFU 策略)、OBJECT FREQ key、MONITOR(生产慎用)
处理:
- 本地缓存(Caffeine/Guava Cache)拦截部分请求
- 读写分离,从节点分担读压力
- Key 打散:hot_key_1 ~ hot_key_N 随机读
- Redis 4.0+ 的 LFU 淘汰策略自动识别热点
Redis 使用两种策略结合:
1. 惰性删除 (Lazy):不主动删除过期 key,访问时检查是否过期,过期则删除。CPU 友好但内存不友好。
2. 定期删除 (Periodic):每 100ms 随机抽取部分设置了过期时间的 key,检查并删除过期的。如果过期比例超过 25%,继续抽查。
两者结合,在 CPU 和内存之间取得平衡。
当 Redis 内存使用达到 maxmemory 限制时,有以下淘汰策略:
不淘汰:noeviction(默认,拒绝写入)
全量 key:allkeys-lru(最近最少使用)、allkeys-lfu(最不常用)、allkeys-random(随机)
仅过期 key:volatile-lru、volatile-lfu、volatile-random、volatile-ttl(优先删 TTL 短的)
推荐:allkeys-lfu(Redis 4.0+),最符合实际访问模式。
全量同步(首次或断线过长):Master 执行 BGSAVE 生成 RDB → 发给 Slave → Slave 清空旧数据并加载 RDB → Master 发送缓冲区中的增量命令。
增量同步(短时间断线):基于 repl_backlog 环形缓冲区,Slave 带着 offset 重连,Master 补发 offset 之后的数据。如果 offset 已经被覆盖则退化为全量同步。
关键参数:repl-backlog-size(默认 1MB,建议根据写流量调大)。
Sentinel 是 Redis 高可用的实现,通过监控、通知、自动故障转移三大功能保证服务可用。
工作流程:每秒 PING 所有节点 → 超时标记主观下线 (SDOWN) → 多数 Sentinel 确认标记客观下线 (ODOWN) → Raft 协议选举 Leader Sentinel → Leader 执行故障转移。
选新 Master 规则:优先级最高 > 复制偏移量最大 > RunID 最小。
建议:至少部署 3 个 Sentinel 实现高可用(容忍 1 个故障)。
Cluster 中不同 key 可能分布在不同节点,跨节点的批量操作(MGET、PIPELINE、Lua 脚本)无法直接执行。
解决方案:
1. Hash Tag:{user}:name 和 {user}:age 使用相同的 hash tag,保证落在同一个 slot
2. 客户端对 key 按 slot 分组,分组后并行发送到各节点,再汇总结果
3. 评估是否真的需要 Cluster,Sentinel + 单机可能更简单
Redisson 加锁时如果不指定 leaseTime,会启动一个后台定时任务 (Watchdog):
- 默认锁有效期 30 秒 (lockWatchdogTimeout)
- 每隔 10 秒(1/3 超时时间)检查锁是否还被持有
- 如果是,自动续期到 30 秒
- 业务执行完毕释放锁后,看门狗不再续期,锁自然过期
- 如果持有锁的客户端宕机,看门狗停止,锁 30 秒后自动过期释放
缓存穿透:查询一个不存在的数据,缓存和 DB 都没有,每次都穿透到 DB。
→ 解决:布隆过滤器(拦截不存在 key)、缓存空值(设短 TTL)
缓存击穿:一个热点 key 过期瞬间,大量请求同时打到 DB。
→ 解决:互斥锁(只放一个请求去加载)、逻辑过期(不设 TTL,异步更新)
缓存雪崩:大量 key 同时过期,或 Redis 宕机,请求全部涌向 DB。
→ 解决:TTL 加随机偏移、Redis 高可用集群、多级缓存、限流降级
Redis 使用 epoll (Linux) / kqueue (macOS) / select (通用) 实现 I/O 多路复用:
1. Redis 启动时创建 epoll 实例,将监听 socket 注册到 epoll
2. 当新连接到来或已有连接有数据可读时,epoll 通知 Redis
3. Redis 的事件循环 (aeMain) 不断从 epoll 获取就绪事件
4. 根据事件类型分发给对应的处理器(连接处理器、命令处理器、回复处理器)
核心:一个线程同时监控 thousands 个连接,只有就绪的连接才会被处理,避免了线程切换开销。
ZipList 中每个 entry 都有 prev_len 字段记录前一个 entry 的长度。当 entry 长度在 253~254 字节之间变化时,会导致 prev_len 从 1 字节扩展到 5 字节,引发连锁更新——后续所有 entry 的 prev_len 都需要扩展,最坏 O(N²)。
ListPack 的改进:去掉 prev_len,改为记录自身长度。从后向前遍历时,通过当前 entry 的自身长度字段回溯到前一个 entry 的起始位置。彻底消除了连锁更新问题。
RESP (REdis Serialization Protocol) 是 Redis 的通信协议,特点:简单、易读、易实现。
基于前缀区分类型:
- + 简单字符串:+OK\r\n
- - 错误:-ERR unknown command\r\n
- : 整数::1\r\n
- $ 批量字符串:$5\r\nhello\r\n
- * 数组:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
Redis 6.0 引入 RESP3,支持更多数据类型(Map、Set、Boolean 等)。
Redis 事务通过 MULTI → 命令队列 → EXEC 实现:
1. MULTI:开始事务,后续命令进入队列
2. EXEC:原子性执行队列中的所有命令
3. DISCARD:放弃事务
4. WATCH:乐观锁,监视 key 在 EXEC 前是否被修改
不支持回滚!如果队列中某条命令执行失败(语法错误除外),后续命令继续执行。这是设计决策——Redis 追求简单快速,回滚需要保存状态日志。
Lua 脚本在 Redis 中原子执行(不会被其他命令打断),适合需要多条命令保证原子性的场景(分布式锁释放、限流等)。
注意事项:
1. Lua 脚本不能执行太久(默认 5 秒超时 lua-time-limit)
2. 脚本过长会占用大量内存,影响复制
3. Redis 7.0 推荐用 Function 替代 Lua 脚本(持久化更好)
4. EVALSHA 优于 EVAL,避免每次传输完整脚本
Redis 的 Pub/Sub 模式有两个关键缺点:
1. 不持久化:消息发出后如果订阅者不在线,消息就丢了。没有 ack 机制。
2. 不保证可靠投递:网络异常时消息可能丢失。
如果需要可靠消息队列,应该用 Redis Stream(Redis 5.0+),支持持久化、消费者组、ack 机制,类似 Kafka 的简化版。
一个简单的 SET key value 实际占用的内存远大于 value 本身:
- RedisObject 头:16 字节(type + encoding + lru + refcount + ptr)
- SDS 头:sdshdr8 至少 3 字节(len + alloc + flags)+ '\0'
- key 本身:也是 SDS
- HashTable 开销:两个 dictEntry 指针(key + value + next)24 字节 + 桶数组
一个简单的 key-value 最少约 64~100 字节,即使 value 只有几个字符。这是优化内存时需要重点关注的。
1. 利用编码优化:小数据量自动用 ListPack/IntSet,调整阈值参数
2. 合并 key 为 Hash field:多个 key 合并为一个 Hash,减少 RedisObject 头开销
3. 使用合适的数据类型:位图 (Bitmap) 存布尔值、HyperLogLog 存 UV、Geo 存经纬度
4. 设置合理过期:避免无用的 key 占用内存
5. 开启 lazyfree:大 key 异步删除,避免阻塞
6. 压缩列表存储:利用 ziplist-max-entries 和 ziplist-max-value 控制
Gossip 是一种去中心化的集群信息传播协议:
- 每个节点定期(每秒)随机选择几个节点发送 PING 消息,携带自己的状态信息
- 收到 PING 的节点回复 PONG,并更新自己的集群状态
- 新节点加入时,只需 CLUSTER MEET 一个现有节点,信息会通过 Gossip 逐步传播到所有节点
- 优点:去中心化、容错好、扩展方便
- 缺点:信息传播有延迟,最终一致而非强一致
Redlock 流程(详见第 5 节):
1. 向 N 个独立 Redis 实例同时请求加锁
2. 在超时时间内,获得 N/2+1 个锁就算成功
3. 加锁成功后设置锁有效期为 初始有效期 - 加锁耗时
4. 失败则向所有实例释放锁
争议(Martin Kleppmann):分布式系统中时钟不可信(GC 停顿、时钟跳跃),可能导致锁安全性被破坏。
Antirez 反驳:实际场景中时钟误差极小,Redlock 足够安全,比单实例锁更可靠。
建议:大多数场景用 Redisson 单实例 + 看门狗就够了,极端场景用 ZooKeeper/etcd。
| 维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | 丰富(String/Hash/List/Set/ZSet/Stream...) | 仅 String |
| 持久化 | RDB + AOF | 不支持 |
| 线程模型 | 单线程(6.0 网络 I/O 多线程) | 多线程 |
| 集群 | 原生 Cluster | 客户端分片 |
| 内存管理 | 丰富淘汰策略 | LRU |
| 发布/订阅 | 支持 | 不支持 |
总结:Redis 功能更丰富,是当前的首选。Memcached 在纯 KV 缓存 + 多核场景下有优势。
Pipeline:将多条命令打包一次发送,减少网络 RTT。命令之间不保证原子性,每条命令独立执行。
事务 (MULTI/EXEC):命令原子执行(中间不会插入其他客户端的命令),但不保证隔离性(WATCH 可实现乐观锁)。
可以结合:在 Pipeline 中发送 MULTI + 命令 + EXEC,既减少网络开销又保证原子性。
常见瓶颈:
1. BigKey 操作:HGETALL/DEL 大 key 阻塞主线程 → 拆分或用 UNLINK
2. 集中过期:大量 key 同时到期 → TTL 加随机偏移
3. 内存满:频繁淘汰 → 扩容或优化数据结构
4. AOF fsync 频繁:磁盘 I/O 成为瓶颈 → everysec 策略或 SSD
5. 慢查询:KEYS *、SORT、SINTER 大集合 → 用 SCAN 替代
排查工具:SLOWLOG GET、redis-cli --latency、INFO、MEMORY DOCTOR