一、什么是 Redis?
Redis 是一个开源的、基于内存的 Key-Value 数据库 ,全称是 Remote Dictionary Server。
与传统的 MySQL 不同,Redis 的数据主要存储在内存中,因此具有非常高的读写性能,常被用于:
-
缓存
-
Session 存储
-
排行榜
-
计数器
-
分布式锁
-
消息队列
-
热点数据存储
在实际项目中,Redis 通常不会完全替代 MySQL,而是和 MySQL 配合使用。
一个比较典型的架构是:
用户请求
↓
后端服务器
↓
┌─────────┐
│ Redis │
└─────────┘
↓ ↓
命中 未命中
↓ ↓
返回数据 MySQL
↓
查询数据
↓
写入 Redis
↓
返回
也就是说:
MySQL 负责持久化存储,Redis 负责高速缓存。
二、Redis 与 MySQL 的区别
Redis 和 MySQL 都可以存储数据,但是它们的设计目标完全不同。
2.1 存储方式不同
Redis 是基于内存进行数据存储的。
MySQL 主要是基于磁盘进行持久化存储。
例如:
Redis
内存
↓
快速读取
↓
高性能
而 MySQL:
MySQL
磁盘
↓
持久化存储
↓
数据可靠
Redis 虽然也提供 RDB、AOF 等持久化机制,但它的核心优势依然是内存中的高速访问。
2.2 数据结构不同
MySQL 是典型的关系型数据库。
数据通常以:
数据库
↓
表
↓
行
↓
字段
的形式组织。
例如:
user
id name age
1 张三 20
2 李四 22
Redis 则是 Key-Value 模型:
key value
user:1001 张三
user:1002 李四
并且 Redis 的 Value 并不只是简单的字符串,还可以是:
-
String
-
List
-
Set
-
Hash
-
Sorted Set
等多种数据结构。
2.3 性能不同
Redis 数据主要在内存中操作,因此读写速度非常快。
MySQL 查询通常涉及:
应用程序
↓
MySQL
↓
磁盘 / Buffer Pool
↓
查询
而 Redis:
应用程序
↓
Redis
↓
内存
因此对于大量频繁读取的数据,Redis 通常比 MySQL 更适合作为缓存。
2.4 使用场景不同
MySQL 更适合:
-
用户信息
-
商品信息
-
订单
-
支付记录
-
交易数据
-
财务数据
-
需要长期保存的数据
Redis 更适合:
-
热点数据
-
登录 Session
-
验证码
-
排行榜
-
计数器
-
分布式锁
-
高频访问数据
例如电商网站:
商品详细信息
↓
MySQL
↓
持久化保存
热门商品
↓
Redis
↓
快速访问
2.5 Redis 和 MySQL 对比
| 对比项 | Redis | MySQL |
|---|---|---|
| 类型 | Key-Value 数据库 | 关系型数据库 |
| 主要存储 | 内存 | 磁盘 |
| 查询速度 | 非常快 | 相对较慢 |
| 数据结构 | 丰富 | 表结构 |
| 持久化 | 支持 | 支持 |
| 事务能力 | 相对简单 | 强 |
| 数据可靠性 | 相对较弱 | 较强 |
| 主要用途 | 缓存、高性能读写 | 核心业务数据 |
| 数据量 | 受内存限制 | 通常更大 |
| 典型场景 | 缓存、排行榜、Session | 用户、订单、商品 |
所以面试中可以总结为:
MySQL 更擅长数据的持久化和复杂查询,Redis 更擅长高性能的数据读写和缓存。实际项目中通常采用 Redis + MySQL 的组合,通过 Redis 减少 MySQL 的访问压力,提高系统整体性能。
三、Redis 的数据类型
Redis 最常见的五种基础数据类型:
-
String
-
Hash
-
List
-
Set
-
Sorted Set(ZSet)
四、String
String 是 Redis 最基本的数据类型。
格式:
key → value
例如:
name → 张三
Redis 命令:
SET name 张三
GET name
结果:
张三
4.1 String 的应用场景
缓存对象
例如:
user:1001 → {"id":1001,"name":"张三","age":20}
可以将 JSON 字符串直接存储到 Redis。
计数器
例如文章阅读量:
article:view:1001 → 100
每访问一次:
INCR article:view:1001
变成:
101
所以 Redis 的 String 非常适合做:
-
阅读量
-
点赞数
-
访问次数
-
库存数量
等计数。
分布式锁
String 还可以配合:
SET key value NX EX
实现简单的分布式锁。
五、Hash
Hash 可以理解成:
Redis 中的一个对象。
例如用户:
user:1001
name → 张三
age → 20
gender → 男
Redis:
HSET user:1001 name 张三
HSET user:1001 age 20
HSET user:1001 gender 男
查询:
HGET user:1001 name
得到:
张三
Hash 的应用场景
Hash 非常适合保存对象。
例如:
user:1001
↓
name = 张三
age = 20
phone = 138xxxx
相比于直接使用 String 保存完整 JSON,Hash 可以单独修改某一个字段。
例如只修改年龄:
HSET user:1001 age 21
六、List
List 是一个有序的字符串列表。
可以理解为:
List
A
B
C
D
Redis 支持从左边或者右边插入数据。
例如:
LPUSH message A
LPUSH message B
LPUSH message C
可以形成:
C B A
List 的应用场景
List 可以用于:
-
消息队列
-
最新消息列表
-
时间线
-
简单队列
例如:
消息队列
Redis List
----------------
消息1
消息2
消息3
消息4
----------------
生产者负责添加:
LPUSH
消费者负责获取:
RPOP
从而实现简单的消息队列。
七、Set
Set 是一个无序且元素唯一的集合。
例如:
set:
Java
Redis
MySQL
JavaScript
如果重复添加:
Java
Java
Redis
最终只会保存:
Java
Redis
Set 的应用场景
共同好友
例如:
张三好友:
A B C D
李四好友:
B C E F
使用 Set 可以求交集:
B C
也就是两个人的共同好友。
标签
例如一个用户拥有:
Java
Redis
MySQL
Spring Boot
可以使用 Set 存储用户标签。
八、Sorted Set(ZSet)
Sorted Set,也叫 ZSet。
它和 Set 最大的区别是:
ZSet 中每个元素都有一个 Score,并且会根据 Score 排序。
例如:
张三 100
李四 80
王五 90
Redis 会按照 Score 进行排序。
可以表示:
张三 100
王五 90
李四 80
ZSet 的应用场景
ZSet 最经典的场景就是:
排行榜。
例如游戏排行榜:
玩家 分数
张三 1000
李四 950
王五 900
赵六 850
Redis:
ZADD ranking 1000 张三
ZADD ranking 950 李四
ZADD ranking 900 王五
然后可以按照分数进行排序。
因此:
排行榜是 Redis ZSet 最经典的应用场景之一。
九、Redis 五种数据类型总结
| 类型 | 特点 | 常见应用 |
|---|---|---|
| String | 字符串、数字 | 缓存、计数器、分布式锁 |
| Hash | Key-Value 对象 | 用户信息、对象 |
| List | 有序列表 | 消息队列、时间线 |
| Set | 无序、唯一 | 标签、共同好友 |
| ZSet | 有序、唯一、带 Score | 排行榜 |
面试可以直接回答:
Redis 常见的数据类型有 String、Hash、List、Set 和 Sorted Set。String 常用于缓存和计数器,Hash 常用于存储对象,List 可以实现简单消息队列,Set 适合去重和集合运算,ZSet 最经典的应用场景是排行榜。
十、什么是 Redis 缓存?
在实际项目中,我们经常会遇到一个问题:
如果每一次用户请求都查询 MySQL,那么当用户数量非常大的时候:
100万请求
↓
100万次 MySQL 查询
↓
MySQL 压力巨大
因此我们可以加入 Redis:
用户
↓
Redis
↓
命中?
↓
├── 是 → 直接返回
│
└── 否
↓
MySQL
↓
查询数据
↓
写入 Redis
↓
返回数据
这样就可以减少 MySQL 的访问压力。
但是,使用 Redis 作为缓存以后,会出现三个非常经典的问题:
-
缓存穿透
-
缓存击穿
-
缓存雪崩
这三个问题也是 Redis 面试中的高频考点。
十一、缓存穿透
11.1 什么是缓存穿透?
缓存穿透指的是:
请求查询一个 Redis 和 MySQL 中都不存在的数据,导致请求每次都绕过 Redis,直接访问 MySQL。
例如:
用户请求:
id = 999999999
Redis:
没有
继续查询 MySQL:
没有
然后返回:
不存在
如果攻击者不断请求不存在的 ID:
999999
888888
777777
666666
...
每次都会:
Redis → 没有
↓
MySQL → 没有
最终大量无效请求会直接打到 MySQL。
这就是:
缓存穿透。
十二、缓存穿透的解决方法
常见有两种方法。
方法一:缓存空对象
当 MySQL 查询不到数据时,也把这个结果缓存起来。
例如:
查询:
user:999999
MySQL:
不存在
Redis:
user:999999 → null
下一次请求:
user:999999
直接从 Redis 读取:
null
就不会继续访问 MySQL。
一般会给空值设置一个较短的过期时间,例如:
60秒
避免大量不存在的数据长期占用 Redis。
方法二:布隆过滤器
布隆过滤器是一种:
空间效率非常高的概率型数据结构。
可以在访问 Redis 和 MySQL 之前先判断:
请求
↓
布隆过滤器
↓
这个数据可能存在吗?
↓
├── 一定不存在 → 直接返回
│
└── 可能存在
↓
Redis
↓
MySQL
例如数据库中只有:
1
2
3
4
5
用户请求:
999999
布隆过滤器可以判断:
一定不存在
那么就可以直接拒绝请求。
需要注意
布隆过滤器存在一个特点:
可能误判存在,但不会把存在判断成不存在。
所以它适合用来判断:
一定不存在
然后直接拦截。
十三、缓存击穿
13.1 什么是缓存击穿?
缓存击穿和缓存穿透很容易混淆。
缓存击穿指的是:
一个热点数据突然过期,在同一时间大量请求访问这个热点数据,导致大量请求同时访问 MySQL。
例如:
Redis:
hot:user:1001
这是一个非常热门的数据。
假设:
10000 个用户
同时访问这个数据。
正常情况下:
10000 请求
↓
Redis
↓
命中
↓
直接返回
但是如果这个热点数据突然过期:
Redis
↓
缓存失效
此时:
10000 请求
↓ ↓ ↓ ↓ ↓
同时访问 MySQL
MySQL 瞬间承受巨大的压力。
这就是:
缓存击穿。
十四、缓存击穿的解决方法
方法一:互斥锁
当发现缓存不存在时,不让所有请求同时查询 MySQL,而是只允许一个请求去查询。
例如:
请求A
请求B
请求C
请求D
↓
发现 Redis 没有数据
↓
加锁
↓
请求A获得锁
↓
查询MySQL
↓
写入Redis
↓
释放锁
其他请求:
请求B
请求C
请求D
↓
等待
↓
Redis重新查询
↓
获得数据
最终:
只有一个请求访问 MySQL
从而避免大量请求同时击穿数据库。
方法二:逻辑过期
另一种方式是:
缓存本身不设置真正的过期时间,而是在数据内部保存一个逻辑过期时间。
例如:
{
"data": "商品信息",
"expireTime": "10:00:00"
}
当请求发现:
当前时间 > expireTime
说明数据逻辑上已经过期。
但是:
仍然可以先返回旧数据。
然后后台异步更新缓存。
流程:
请求
↓
Redis
↓
发现逻辑过期
↓
返回旧数据
↓
后台线程更新
↓
MySQL
↓
重新写入 Redis
这样用户不会因为缓存更新而大量等待 MySQL。
十五、缓存雪崩
15.1 什么是缓存雪崩?
缓存雪崩指的是:
大量缓存数据在同一时间失效,导致大量请求直接访问数据库,从而造成数据库压力瞬间暴增。
例如:
系统缓存了:
商品1
商品2
商品3
商品4
...
商品10000
如果这些数据都是:
10:00
同时过期。
那么:
10:00
↓
大量 Redis Key 同时失效
↓
大量请求无法命中 Redis
↓
大量请求访问 MySQL
↓
MySQL 压力暴增
↓
甚至数据库宕机
这就是:
缓存雪崩。
十六、缓存击穿和缓存雪崩的区别
这是面试中非常容易问的问题。
缓存击穿
重点:
一个热点 Key 过期。
例如:
热门商品
↓
缓存过期
↓
大量请求同时访问 MySQL
缓存雪崩
重点:
大量 Key 同时过期。
例如:
商品1
商品2
商品3
...
商品10000
同时过期
↓
大量请求访问 MySQL
可以记忆为:
穿透:不存在
击穿:一个热点 Key
雪崩:大量 Key
十七、缓存雪崩的解决方法
方法一:设置随机过期时间
不要让所有缓存同时过期。
例如原本:
所有缓存:
TTL = 30分钟
可以改成:
TTL = 30分钟 + 随机时间
例如:
商品A:30分钟
商品B:32分钟
商品C:35分钟
商品D:38分钟
这样缓存会逐渐失效,而不是在同一时间大量失效。
方法二:热点数据永不过期
对于非常重要的热点数据,可以不设置 Redis 的物理过期时间。
通过:
逻辑过期
或者后台定时任务主动更新。
例如:
热门商品
热门排行榜
热门文章
这些数据可以提前刷新。
方法三:Redis 集群
如果单个 Redis 节点出现问题,可以使用 Redis Cluster 等方案进行分布式部署。
例如:
Redis Cluster
/ | \
Node1 Node2 Node3
通过集群提高:
-
可用性
-
承载能力
-
数据分布能力
方法四:服务降级
当 Redis 或 MySQL 出现异常时,可以进行服务降级。
例如:
正常:
Redis
↓
MySQL
↓
返回完整数据
异常情况下:
Redis异常
↓
返回默认数据
或者:
暂时关闭非核心功能
从而保证核心业务继续运行。
十八、缓存三大问题总结
| 问题 | 原因 | 典型情况 | 解决方案 |
|---|---|---|---|
| 缓存穿透 | 查询不存在的数据 | Redis没有,MySQL也没有 | 布隆过滤器、缓存空值 |
| 缓存击穿 | 热点Key突然过期 | 一个热点Key大量请求 | 互斥锁、逻辑过期 |
| 缓存雪崩 | 大量Key同时过期 | 大量缓存同时失效 | 随机TTL、逻辑过期、集群、降级 |
可以用一句话记忆:
缓存穿透:
查不存在的数据
缓存击穿:
一个热点Key失效
缓存雪崩:
大量Key同时失效
十九、Redis + MySQL 的经典缓存架构
实际项目中经常使用:
用户请求
↓
后端服务
↓
查询 Redis
↓
┌────┴────┐
↓ ↓
命中 未命中
↓ ↓
返回数据 查询 MySQL
↓
获取数据
↓
写入 Redis
↓
返回
代码逻辑可以简单理解为:
String data = redis.get(key);
if (data != null) {
return data;
}
data = mysql.query(key);
if (data != null) {
redis.set(key, data);
}
return data;
实际项目还需要进一步考虑:
缓存穿透
缓存击穿
缓存雪崩
缓存与数据库数据一致性
缓存过期时间
Redis 高可用
Redis 持久化
二十、面试总结
如果面试官问:
"你了解 Redis 吗?"
可以这样回答:
Redis 是一个基于内存的 Key-Value 数据库,主要特点是读写速度快、支持丰富的数据结构,因此在实际项目中经常作为缓存使用。Redis 常见的数据类型包括 String、Hash、List、Set 和 Sorted Set。
Redis 通常和 MySQL 配合使用,MySQL 主要负责核心业务数据的持久化存储,而 Redis 主要负责热点数据缓存,从而减少 MySQL 的访问压力,提高系统性能。
使用 Redis 做缓存时,需要重点考虑缓存穿透、缓存击穿和缓存雪崩三个问题。缓存穿透是查询不存在的数据,可以通过缓存空值或者布隆过滤器解决;缓存击穿是热点 Key 失效导致大量请求同时访问数据库,可以通过互斥锁或者逻辑过期解决;缓存雪崩是大量缓存 Key 同时失效,可以通过设置随机过期时间、逻辑过期、Redis 集群和服务降级等方式解决。
二十一、最终记忆图
Redis
│
┌──────────────┼──────────────┐
│ │ │
数据类型 Redis缓存 MySQL
│ │ │
┌────┼────┐ │ 持久化存储
│ │ │ │
String Hash List │
│ │ │ │
Set ZSet │
│
┌───────────┼───────────┐
│ │ │
穿透 击穿 雪崩
│ │ │
不存在数据 热点Key 大量Key
│ │ │
布隆过滤器 互斥锁 随机TTL
缓存空值 逻辑过期 逻辑过期
集群/降级
一句话总结
Redis 的核心价值就是利用内存和丰富的数据结构提供高性能的数据访问能力;MySQL 负责可靠持久化,Redis 负责高速缓存。使用 Redis 时重点掌握五种数据类型,以及缓存穿透、缓存击穿、缓存雪崩三个经典问题和对应的解决方案。