本文最后更新于:5 天前

常见考点

image.png|500

  1. 缓存
    1. 穿透、击穿、雪崩
    2. 双写一致、持久化
    3. 数据过期、淘汰策略
  2. 分布式锁
    1. setnx、redisson
  3. 计数器
  4. 保存 token:常用 String 类型
  5. 消息队列:常用 list 类型
  6. 延迟队列:常用 zset 类型
  7. 集群:
    1. 主从
    2. 哨兵
    3. 集群
  8. 事务
  9. 单线程但很快的原因

缓存穿透

概念:查询一个不存在的数据,mysql 查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库
解决方案:
1. 缓存空数据:查询返回的数据为空,仍把这个空结果进行缓存
1. 优点:简单
2. 缺点:消耗内存,可能会发生不一致的问题(新增数据存在旧缓存)
2. 布隆过滤器:放在缓存层之前,利用位图快速判断元素是否存在(基于 Redisson 或 Guava 实现,缓存预热同时预热过滤器)
1. 优点:内存占用较少,无多余 key
2. 缺点:存在交叉哈希碰撞导致误判(过滤器数组大小决定误判概率,一般误判率控制在 5%);数组较大消耗内存
image.png

缓存击穿

概念:给某一个 key 设置了过期时间,当 key 过期的时候,恰好这时间点对这个 key 有大量的并发请求过来,这些并发的请求可能会瞬间把 DB 压垮
解决方案:
1. 互斥锁:加锁使首个请求更新缓存,迫使其他并发请求等待缓存更新
1. 优点:数据强一致性
2. 缺点:性能较差
2. 逻辑过期:value 中添加逻辑过期字段,并发请求检测到逻辑过期则异步更新缓存,但直接返回旧值,保证后续请求取缓存
1. 优点:高可用、性能优
2. 缺点:不保证数据绝对一致
image.png

缓存雪崩

概念:同一时段大量的缓存 key 同时失效或者 Redis 服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
1. 不同 key 设置不同 TTL:给不同的 Key 的 TTL 添加随机值
2. 构建高可用 Redis 集群:搭建哨兵模式、集群模式
3. 添加降级限流策略:给缓存业务添加降级限流策略,降级作为保底策略,适用于穿透、击穿、雪崩。
4. 添加多级缓存:添加 Guava 或 Caffeine 作为一级缓存

速记顺口溜
穿透无中生有 key,布隆过滤 null 隔离。
缓存击穿过期 key,锁与非期解难题。
雪崩大量过期 key,过期时间要随机。
面试必考三兄弟,可用限流来保底。

双写一致性

当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致

根据业务需要,选择合适的方案。

延迟双删(最终一致性)

读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间
写操作:延迟双删(双删为了较大程度上控制了脏数据的风险,延迟是为了等待数据库主从同步,保证最终一致性)
image.png
仅删除一次缓存,始终存在脏数据问题。
image.png

强一致性

Redisson 分布式锁/读写锁:共享读,独占写保证数据强一致性,但性能差(Java 对应类 RReadWriteLock
image.png
共享锁:读锁 readLock,加锁之后,其他线程可以共享读操作
排他锁:也叫独占锁 writeLock,加锁之后,阻塞其他线程读写操作(底层使用 setnx,保证同时只能有一个线程操作加锁的方法)

允许延迟一致

异步通知保证数据的最终一致性

  • 消息队列:MQ 发送写数据通知,Redis 监听消息同步更新。

image.png

  • Canal 中间件:基于 Canal 的异步通知,非侵入式,伪装为 MySQL 的一个从节点,监听 binlog 通知数据变更情况更新缓存。

二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言) 语句,但不包括数据查询(SELECT、SHOW)语句。

image.png

持久化

RDB

RDB 全称 Redis Database Backup file(Redis 数据备份文件),也被叫做 Redis 数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当 Redis 实例故障重启后,从磁盘读取快照文件,恢复数据。

  • 主动备份
1
2
3
4
5
[root@localhost ~]# redis-cli
127.0.0.1:6379> save #由Redis主进程来执行RDB,会阻塞所有命令
ok
127.0.0.1:6379> bgsave #开启子进程执行RDB,避免主进程受到影响
Background saving started
  • 定时备份(配置文件配置备份频率)
1
2
3
4
#900秒内,如果至少有1个key被修改,则执行bgsave
save 900 1
save 300 10
save 60 10000

执行原理
bgsave 开始时会 fork 主进程得到子进程,子进程共享主进程的内存数据。完成 fork 后读取内存数据并写入 RDB 文件。
fork 采用的是 copy-on-write 技术:

  1. 当主进程执行读操作时,访问共享内存
  2. 当主进程执行写操作时,则会拷贝一份数据,执行写操作

image.png|775

AOF

AOF 全称为 Append Only File(追加文件)。Redis 处理的每一个写命令都会记录在 AOF 文件,可以看做是命令日志文件

开启方式:AOF 默认是关闭的,需要修改 redis.conf 配置文件来开启 AOF

