Redis八股

Redis

数据结构

5种基本数据结构:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)

使用场景

  • 缓存(穿透、击穿、雪崩、双写一致、持久化、数据过期、淘汰策略)、
  • 分布式锁(setnx、redisson)
  • 计数器(浏览量、点赞数)
  • 排行榜等

Redis为什么快

  • Redis是基于内存
  • 执行命令和核心数据结构操作采用单线程,避免了多线程模型中的上下文切换和锁竞争问题
  • 使用I/O多路复用模型,非阻塞IO(实现高效的网络请求)

缓存穿透

定义:查询一个不存在的数据时,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查询数据库

解决方式

  1. 缓存空数据(消耗内存,可能发送不一致的问题)

  2. 布隆过滤器拦截不存在的数据(内存占用小,存在误判的风险,实现复杂)

    布隆过滤器:通过位图检索一个元素是否在一个集合中,hash冲突可能造成误判

缓存击穿

定义:某个key过期的时候,恰好有大量的并发请求在同一时间发过来,所有的请求都打到了数据库上,如果重建缓存时间太长,数据库就有可能崩溃

解决方式

  1. 互斥锁(强一致,性能差)

    第一个没查到缓存的线程获取锁去重建缓存,其他线程等待

  2. 逻辑过期(弱一致,高可用)

    添加逻辑过期字段,第一个发现过期的线程获取互斥锁,新开一个线程去重建缓存,其他线程返回旧数据

缓存雪崩

定义:同一时段大量key同时过期或Redis服务宕机,导致大量请求打到数据库

解决方式

  1. 给key设置不同的TTL
  2. 搭建集群
  3. 添加降级限流规则
  4. 添加多级缓存

双写一致性

定义:指的是mysql的数据和redis的数据保持一致

读操作:缓存命中,直接返回;未命中查询数据库,设置超时时间写入缓存

写操作:

  1. 延迟双删(弱一致,先更新数据库再删除缓存,延迟一段时间后再删一次)
  2. 加锁后写数据(强一致)
  3. 通过消息队列,数据库更新后通知redis(弱一致)

不延时双删

先删缓存再更新数据库:删除缓存后又有线程重建了缓存,造成数据不一致

先更新数据库再删缓存:此时key过期,另一个线程查到旧数据,待删除缓存后又重建缓存,造成数据不一致

持久化

RDB:Redis Database Backup file,Redis数据备份文件,把所有数据都备份到磁盘

执行原理

主进程使用页表映射内存中的地址,子进程备份数据时拷贝一份页表来进行备份;

主进程写操作时,先拷贝一份要修改数据到另一块内存,修改完数据后修改页表指向的内存地址(copy-on-write),保证备份时的数据一致性

AOF:Append Only File,追加文件,记录每一个写操作。需要去配置文件里打开此功能

比较

  • 完整性比RDB高,文件大小会比RDB大,可以通过bgrewriteaof来重写AOF以减少大小
  • rdb和aop都不能保证恢复所有数据,如果在写rdb和aop文件时出现故障,或由于压缩rdb、重写aop都有可能导致数据丢失

过期策略

惰性删除:使用时发现key过期了才删除,占内存

定期删除:每隔一段时间检查一批key,把过期的删除

Redis的过期删除策略是两者结合

淘汰策略

内存占用太多时删掉一些数据

  • noeviction:内存满时不淘汰数据,而是不允许写入数据(默认)

  • volatile-ttl:淘汰TTL小的数据

  • allkeys-random:对所有数据随机淘汰

  • volatile-random:对设置了过期时间的数据随机淘汰

  • allkeys-lru:(Least Recently Used,最近最少使用:最近最少使用的key)

  • volatile-lru

  • allkeys-lfu:(Least Frequently Used,最少频率使用:一定时间内使用频率最少的key)

  • volatile-lfu

分布式锁

使用Redis简单实现

SETNX:SET if Not eXists,如果 key 不存在的话,才会设置 key 的值

加锁SETNX lockKey uniqueValue

释放锁DEL lockKey,防止误删可以使用Lua脚本

1
2
3
4
5
6
-- 释放锁时,先比对 key 对应的 value 是否为加锁时写入的唯一值,校验通过后再删除,避免误删其他客户端持有的锁
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end

加过期自动释放锁SET lockKey uniqueValue EX 3 NX

使用Redisson实现

性质

  • 原子性:加锁、设置过期时间都是通过Lua脚本实现的

  • 看门狗机制(Watch Dog):如果操作共享资源的线程还未执行完成的话,Watch Dog会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放

  • 重试机制:在一定次数内通过while循环尝试加锁,增加分布式锁的性能

  • 可重入:根据线程id判断是否是同一线程

  • 主从一致性:Redis主从复制默认异步。如果一个线程在主节点加锁后,主节点还没有和从节点同步数据就宕机了,此时另一个线程也来加锁,就会导致同一把锁被两个线程持有;

    Redisson通过RedLock(红锁)解决:让客户端向多个相互独立的 Redis master 依次请求申请加锁,至少获得N/2 + 1个节点成功才算成功;失败要释放所有锁。RedLock实现复杂、性能较差

