缓存中间件:Redis 与 Memcached
Redis 就是内存里的百货商店——内存键值数据库,C 语言写的,单线程事件驱动,性能极高,缓存/会话/排行榜/分布式锁全能选手。读一次数据库要 10ms,读一次 Redis 只要 0.1ms,差 100 倍。热点数据放 Redis,数据库就不用被反复敲。Memcached 是纯内存 KV,多线程,简单轻量,但功能远不如 Redis 丰富。
4.1 Redis 是什么
论Redis 的脾气
内存键值数据库,C 语言写的,单线程事件驱动,性能极高。Redis 把数据全放内存,读写都是内存操作,每秒十万次读写轻轻松松。它不只是一个缓存——它支持 9 种数据结构(String/List/Hash/Set/ZSet/Bitmap/HyperLogLog/Geo/Stream),能做排行榜、分布式锁、消息队列、限流,简直是"瑞士军刀"。
Redis 8.x 新特性:向量搜索(RediSearch 整合)、JSON 模块增强、时序数据支持。但核心 set/get 命令没变,老配置文件基本兼容。
4.2 安装:Docker 最省事
4.3 核心配置 redis.conf
4.4 九大数据结构(每个配命令 + 应用场景)
| 结构 | 常用命令 | 解释 | 应用场景 |
|---|---|---|---|
| String | SET/GET/INCR/EXPIRE | 最基本的 KV,值可以是字符串/数字/二进制 | 缓存对象、计数器、分布式锁 |
| Hash | HSET/HGET/HGETALL | 键值对集合,像一个小字典 | 存用户信息(user:1 → name/age/email) |
| List | LPUSH/RPUSH/LPOP/RPOP/LRANGE | 双向链表,两端进出 | 消息队列、最新动态列表 |
| Set | SADD/SMEMBERS/SISMEMBER/SINTER | 无序去重集合 | 标签、共同好友、去重 |
| ZSet | ZADD/ZRANGE/ZRANK | 有序集合,每个元素带 score 排序 | 排行榜、热度榜、延迟队列 |
| Bitmap | SETBIT/GETBIT/BITCOUNT | 位操作,一个 bit 存一个状态 | 签到打卡、用户在线状态 |
| HyperLogLog | PFADD/PFCOUNT/PFMERGE | 基数统计(去重计数),省内存 | UV 统计(百万级只需 12KB) |
| Geo | GEOADD/GEORADIUS | 地理位置(经纬度) | 附近的人/店 |
| Stream | XADD/XREAD/XGROUP | 消息流(Kafka 轻量版) | 轻量级消息队列 |
4.5 持久化:RDB 和 AOF
| 对比点 | RDB(快照) | AOF(日志) |
|---|---|---|
| 原理 | 定时把内存数据全量快照存磁盘 | 每写一条命令追加到日志文件 |
| 恢复速度 | 快(直接加载快照) | 慢(重放所有命令) |
| 数据安全 | 差(两次快照之间崩了丢数据) | 好(everysec 最多丢 1 秒) |
| 文件大小 | 小(压缩二进制) | 大(文本日志) |
| 生产建议 | 做备份 | 开 everysec,和 RDB 混合 |
Redis 4.0+ 支持 RDB+AOF 混合持久化:重启时先加载 RDB 快速恢复,再重放 AOF 增量。生产推荐这个模式。
4.6 过期策略与淘汰策略
论key 过期了怎么办
主动过期:Redis 每秒随机抽几个设了 TTL 的 key 检查,过期了就删。惰性过期:你访问一个 key 时才检查它过没过期。两种结合,省 CPU 又省内存。
内存满了怎么办?六种淘汰策略:
| 策略 | 淘汰谁 |
|---|---|
noeviction | 不淘汰,写直接报错(默认) |
allkeys-lru | 所有 key 里淘汰最久没用的(最常用) |
volatile-lru | 只在设了 TTL 的 key 里 LRU 淘汰 |
allkeys-lfu | 所有 key 里淘汰最少用的(8.x 推荐) |
volatile-lfu | 只在设了 TTL 的 key 里 LFU 淘汰 |
volatile-ttl | 淘汰最快过期的 |
4.7 缓存三大问题:穿透、击穿、雪崩
| 问题 | 原因 | 解决方案 | 一句话 |
|---|---|---|---|
| 缓存穿透 | 查一个根本不存在的 key,每次都打到数据库 | 布隆过滤器(Bloom Filter)拦住;缓存空值 | 坏人查不存在的数据 |
| 缓存击穿 | 某个热点 key 突然过期,瞬间大量请求打到数据库 | 热点 key 不过期;互斥锁(只放一个请求去查库) | 热点 key 过期瞬间 |
| 缓存雪崩 | 大量 key 同时过期,或 Redis 挂了,请求全打数据库 | 过期时间加随机值;Redis 集群;熔断降级 | 大片 key 同时过期 |
布隆过滤器是一个巨大的 bit 数组 + 多个哈希函数。把数据哈希到位数组里,查的时候看对应位是不是 1。它说"不存在"就一定不存在,说"存在"可能误判。所以:先用布隆过滤器挡一道,说不存在的直接返回,不用去查 Redis 和数据库。Redis 自己就支持布隆过滤器模块(RedisBloom)。
4.8 分布式锁:SET NX PX
多台应用同时操作同一个资源(比如扣库存),要加分布式锁。最简单的实现:SET lock_key unique_value NX PX 30000——NX 表示"key 不存在才设",PX 表示"30 毫秒后自动过期"。用完了 DEL 掉。
论Redisson:别自己造轮子
生产环境别手写分布式锁。Redisson(Java 客户端)封装好了:可重入锁、公平锁、读写锁、锁续期(watch dog)——你设了 30 秒过期,但业务还没跑完,它自动续期,不用怕锁提前释放。
Redlock 算法:如果单 Redis 挂了锁就丢了,Redlock 要求同时往 5 个独立 Redis 节点加锁,超过半数(3 个)成功才算拿到锁。但争议大,大多数场景用 Redisson + 单 Redis 主从就够了。
4.9 主从复制、哨兵、集群
| 方案 | 原理 | 适合 |
|---|---|---|
| 主从复制 | 主写从读,主写从同步。主挂了要手动切换 | 读多写少,容灾要求不高 |
| 哨兵 Sentinel | 独立进程监控主从,主挂了自动选新主并通知客户端 | 中小规模,自动故障转移 |
| 集群 Cluster | 16384 个哈希槽分到多节点,每个节点管一部分槽,自动分片+故障转移 | 大规模、数据量大、要水平扩展 |
Cluster 的哈希槽:Redis Cluster 把所有 key 分到 16384 个槽里,每个 Master 节点管一段槽。新增节点时把槽迁过去就行。客户端连任何节点,收到 MOVED 重定向就去正确节点找。
4.10 完整案例:Redis 商品秒杀
秒杀场景:100 件商品,瞬间 1 万人来抢。数据库直接打挂?用 Redis 扛:库存放 Redis,扣减用 Lua 脚本(原子操作,防超卖),抢成功的人写数据库,没抢到的直接返回。
4.11 Memcached:纯内存 KV,轻量但功能少
| 对比点 | Redis | Memcached |
|---|---|---|
| 数据结构 | 9 种(String/Hash/List/ZSet...) | 只有 String |
| 持久化 | RDB + AOF | 不支持,重启就没 |
| 线程模型 | 单线程(事件驱动) | 多线程 |
| 集群 | 原生 Cluster | 客户端分片 |
| 内存管理 | 多种淘汰策略 | Slab 分配 |
| 适合 | 复杂业务、排行榜、分布式锁 | 纯缓存、简单 KV |
现在 90% 的场景用 Redis。Memcached 只剩一些老项目和纯缓存场景。它唯一的优势是多线程、简单,但 Redis 单线程已经够快了。
Q1:Redis 为什么快?
查看答案
纯内存操作;单线程避免上下文切换;IO 多路复用(epoll);高效数据结构(跳表、压缩列表)。
Q2:缓存穿透/击穿/雪崩怎么区别和解决?
查看答案
穿透=查不存在的数据→布隆过滤器;击穿=热点key过期→互斥锁/永不过期;雪崩=大量key同时过期→加随机TTL+集群+熔断。
Q3:Redis 分布式锁怎么实现?
查看答案
SET key value NX PX 过期时间,value 设唯一标识(UUID),释放锁用 Lua 脚本先比较再删。生产用 Redisson(自动续期+可重入)。
① Redis = 内存百货商店,9 种数据结构全能选手。生产开 AOF everysec + 混合持久化。
② 缓存三兄弟:穿透(布隆过滤器)、击穿(互斥锁)、雪崩(随机 TTL)。
③ 分布式锁用 Redisson,别手写。秒杀用 Lua 脚本原子扣减库存。