Redis
一、Redis 基础
01 什么是 Redis ⭐⭐⭐
Redis (REmote DIctionary Server)是一个基于 C 语言开发的开源 NoSQL 数据库(BSD 许可)。与传统数据库不同的是,Redis 的数据是保存在内存中的(内存数据库,支持持久化),因此读写速度非常快,被广泛应用于分布式缓存方向。并且,Redis 存储的是 KV 键值对数据。
02 Redis 为什么这么快?⭐️⭐️⭐
Redis 快的本质原始是因为它的操作都是在内存中进行的,靠 单线程 和 IO 多路复用解决了并发性能瓶颈,再配合深度优化的底层数据结构和简洁的通信协议,把性能压榨到了极致。
Redis 主要是单线程执行命令,但网络 I/O、持久化等部分可以使用多线程或后台线程。
03 除了 Redis,你还知道哪些分布式缓存方案?(了解即可)
分布式缓存的话,比较老牌同时也是使用的比较多的还是 Memcached 和 Redis。不过,现在基本没有看过还有项目使用 Memcached 来做缓存,都是直接用 Redis。
04 Redis 如何删除过期 Key?⭐⭐
Redis 采用惰性删除和定期删除相结合的策略:
- 惰性删除:访问 Key 时检查是否过期,过期后立即删除。
- 定期删除:Redis 定期随机检查设置了过期时间的 Key,并删除其中已过期的 Key。
两种策略结合可以在控制 CPU 开销的同时,尽量减少过期 Key 占用内存的问题。
05 为什么要用 Redis⭐️⭐⭐
Redis 作为独立的集中式缓存,可以被多个应用节点共享,避免本地缓存的数据不一致和容量受 JVM 内存限制等问题。
此外,Redis 提供了丰富的数据结构和原子操作,除了缓存之外,还可以用于实现分布式锁、消息队列、排行榜、计数器和限流等功能。
06 Redis 的 module 是什么?(了解即可)
Redis Module(Redis 模块) 是 Redis 4.0 版本引入的一中“拓展 | 插件”机制。它允许开发者使用 C、C++ 或 Rust 等语言编写自定义插件,直接集成到 Redis 核心中。
- RedisSearch(全文搜索)
- RedisJSON(原生 JSON 支持)
- RedisTimeSeries(时序数据库)
07 为什么 Redis 操作是原子的?⭐️⭐️⭐️
Redis 的命令执行由主线程串行处理,单条命令在执行过程中不会被其他命令打断,因此具有原子性。多条普通命令组合执行时,命令之间可能被其他客户端的命令插入。如果需要保证多条命令连续执行,可以使用 MULTI/EXEC 事务;如果需要将判断和修改等多步逻辑作为一个整体执行,通常使用 Lua 脚本。
08 Redis Cluster 的哈希槽是什么? ⭐⭐⭐
为了实现数据的分布式存储,Redis Cluster 没有使用传统的“一致性哈希”,而是引入了哈希槽的概念。它将整个数据空间划分为 16384 个哈希槽,Key 通过 CRC16(key) % 16384 计算所属槽位,再由负责该槽位的主节点存储和处理。
09 Redis 集群有哪些方案?⭐⭐
- 主从复制:一个主节点和多个从节点,支持数据复制和读写分离,但主节点故障时不能自动切换。
- 哨兵模式:在主从复制的基础上实现故障检测和自动主从切换,主要用于高可用。
- Redis Cluster:通过多个主节点分片存储数据,本质上就是多套主从复制。
二、Redis 应用
01 Redis 除了做缓存,还能做什么?⭐⭐⭐
Redis 还可以利用不同数据结构实现分布式锁、计数器、排行榜、限流、消息队列和延时任务等功能,例如使用 ZSet 实现排行榜和延时任务,使用 INCR 实现计数器。
02. 如何基于 Redis 实现分布式锁 ⭐️⭐⭐
使用 set nx 命令,注意当前 lock_key 对应的 value 是唯一的,这样我们才能判断当前锁属于哪个线程,避免后续锁的误删。
SET lock_key unique_value NX PX 30000
````
只有命令返回成功时才表示**加锁成功**;释放锁时必须使用 **Lua** 脚本,先判断 Value 是否属于**当前线程**,再删除锁,避免误删其他线程的锁。
>生产环境通常使用 Redisson,它提供了可重入锁、自动续期等能力。
#### 03 为什么释放锁的时候要用 lua 脚本 ⭐️⭐⭐
释放锁需要先判断锁的**持有者**,再删除锁,这两个操作必须作为一个整体执行,否则锁可能在判断后被**其他线程获取**,导致误删其他线程的锁。
#### 04 Redis 可以做消息队列吗?
可以。**List** 适合实现简单的阻塞队列,**Pub/Sub** 适合实时广播但不保证**消息可靠投递**,Stream 支持**消息持久化**、**消费者组**和 **ACK**,功能更完整;对可靠性要求较高的场景仍应优先使用 Kafka、RabbitMQ 等专业消息队列。
#### 05 如何基于 Redis 实现延时任务?⭐⭐
如果面试官问“Redis 怎么做延时任务”,你先答 **Zset** 的实现原理,再提 **Redisson** 的成熟方案。最后如果他问“过期监听行不行”,你一定要指出其**消息丢失**和**延迟不准**的致命缺点,这样能体现你是有实战经验的。
1、基于 Zset(最推荐,最通用)⭐
原理:利用 Zset 的 `score` 属性。我们将任务的**执行时间戳**作为 `score`。
- **生产者**:使用 `ZADD` 将任务加入 Zset,`score` 设为 `当前时间 + 延迟时间`。
- **消费者**:轮询 Zset,使用 `ZRANGEBYSCORE` 获取 `score` 小于等于 `当前时间` 的任务。
- **核心操作**:拿到任务后,必须用 `ZREM` 尝试删除任务。如果删除成功,说明抢到了任务,开始执行。
2、生产环境也可以使用 Redisson 提供的延时队列。
``` java
// 1. 获取延时队列
RBlockingQueue<String> blockingQueue = redisson.getBlockingQueue("my_queue");
RDelayedQueue<String> delayedQueue = redisson.getDelayedQueue(blockingQueue);
// 2. 发送延时任务(10秒后执行)
delayedQueue.offer("task_data", 10, TimeUnit.SECONDS);
// 3. 消费者另起一个线程消费(类似监听器)
new Thread(() -> {
while (true) {
try {
// take() 是阻塞的,有任务才会醒来,不费 CPU
String task = blockingQueue.take();
System.out.println("执行任务: " + task);
} catch (InterruptedException e) { }
}
}).start();
3、Redis 过期监听(最不推荐,有坑)❌
开启 Redis 的 Keyspace Notifications,当某个 Key 过期时,Redis 会发布一个事件,应用监听这个事件。
不建议使用 Keyspace Notifications 实现核心延时任务,因为过期事件可能延迟或丢失。
三、Redis 数据类型
01 Redis 的数据类型有哪些?⭐️⭐⭐
Redis 的数据类型可以分为 5 种基本类型和 4 种特殊类型
- 基本类型有:
String,Hash,List,Set,Zset - 高级类型有:
Bitmaps,Hyperlogs,Geospatial,Streams
02 String ⭐⭐⭐
String 是 Redis 最基础的类型,它是二进制安全的,意味着它不仅能存文本,还能存任何二进制数据(如图片、序列化后的对象等)。
它本质上就是一个 c 的结构体,里面有一个字符数组和一个变量来维护长度,从而实现了二进制安全,因为它不再需要 \0 来判断是否是字符串结尾了。
03 String 还是 Hash 存储对象更好?⭐⭐
首先,String 存储对象是将整个对象看成一个 Json,优点就是读取快,一次 get 请求就能拿走对象的全部数据。但缺点就是对对象的属性操作不方便。如果你只想修改 age,必须把整个字符串拿出来,反序列化,改完再序列化塞回去。在高并发下容易覆盖别人的修改。
而 Hash 则将对象的字段分开存储,优点就是灵活性高,我们可以轻松的操作对象的任意属性,其次 Hash 类型在数据较少时使用 ziplist(压缩列表) 编码,因此占用内存更小!但缺点就是结构相对 String 来说比较复杂。
04 String 底层实现是什么? ⭐️⭐⭐
String 底层使用 SDS,本质就是 C 语言的结构体,而不是普通的 C 字符串。SDS 通过单独的长度字段记录字符串长度,因此支持二进制数据,并且可以在 O(1) 时间内获取长度,同时通过空间预分配减少内存重分配次数。
- len(长度):已使用的字节数。获取字符串长度的时间复杂度为 \(O(1)\)。
- alloc(分配总量):总共分配了多少空间。
- flags(标志):标记 SDS 的类型(Redis 会根据内容长度选择不同的 header 以节省空间)。
- buf[](字节数组):实际存储数据的区域,最后依然保留了
\0以兼容 C 语言部分库函数。
05 List、Set 和 ZSet 分别适合什么场景?⭐
List:有序、允许重复,适合列表和简单消息队列。Set:无序、自动去重,适合点赞、标签、共同关注和抽奖。ZSet:根据 Score 排序,适合排行榜和延时任务。
06 List 的底层结构⭐️⭐⭐
Redis 7 中,List 主要使用 QuickList 实现,QuickList 由双向链表和多个 ListPack 组成,在读写效率和内存占用之间取得平衡。
07 Zset 的底层结构?⭐️⭐⭐
当数据量较小时,ZSet 使用 ListPack 进行紧凑存储;数据量较大时,使用 Dict 和 SkipList 组合存储:Dict 用于根据成员快速查询分数,SkipList 用于排序、排名和范围查询。
08 为什么 ZSet 使用跳表?⭐⭐⭐
跳表能够以较简单的结构实现接近 O(logN) 的增删查操作,并且通过底层有序链表高效支持范围查询,比较适合 Redis 的内存数据库场景。
09 购物车信息是用 String 还是 Hash 呢?⭐️
由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储。
最简单的情景:在 Redis 中,我们通常以 用户 ID 作为 Redis 的 key,以 商品 ID 作为 Hash 里的 field,以 商品数量 作为 value。
- 用户添加商品就是往 Hash 里面增加新的 field 与 value;
- 查询购物车信息就是遍历对应的 Hash;
- 更改商品数量直接修改对应的 value 值(直接 set 或者做运算皆可);
- 删除商品就是删除 Hash 中对应的 field;
- 清空购物车直接删除对应的 key 即可。
你会发现,上面的 Hash 只存了数量。那商品的标题、图片等详情存哪?这里有两种主流做法:
1、二次查询 - 推荐 2、序列化存入 Hash(适合小规模)
10 Set 的底层结构 ⭐️⭐⭐
Set 的设计同样遵循 Redis “看人下菜碟”的原则:
-
整数集合 (Intset):如果集合里存的全是整数,且数量较少,Redis 会使用一块连续的内存来存储,极其节省空间。
-
哈希表 (Dict/Hashtable):一旦存入了字符串,或者元素数量变多,Redis 就会将其转为 Dict。在 Dict 中,
member存为 Key,而 Value 统一指向NULL(就像 Java 里的HashSet底层是HashMap一样)。
11 使用Redis实现一个排行榜怎么做呢?⭐️
实现排行榜是 Redis 非常擅长的,这主要归功于它提供的一种特殊数据结构:Zset(Sorted Set,有序集合)。在有序集合中,每个 Member 都会关联一个 Score,Redis 会自动的按照 Score 对 Member 进行排序
eg:实现“当天步数排行榜”,核心思路是利用 Redis 的 Zset(有序集合)。每一天使用一个独立的 Key,将用户 ID 作为 member,步数作为 score。
# 1. 用户 001 走了 5000 步
ZINCRBY rank:steps:20260111 5000 "user:001"
# 2. 用户 002 走了 8000 步
ZINCRBY rank:steps:20260111 8000 "user:002"
# 3. 用户 001 后来又走了 4000 步
ZINCRBY rank:steps:20260111 4000 "user:001"
# 4. 查看排行榜前三名(带分数)
ZREVRANGE rank:steps:20260111 0 2 WITHSCORES
# 结果显示:
# 1) "user:001"
# 2) "9000"
# 3) "user:002"
# 4) "8000"
# 5. 查看用户 002 的排名(返回 1 表示排在第 2 位)
ZREVRANK rank:steps:20260111 "user:002"
11 Set 的应用场景是什么?⭐⭐⭐
利用 Set 自动去重和集合运算的特点,可以实现点赞、收藏、共同关注、标签、抽奖以及交集、并集和差集等功能。
12 Bitmap 适合什么场景?
Bitmap 使用二进制位表示状态,适合记录用户签到、活跃和连续登录等信息,常用命令包括 SETBIT、GETBIT 和 BITCOUNT。
13 HyperLogLog 适合什么场景?
HyperLogLog 用于统计海量数据的基数,例如网站 UV。它占用内存很小,但只能得到近似结果,不能查询具体的元素。
14 如果我想判断一个元素是否不在海量元素集合中,用什么数据类型?
需要精确判断时使用 Set;允许少量误判且追求低内存占用时使用布隆过滤器。Bitmap 只适合元素能够映射为整数偏移量的场景。
四、Redis 持久化机制⭐⭐⭐
Redis 持久化用于将内存中的数据保存到磁盘,保证 Redis 重启或故障恢复后能够恢复数据。常见方式有 RDB、AOF,以及 RDB 与 AOF 的混合持久化。
01 什么是 RDB 持久化?⭐⭐
RDB 是在指定的时间间隔内,将内存中的全量数据生成一个二进制快照(Snapshot)保存到磁盘。恢复速度快,但会丢失最后一次快照后的数据。
02 创建 RDB 快照会阻塞 Redis 吗?⭐
SAVE:由主进程同步生成快照,会阻塞 Redis 处理其他请求。BGSAVE:通过fork创建子进程生成快照,主进程仍可处理请求,但fork瞬间可能产生短暂阻塞。
03 什么是 AOF 持久化?⭐⭐
AOF 以追加日志的方式记录 Redis 执行过的写命令,Redis 重启或者故障恢复时重新执行这些命令恢复数据。AOF 通常比 RDB 更安全,但文件体积更大,恢复速度也相对较慢。
04. AOF 的刷盘策略有哪些?⭐⭐⭐
AOF 写入磁盘大致经过以下过程:
- Redis 执行写命令后,将命令追加到 AOF 缓冲区。
- Redis 调用
write,将缓冲区内容写入操作系统的内核缓冲区。 - Redis 根据
appendfsync策略调用fsync,将内核缓冲区的数据真正刷入磁盘。
appendfsync 主要有三种策略:
always:每次写命令都执行fsync,数据安全性最高,但性能开销最大。everysec:每秒执行一次fsync,性能和安全性比较均衡,宕机时通常最多丢失约 1 秒数据。no:不主动调用fsync,由操作系统决定刷盘时机,性能最好,但数据丢失时间不可控。
因此,appendfsync 控制的是:Redis 将数据写入操作系统内核缓冲区后,多久调用一次 fsync 将数据真正刷入磁盘。
05 什么是 AOF 重写?⭐
AOF 重写会根据 Redis 当前内存中的最终数据,生成一份更精简的 AOF 文件,从而删除历史冗余命令、缩小文件体积。重写通常在后台子进程中执行,但 fork 和最后的文件替换阶段可能产生短暂阻塞。
06 RDB 和 AOF 有什么区别?⭐⭐
| 对比项 | RDB | AOF |
|---|---|---|
| 存储内容 | 某一时刻的内存快照 | Redis 执行过的写命令 |
| 文件大小 | 较小 | 较大 |
| 恢复速度 | 较快 | 较慢 |
| 数据安全性 | 可能丢失两次快照之间的数据 | 通常更安全,取决于刷盘策略 |
| 对性能影响 | 适合定期生成快照 | 需要持续记录写命令 |
07 什么是混合持久化?⭐⭐⭐
混合持久化是在 AOF 重写时,先将当前内存全量数据以 RDB 格式写入文件,再将重写期间产生的写命令以 AOF 格式追加到文件末尾,从而兼顾较快的恢复速度和较好的数据安全性。
五、Redis 线程模型⭐⭐
Redis 的命令执行仍然由主线程串行完成,网络 I/O、异步删除、AOF 刷盘等部分可以使用其他线程或后台线程处理。因此,Redis 是“命令执行单线程”,而不是整个 Redis 完全只有一个线程。
文件事件处理器(file event handler)主要是包含 4 个部分:
- 多个 socket(客户端连接)
- IO 多路复用程序(支持多个客户端连接的关键)
- 文件事件分派器(将 socket 关联到相应的事件处理器)
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
01 Redis 为什么采用单线程执行命令?⭐⭐⭐
Redis 的命令大多是内存操作,性能瓶颈通常在网络和内存,而不是 CPU。单线程执行命令可以避免线程切换和并发竞争,同时保证单条命令执行的原子性。
02 Redis 4.0 引入了哪些后台线程?⭐
Redis 4.0 引入了异步删除机制,可以将大对象的内存释放交给后台线程处理,避免删除大 Key 长时间阻塞主线程,常见命令包括:
UNLINKFLUSHDB ASYNCFLUSHALL ASYNC
03 Redis 6.0 为什么引入多线程?⭐⭐
Redis 6.0 引入多线程,主要是为了让其他线程帮助主线程完成网络数据的接收和发送,减少主线程处理网络通信的压力;但 Redis 命令仍由主线程串行执行。
04 Redis 中还有哪些后台线程?⭐
Redis 还会使用后台线程执行 AOF 刷盘、异步释放内存和清理临时文件等耗时操作,避免这些操作阻塞主线程。
六、Redis 内存管理
01 Redis 为什么要给 Key 设置过期时间?⭐⭐⭐
设置过期时间可以自动清理过期 Key、节省内存,并满足验证码、登录状态、缓存数据等具有时效性的业务需求。
127.0.0.1:6379> expire key 60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56
注意 ⚠️:Redis 中除了字符串类型有自己独有设置过期时间的命令 setex 外,其他方法都需要依靠 expire 命令来设置过期时间 。另外,persist 命令可以移除一个键的过期时间。
02 Redis 如何判断数据是否过期呢?⭐️
Redis 使用过期字典记录 Key 的过期时间。当访问一个 Key 时,Redis 会检查它是否过期;如果已过期,就删除该 Key 并返回空结果。
03 Redis 过期 Key 的删除策略 ⭐️⭐⭐
Redis 采用惰性删除和定期删除相结合的策略:
- 惰性删除:访问 Key 时检查是否过期,过期后立即删除。
- 定期删除:Redis 定期随机检查设置了过期时间的 Key,并删除其中已过期的 Key。
04 Redis 内存淘汰策略 ⭐️⭐⭐
Redis 的内存淘汰策略只有在运行内存达到了配置的最大内存阈值时才会触发,这个阈值是通过 redis.conf 的 maxmemory 参数来定义的。64 位操作系统下,maxmemory 默认为 0,表示不限制内存大小。32 位操作系统下,默认的最大内存值是 3GB。
- volatile-lru(least recently used):从已设置过期时间的数据集(
server.db[i].expires)中挑选最近最少使用的数据淘汰。 - volatile-ttl:从已设置过期时间的数据集(
server.db[i].expires)中挑选将要过期的数据淘汰。 - volatile-random:从已设置过期时间的数据集(
server.db[i].expires)中任意选择数据淘汰。 - allkeys-lru(least recently used):从数据集(
server.db[i].dict)中移除最近最少使用的数据淘汰。 - allkeys-random:从数据集(
server.db[i].dict)中任意选择数据淘汰。 - no-eviction(默认内存淘汰策略):禁止驱逐数据,当内存不足以容纳新写入数据时,新写入操作会报错。
4.0 版本后增加以下两种:
- volatile-lfu(least frequently used):从已设置过期时间的数据集(
server.db[i].expires)中挑选最不经常使用的数据淘汰。 - allkeys-lfu(least frequently used):从数据集(
server.db[i].dict)中移除最不经常使用的数据淘汰。
七、Redis 事务
Redis 可以通过 MULTI、EXEC、DISCARD 和 WATCH 等命令来实现事务(Transaction)功能。
01 什么是 Redis 事务?⭐️⭐️⭐️
Redis 的事务就是一组 redis 命令的集合,但 Redis 的事务不是原子的,只是这些命令会被一次顺序执行,途中不会插入其他命令。
02 如何使用 Redis 事务?
MULTI:开启事务,后续命令进入队列。EXEC:执行队列中的所有命令。DISCARD:清空命令队列并取消事务。WATCH:监视 Key,在执行EXEC前如果被其他客户端修改,事务会执行失败。
03 Redis 事务执行出错会回滚吗?⭐⭐
- 如果命令入队时发生语法错误,事务通常会被拒绝执行。
- 如果命令执行时发生错误,只有当前命令执行失败,其他命令仍会继续执行。
- 已经执行成功的命令不会被撤销。
04 为什么 Redis 事务不支持回滚?
Redis 认为命令执行错误通常是程序问题,支持回滚会增加实现复杂度、内存开销和执行延迟,因此选择保持简单高效。
05 Redis 事务和 Lua 脚本有什么区别?⭐⭐
Redis 事务适合将多个命令按顺序连续执行;Lua 脚本适合包含条件判断和复杂业务逻辑的原子操作。
Lua 脚本在执行期间不会被其他命令插入,并且只需要一次网络请求,但脚本执行期间会阻塞 Redis 处理其他命令,因此不能编写耗时过长的脚本。
06 Lua 脚本支持回滚吗?⭐⭐⭐
不支持。如果 Lua 脚本执行到一半发生错误,已经执行的修改不会自动撤销。需要在写入数据前完成所有校验,确保脚本执行过程中不会发生错误。
八、Redis 性能优化
01 如何通过批量操作提高 Redis 性能?⭐
批量操作可以减少客户端与 Redis 之间的网络往返次数,从而降低网络开销。常见方式有:
- 使用
MGET、MSET等批量命令。 - 使用 Pipeline 一次发送多条命令。
- 使用 Lua 脚本将多步逻辑放到 Redis 内部执行。
批量命令通常只适用于同一种数据结构;Pipeline 可以批量执行不同命令,但不保证事务原子性。
02 什么是 Redis BigKey?⭐
BigKey 是指占用内存过大或包含大量元素的 Key,例如超大的 String、Hash、List、Set 或 ZSet。
BigKey 可能导致命令执行时间过长、网络流量增加、删除阻塞 Redis,甚至影响主从同步和故障转移。
03 如何发现和处理 BigKey?⭐
可以使用 redis-cli --bigkeys、MEMORY USAGE 或离线分析 RDB 文件发现 BigKey。
处理方式包括:
- 拆分大对象,避免单个 Key 保存过多数据。
- 使用
UNLINK代替DEL,异步释放大 Key 占用的内存。 - 避免一次性读取或删除大量数据,使用分批处理。
- 合理开启 Lazy Free,减少过期删除和内存淘汰对主线程的影响。
04 什么是 Redis HotKey?⭐
HotKey 是短时间内被大量访问的 Key。在 Redis Cluster 中,一个 Key 只会分配到一个节点,HotKey 可能导致该节点负载过高,而其他节点负载较低。
- 使用本地缓存作为二级缓存。
- 将热点 Key 拆分为多个 Key,分散访问压力。
05 什么是 Redis 慢查询?⭐
Redis 慢查询是指命令执行时间超过指定阈值的操作。可以通过 slowlog-log-slower-than 配置阈值,通过 SLOWLOG GET 查看慢查询记录。
需要注意,慢查询只统计命令在 Redis 内部的执行时间,不包括网络传输和客户端等待时间。
06 哪些操作容易阻塞 Redis?⭐⭐
以下操作可能长时间占用 Redis 主线程:
KEYS *、SMEMBERS、HGETALL等处理大量数据的命令。- 一次性读取、删除或遍历大 Key。
SAVE、FLUSHALL、FLUSHDB等同步操作。- 大量数据过期、内存淘汰或集群迁移。
- 执行耗时过长的 Lua 脚本。
优化时应使用 SCAN 等渐进式命令,避免对大 Key 进行一次性操作,并使用 BGSAVE、UNLINK 等非阻塞方式。
九、Redis 生产问题⭐️⭐️⭐️
01 什么是缓存穿透
是指查询一个不存在的数据,由于没有经过特殊处理,所以缓存永远无法命中,那么每一次访问不存在的数据,都会打到数据库,如果有很多请求都访问不存在的数据,就可能导致数据库挂掉。
- 空结果也进行缓存,但它的过期时间会很短,最长不超过五分钟,但是不能防止随机穿透。
- 使用 布隆过滤器 或者 Redis 的 Bitmap 来解决随机穿透问题
02 什么是缓存击穿
对于将要被超高并发访问的 key,在即将被访问前,缓存过期了,那么可能导致大量请求打到数据库,导致数据库挂掉,解决方式就是分布式锁
03 什么是缓存雪崩
是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
- 在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
04 分布式锁的实现方式
- 基于数据库实现分布式锁
- 基于缓存
- 基于 zookeeper
05 缓存预热如何实现?
缓存预热(Cache Pre-warming) 是指在系统上线或高流量到来之前,提前将可能被 频繁访问的热点数据加载到 Redis 缓存中。
- 使用定时任务,比如 xxl-job,来定时触发缓存预热的逻辑,将数据库中的热点数据查询出来并存入缓存中。
- 使用消息队列,比如 Kafka,来异步地进行缓存预热,将数据库中的热点数据的主键或者 ID 发送到消息队列中,然后由缓存服务消费消息队列中的数据,根据主键或者 ID 查询数据库并更新缓存。
06 如何保证缓存和数据库的数据一致性?
- 先更新数据库,再更新缓存 (不推荐)
- 先删除缓存,再更新数据库 (不推荐)
- 延迟双删
- 监听 bin log,canal,每次监听到,就手动删除缓存