使用

1
2
3
4
5
6
7
8
// 1.获取指定的分布式锁对象
RLock lock = redisson.getLock("lock");
// 2.拿锁且不设置锁超时时间,具备 Watch Dog 自动续期机制
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();

集群

主从复制:搭建主从节点,实现读写分离,提高并发能力

主节点写操作,从节点读操作

主从同步

全量同步

增量同步

  • 通过replid判断主从节点数据的版本号,如果从节点没有replid表示第一次同步数据,主节点需要发送RDB文件(全量同步)
  • 如果不是第一次同步数据,则通过offset偏移量判断需要从repl_backlog中发送多少数据来更新从节点数据(增量同步)

哨兵模式:监控、故障恢复、通知

监控:心跳检测,监控主从节点是否能正常工作

故障恢复:主节点挂了选择offset大的从节点提升为主节点

通知:当集群发生故障转移时,通知Redis客户端

脑裂问题:主节点没有挂,而是由于网络延迟导致哨兵节点检测不到,从而又选了个主节点,原先的主节点强制变为从节点,而主节点里这段时间的写数据就丢失了。解决方式:设置主节点要有一定数量的从节点时才能接收写数据、设置主从同步延迟时间的上限

分片集群

解决海量数据存储和高并发写的问题

特点:

  • 集群中有多个master、每个master保存不同的数据
  • 每个master都可以有多个slave节点
  • master之间通过ping检测彼此健康状态

数据读写:

  • Redis集群有16384个哈希槽,每个key通过哈希运算后决定放到哪个槽中。
  • 每个节点负责一部分hash槽

Redis 的 I/O 多路复用模型

1. 客户端连接

Redis 服务端监听一个 TCP 端口。

客户端建立连接后,Redis 会:

  1. 接收客户端连接;
  2. 将客户端 Socket 设置为非阻塞模式;
  3. 将 Socket 注册到事件监听器中;
  4. 监听连接上的可读、可写等事件。

当某个 Socket 就绪时,Redis 才会处理对应事件。

2. 用户空间与内核空间

操作系统将内存和执行权限划分为:

  • 用户空间:应用程序运行的区域,不能直接访问硬件和关键系统资源。
  • 内核空间:操作系统内核运行的区域,可以管理 CPU、内存、网络和文件等资源。

Redis 接收网络数据时,数据通常经历:

1
网卡 → 内核缓冲区 → 用户空间中的 Redis

3. 阻塞 I/O

线程调用读取操作后,如果数据尚未到达,线程会一直等待,直到:

  1. 内核准备好数据;
  2. 数据从内核空间复制到用户空间;
  3. 读取操作返回。
1
调用 read → 等待数据 → 复制数据 → 返回

如果 Redis 主线程使用阻塞 I/O 处理某个客户端,就可能无法及时处理其他客户端。

4. 非阻塞 I/O

Socket 设置为非阻塞模式后,如果数据尚未准备好,读取操作会立即返回,不会一直等待。

应用程序需要不断轮询 Socket:

1
调用 read → 数据未就绪 → 立即返回 → 再次调用 read

这种方式虽然不会长期阻塞线程,但频繁轮询会浪费大量 CPU。

数据准备完成后,从内核空间复制到用户空间的过程通常仍需要同步完成。

5. I/O 多路复用

I/O 多路复用允许一个线程同时监听多个 Socket

线程通过 selectpollepollkqueue 等系统调用,等待多个 Socket 的事件。当某个 Socket 可读或可写时,系统通知 Redis,Redis 再处理该 Socket。

1
2
3
4
5
I/O 多路复用器统一监听多个客户端 Socket

返回已经就绪的 Socket

Redis 处理对应事件

它的核心特点是:

  • 一个线程可以管理大量客户端连接;
  • 不会阻塞在某一个客户端上;
  • 不需要不断轮询每一个 Socket;
  • 只有 Socket 就绪时才进行处理。

6. Redis 中的作用

Redis 的命令执行速度快,但网络连接数量可能非常多。

通过非阻塞 Socket + I/O 多路复用,Redis 主线程可以高效处理大量客户端连接:

1
2
3
4
5
6
7
8
9
监听多个连接

获取就绪事件

读取请求

执行命令

返回响应

因此,Redis 的高并发能力并不依赖一个客户端对应一个线程,而是通过一个事件循环管理大量连接。


Redis八股
http://xwww12.github.io/2026/07/30/八股/Redis八股/
作者
xw
发布于
2026年7月30日
许可协议