1
2
3
4
#是否开启AOF功能,默认是no
appendonly yes
#AOF文件的名称
appendfilename "appendonly.aof"

AOF 的命令记录的频率也可以通过 redis.conf 文件来配:常用每秒刷盘

1
2
3
4
5
6
#表示每执行一次写命令,立即记录到AOF文件
appendfsync always
#写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
#写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no

image.png

因为是记录命令,AOF 文件会比 RDB 文件大的多。而且 AOF 会记录对同一个 key 的多次写操作,但只有最后一次写操作才有意义。通过执行 bgrewriteaof 命令,可以让 AOF 文件执行重写功能,用最少的命令达到相同效果。

ff3975f2-d211-48b0-be6a-e73805f28a9e.png

Redis 也会在触发阀值时自动去重写 AOF 文件。國值也可以在 redis.conf 中配置:

1
2
3
4
#AOF文件比上次文件增长大小超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
#AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb

RDB 与 AOF 对比

RDB 和 AOF 各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。

image.png

数据过期策略

概念:Redis 对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉。可以按照不同的规则进行删除,这种删除规则就被称之为数据的删除策略(数据过期策略)。

Redis 数据删除策略-惰性删除

惰性删除:设置该 key 过期时间后,我们不去管它,当需要该 key 时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该 key
例子

1
2
set name zhangsan 10
get name //发现name过期了,直接删除key
  • 优点:对 CPU 友好,只会在使用该 key 时才会进行过期检查,对于很多用不到的 key 不用浪费时间进行过期检查
  • 缺点:对内存不友好,如果一个 key 已经过期,但是一直没有使用,那么该 key 就会一直存在内存中,内存永远不会释放

Redis 数据删除策略-定期删除

定期删除:每隔一段时间,我们就对一些 key 进行检查,删除里面过期的 key(从一定数量的数据库中取出一定数量的随机 key 进行检查,并删除其中的过期 key)。
定期清理有两种模式:

  • SLOW 模式是定时任务,执行频率默认为 10hz,每次不超过 25ms,以通过修改配置文件 redis.conf 的 hz 选项来调整这个次数
  • FAST 模式执行频率不固定,但两次间隔不低于 2ms,每次耗时不超过 1ms

优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。

缺点:难以确定删除操作执行的时长和频率。

Redis 的过期删除策略:情性删除+定期删除 两种策略进行配合使用

数据淘汰策略

概念:当 Redis 中的内存不够用时,此时在向 Redis 中添加新的 key,那么 Redis 就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。

Redis 支持 8 种不同策略来选择要删除的 key:

  • noeviction:不淘汰任何 key,但是内存满时不允许写入新数据,默认就是这种策略
  • volatile-ttl: 对设置了 TTL 的 key,比较 key 的剩余 TTL 值,TTL 越小越先被淘汰。
  • allkeys-random:对全体 key,随机进行淘汰。
  • volatile-random:对设置了 TTL 的 key,随机进行淘汰。
  • allkeys-lru:对全体 key,基于 LRU 算法进行淘汰。
  • volatile-Iru: 对设置了 TTL 的 key,基于 LRU 算法进行淘汰。
  • allkeys-lfu: 对全体 key,基于 LFU 算法进行淘汰。
  • volatile-Ifu:对设置了 TTL 的 key,基于 LFU 算法进行淘汰。

LRU(Least Recently Used) 最近最少使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
LFU(Least Frequently Used) 最少频率使用。会统计每个 key 的访问频率,值越小淘汰优先级越高。

数据淘汰策略-使用建议

  1. 优先使用 allkeys-Iru 策略。充分利用 LRU 算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。
  2. 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用allkeys-random,随机选择淘汰。
  3. 如果业务中有置顶的需求,可以使用volatile-Iru策略,同时置顶数据不设置过期时间,这些数据就一直不被删除,会淘汰其他设置过期时间的数据。
  4. 如果业务中有短时高频访问的数据,可以使用 allkeys-Ifu 或 volatile-lfu策略。

分布式锁

Redis 实现分布式锁主要利用 Redis 的 setnx 命令。setnx 是 SET if not exists(如果不存在,则 SET)的简写。
使用场景:定时任务、抢卷、幂等(涉及到共享数据的一切分布式/多线程操作)

1
2
3
4
5
6
获取锁:
#添加锁,NX是互斥,EX是设置超时时间,一条命令保证原子性
SET lock value NX EX 10
释放锁:
#释放锁,删除即可
DEL key

image.png

Redisson 实现的分布式锁-执行流程

image.png

  • 看门狗对持有锁的线程进行以 1/3 过期时间的周期进行续期
  • 未持有锁的线程重试等待获取锁
  • 锁的获取、设置过期时间、释放等都基于 lua 脚本,保证执行的原子性

Redisson 实现的分布式锁-可重入

image.png|675

redisson 分布式锁可重入,redis 锁不可重入,基于线程 id 做标识。

Redisson 实现的分布式锁-主从一致性

