上篇的 MongoDB 解决”结构灵活”,这一篇的 Redis 解决另一个问题:快。它把数据存在内存里,读写速度是微秒级,比查数据库快成百上千倍。它是后端工程师的”标配武器”——缓存、排行榜、计数器、分布式锁都离不开它。
读完这篇,你能说清 Redis 为什么快、五种核心数据类型怎么用、以及缓存三大坑怎么防。
Redis 为什么这么快
三个原因:
- 数据在内存:内存访问比磁盘快几个数量级,这是根本。
- 单线程模型:Redis 核心处理是单线程的,避免了多线程锁竞争和上下文切换。别误会”单线程=慢”,因为它够快且无需锁,反而简单高效。(新版有多线程做异步删除等,但命令执行仍单线程。)
- 基于 IO 多路复用:一个线程能同时处理大量连接,不阻塞。
代价是:内存贵、容量有限,且 Redis 是”缓存”而非”唯一数据源”——数据最终要落盘到 MySQL 等持久层,Redis 只加速热点访问。
五大核心数据类型
1. 字符串(String)
最常用,存文本或数字,可做计数器:
SET user:1:name "小明"
GET user:1:name
INCR article:100:views # 阅读量 +1,原子操作
EXPIRE article:100:views 3600 # 1 小时后过期
INCR 是原子自增,高并发下不会算错,特别适合点赞数、库存计数。
2. 哈希(Hash)
适合存”对象的多个字段”,比如用户信息:
HSET user:1 name "小明" age 18
HGET user:1 name
HGETALL user:1
比把整个对象序列化成一个 String 更省,能单独改某个字段。
3. 列表(List)
有序可重复,可做简单队列:
LPUSH queue "task1" # 左侧入队
RPOP queue # 右侧出队
LPUSH+RPOP 实现 FIFO 队列;配合 BRPOP 还能做阻塞队列。
4. 集合(Set)
无序不重复,适合去重、共同好友:
SADD tags:1 "vip" "new"
SISMEMBER tags:1 "vip" # 是否包含
SINTER tags:1 tags:2 # 交集:共同标签
5. 有序集合(ZSet)
带分数的有序集合,天然适合排行榜:
ZADD leaderboard 100 "小明"
ZADD leaderboard 200 "小红"
ZREVRANGE leaderboard 0 9 WITHSCORES # 取前 10 名(分数降序)
分数(如积分)决定排名,ZREVRANGE 直接拿 Top N,做游戏排行榜、热搜榜极方便。
典型用法:把 Redis 当缓存
最常见架构:先查 Redis,命中直接返回;没命中再查 MySQL,并把结果写回 Redis:
请求 → Redis 有? → 有:返回
↓ 无
查 MySQL → 写入 Redis → 返回
伪代码:
data = redis.get(f"user:{id}")
if not data:
data = db.query(f"SELECT * FROM user WHERE id={id}")
redis.setex(f"user:{id}", 3600, serialize(data)) # 缓存 1 小时
缓存三大经典坑
这是面试和实战必考:
- 缓存穿透:查一个根本不存在的数据(如 id=-1),Redis 没有、每次都打到 MySQL,攻击者用不存在的 key 把数据库打挂。解法:缓存空值(短过期)、或用布隆过滤器拦截不存在的 key。
- 缓存击穿:某个热点 key 突然过期,瞬间大量请求同时打到数据库。解法:热点 key 不过期(逻辑过期)、或加互斥锁只放一个请求去查库重建缓存。
- 缓存雪崩:大量 key 同一时刻过期,或 Redis 宕机,请求全部涌向数据库。解法:过期时间加随机抖动错开、Redis 高可用(主从+哨兵)、限流降级。
一句话记:穿透是”查不存在的”、击穿是”热点过期”、雪崩是”集体失效”。
常见坑位提醒
- 把 Redis 当主数据库:Redis 持久化可能丢数据(尤其默认 RDB 快照间隔),它定位是缓存/辅助,核心数据还得在 MySQL。
- key 没有过期时间:只
SET不EXPIRE,Redis 内存被占满触发淘汰,甚至 OOM。给缓存 key 设合理 TTL。 - 大 key 问题:一个 String 存几 MB、或一个 Hash 有百万字段,操作它会阻塞单线程。拆分大 key。
- key 命名混乱:
user1、u_1、user:1混用,后期难维护。统一业务:id:字段规范。 - 用了 Redis 锁却没设超时:
SETNX加锁后程序崩溃没释放,锁死。务必加过期时间,或用 Redlock 等成熟方案。
持久化:内存数据怎么不丢
Redis 是内存库,重启后数据会没,所以必须持久化到磁盘:
- RDB(快照):定时把内存数据拍成二进制快照文件。优点:恢复快、文件小;缺点:两次快照间的数据会丢(如宕机丢最近 5 分钟)。
- AOF(追加日志):把每条写命令追加记录,重启时重放。可配置成”每秒落盘”,最多丢 1 秒数据,更安全,但文件更大、恢复慢。
实战常两者结合:RDB 做定期全量备份,AOF 保最近写入。但务必记住:持久化是为”故障恢复”,不是为”当主数据库”。AOF 重写、RDB 生成都有开销,配置不当会阻塞。
高可用:主从、哨兵与集群
单点 Redis 挂了,缓存全失效、请求全压到 MySQL——这就是”缓存雪崩”的极端版。生产要用高可用:
- 主从复制:主节点写、从节点读,既分担读压力又做备份。
- 哨兵(Sentinel):监控主节点,主挂了自动把一个从提升为新主,实现故障转移。
- Cluster 集群:数据按槽位分片到多节点,突破单机内存上限,支撑海量缓存。
对绝大多数业务,用云厂商的托管 Redis(自带主从+哨兵+备份)最省心,别自己手搓。
进阶实战一:分布式锁
多实例部署时,要保证”同一时刻只有一个实例执行某任务”(如定时发券),用 Redis 做分布式锁:
# 加锁:SET 带 NX(不存在才设)和 EX(过期),原子操作
SET lock:order:100 "inst1" NX EX 30
# 业务执行完释放(用 Lua 保证"只删自己的锁")
# 若程序崩溃,30 秒后锁自动过期,不会死锁
要点:加锁和设过期必须原子(否则崩溃在 SET 和 EXPIRE 之间会死锁);释放锁要用脚本确认是自己的锁,别误删别人的。更严谨用 Redlock,但多数场景单实例锁够用。
进阶实战二:简单限流
用 INCR + 过期时间做接口限流:
# 每 IP 每分钟最多 100 次:key 不存在时 INCR 返回 1 并设过期
INCR rate:limit:ip:1.2.3.4
EXPIRE rate:limit:ip:1.2.3.4 60
# 值 > 100 则拒绝请求
这是”计数器限流”的最简实现,配合滑动窗口能更平滑。
内存优化与淘汰策略
Redis 数据在内存,内存有限,必须知道”满了怎么办”和”怎么省内存”:
- 淘汰策略(maxmemory-policy):内存到上限后的处理。常用:
allkeys-lru:所有 key 里淘汰最久没用的(最常用)。volatile-lru:只在设了过期的 key 里淘汰最久没用的。allkeys-lfu:按访问频率淘汰(新近但少访问的先走)。noeviction:不淘汰,写操作直接报错(危险,慎用)。
- 省内存技巧:用紧凑数据结构(如用
ziplist/listpack编码的小 Hash/List)、合理设过期、避免大 key(一个 String 几 MB 会阻塞单线程)、定期清理无用 key。
大 key 是 Redis 性能头号杀手——一个百万字段的 Hash,执行 HGETALL 会长时间阻塞单线程,拖垮整实例。发现大 key 要拆分(如按哈希分桶成多个小 key)。
发布订阅与 Lua 脚本
除了五种数据类型,还有两个常用能力:
- 发布订阅(Pub/Sub):一个客户端发消息到频道,多个订阅者实时收到,适合轻量消息通知(如”订单状态变更广播”)。注意 Pub/Sub 不持久化,订阅者离线期间的消息会丢,重要消息别只靠它。
- Lua 脚本:把一段逻辑写成脚本在 Redis 服务端原子执行,避免”先 GET 再 SET”的网络往返和竞态。前面分布式锁的”只删自己的锁”就用 Lua 保证原子。脚本要短小,别在里面写重逻辑,否则阻塞单线程。
小测验:看看你掌握了没
- 问题一:Redis 为什么快?答案:数据在内存、单线程无锁竞争、IO 多路复用高并发。
- 问题二:排行榜该用哪种类型?答案:有序集合 ZSet,按分数排序取 Top N。
- 问题三:缓存穿透和击穿区别?答案:穿透是查不存在的数据总打库,击穿是热点 key 过期瞬间打库。
这一篇你该记住的
- Redis 快因:内存存储、单线程无锁、IO 多路复用;定位是缓存/辅助,非主库。
- 五种类型:String(计数/缓存)、Hash(对象字段)、List(队列)、Set(去重/交集)、ZSet(排行榜)。
- 缓存模式:先查 Redis 命中则返回,未命中查库并写回。
- 三大坑:穿透(查不存在)、击穿(热点过期)、雪崩(集体失效),各有对应解法。
- 坑:别当主库、key 要设 TTL、避免大 key、锁要超时。
NoSQL 三篇讲完,你已能按场景选型和上手。但无论关系型还是 NoSQL,跑得慢了都得调优——下个系列我们专攻数据库性能调优。