RedLock(红锁):不能只在一个 redis 实例上创建锁,应该是在多个 redis 实例上创建锁 (n / 2 + 1) (一半节点),避免在一个 redis 实例上加锁。

实际很少使用红锁,缺点比较多,redis 重在 AP 思想数据最终一致性,zookeeper 的 CP 思想强调强一致性。

image.png

集群方案

在 Redis 中提供的集群方案总共有三种

  • 主从复制
  • 哨兵模式
  • 分片集群

主从复制

单节点 Redis 的并发能力是有上限的,要进一步提高 Redis 的并发能力,就需要搭建主从集群,实现读写分离。

image.png
主从数据同步原理

主从全量同步

ReplicationId:简称 replid,是数据集的标记,id 一致则说明是同一数据集。每一个 master 都有唯一的 replid,slave 则会继承 master 节点的 replid
offset:偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。slave 完成同步时也会记录当前同步的 offset。如果 slave 的 offset 小于 master 的 offset,说明 slave 数据落后于 master,需要更新。

image.png

主从增量同步(slave 重启或后期数据变化)

image.png

哨兵的作用

Redis 提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:

  • 监控:Sentinel 会不断检查您的 master 和 slave 是否按预期工作
  • 自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master。当故障实例恢复后也以新的 master 为主
  • 通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端

image.png|500

服务状态监控

Sentinel 基于心跳机制监测服务状态,每隔 1 秒向集群的每个实例发送 ping 命令:
主观下线:如果某 sentinel 节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:若超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例客观下线。quorum 值最好超过 Sentinel 实例数量的一半。

image.png|500

哨兵选主规则

  • 首先判断主与从节点断开时间长短,如超过指定值就排该从节点
  • 然后判断从节点的 slave-priority 值,越小优先级越高
  • 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大优先级越高
  • 最后是判断 slave 节点的运行 id 大小,越小优先级越高。

Redis 集群(哨兵模式)脑裂

集群脑裂是由于主节点和从节点和 sentinel 处于不同的网络分区,使得 sentinel 没有能够心跳感知到主节点,所以通过选举的方式提升了一个从节点为主,这样就存在了两个 master,就像大脑分裂了一样,这样会导致客户端还在老的主节点那里写入数据,新节点无法同步数据,当网络恢复后,sentinel 会将老的主节点降为从节点,这时再从新 master 同步数据,就会导致数据丢失

image.png|500
通过以下配置减小数据丢失:

我们可以修改redis的配置,可以设置最少的从节点数量以及缩短主从数据同步的延迟时间,达不到要求就拒绝请求就可以避免大量的数据丢失

1
2
min-replicas-to-write 1 表示最少的salve节点为1个
min-replicas-max-lag 5 表示数据复制和同步的延迟不能超过5秒

image.png|500

分片集群结构

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

  • 海量数据存储问题
  • 高并发写的问题

使用分片集群可以解决上述问题,分片集群特征:

  • 集群中有多个master,每个master保存不同数据每个master都可以有多个slave节点
  • master之间通过ping监测彼此健康状态
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

image.png|375

分片集群结构-数据读写

Redis分片集群引入了哈希槽的概念,Redis集群有16384个哈希槽,分配到不同的实例,每个key的有效部分(如果key前面有大括号,则大括号里的就是有效部分,没有大括号key就是有效部分) 通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

image.png|525

Redis 单线程-快的原因

  • Redis是纯内存操作,执行速度非常快
  • 采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
  • 使用I/O多路复用模型,非阻塞IO

I/O多路复用模型

Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型主要就是实现了高效的网络请求

  • 用户空间和内核空间
  • 常见的IO模型
    • 阻塞IO (Blocking IO)
    • 非阻塞lO (Nonblocking IO)
    • IO多路复用(IO Multiplexing)
  • Redis网络模型

用户空间和内核空间

Linux系统中一个进程使用的内存情况划分两部分:内核空间、用户空间

  • 用户空间只能执行受限的命令(Ring3),而且不能直接调用系统资源必须通过内核提供的接口来访问
  • 内核空间可以执行特权命令(RingO),调用一切系统资源
    Linux系统为了提高lO效率,会在用户空间和内核空间都加入缓冲区:
  • 写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备
  • 读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区

image.png|500

阻塞IO

image.png|500

非阻塞IO

image.png|500

IO多路复用

是指利用单个线程来同时监听多个Socket,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的l/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。

Redis网络模型

Redis通过 IO多路复用 + 事件派发 来提高网络性能,支持各种不同的多路复用实现,并且将这些实现进行封装,提供了统一的高性能事件库

使用IO多路复用结合事件的处理器来应对多个Socket请求连接应答处理器

  1. 命令回复处理器,在Redis6.0之后,为了提升更好的性能,使用了多线程来处理回复事件
  2. 命令请求处理器,在Redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程

多线程优化部分:

  • 接受请求参数转换为命令
  • 响应数据处理

image.png|500


https://alleyf.github.io/2026/02/e528cc3d1150.html
作者
alleyf
发布于
2026年2月28日
更新于
2026年3月4日
许可协议