Redis八股
Redis
数据结构
5种基本数据结构:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)
使用场景
- 缓存(穿透、击穿、雪崩、双写一致、持久化、数据过期、淘汰策略)、
- 分布式锁(setnx、redisson)
- 计数器(浏览量、点赞数)
- 排行榜等
Redis为什么快:
- Redis是基于内存的
- 执行命令和核心数据结构操作采用单线程,避免了多线程模型中的上下文切换和锁竞争问题
- 使用I/O多路复用模型,非阻塞IO(实现高效的网络请求)
缓存穿透
定义:查询一个不存在的数据时,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查询数据库
解决方式:
缓存空数据(消耗内存,可能发送不一致的问题)
布隆过滤器拦截不存在的数据(内存占用小,存在误判的风险,实现复杂)
布隆过滤器:通过位图检索一个元素是否在一个集合中,hash冲突可能造成误判
缓存击穿
定义:某个key过期的时候,恰好有大量的并发请求在同一时间发过来,所有的请求都打到了数据库上,如果重建缓存时间太长,数据库就有可能崩溃
解决方式:
互斥锁(强一致,性能差)
第一个没查到缓存的线程获取锁去重建缓存,其他线程等待
逻辑过期(弱一致,高可用)
添加逻辑过期字段,第一个发现过期的线程获取互斥锁,新开一个线程去重建缓存,其他线程返回旧数据
缓存雪崩
定义:同一时段大量key同时过期或Redis服务宕机,导致大量请求打到数据库
解决方式:
- 给key设置不同的TTL
- 搭建集群
- 添加降级限流规则
- 添加多级缓存
双写一致性
定义:指的是mysql的数据和redis的数据保持一致
读操作:缓存命中,直接返回;未命中查询数据库,设置超时时间写入缓存
写操作:
- 延迟双删(弱一致,先更新数据库再删除缓存,延迟一段时间后再删一次)
- 加锁后写数据(强一致)
- 通过消息队列,数据库更新后通知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 | |
加过期自动释放锁:SET lockKey uniqueValue EX 3 NX
使用Redisson实现
性质:
原子性:加锁、设置过期时间都是通过Lua脚本实现的
看门狗机制(Watch Dog):如果操作共享资源的线程还未执行完成的话,Watch Dog会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放
重试机制:在一定次数内通过while循环尝试加锁,增加分布式锁的性能
可重入:根据线程id判断是否是同一线程

主从一致性:Redis主从复制默认异步。如果一个线程在主节点加锁后,主节点还没有和从节点同步数据就宕机了,此时另一个线程也来加锁,就会导致同一把锁被两个线程持有;
Redisson通过RedLock(红锁)解决:让客户端向多个相互独立的 Redis master 依次请求申请加锁,至少获得
N/2 + 1个节点成功才算成功;失败要释放所有锁。RedLock实现复杂、性能较差
使用:
1 | |
集群
主从复制:搭建主从节点,实现读写分离,提高并发能力

主从同步


- 通过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 会:
- 接收客户端连接;
- 将客户端 Socket 设置为非阻塞模式;
- 将 Socket 注册到事件监听器中;
- 监听连接上的可读、可写等事件。
当某个 Socket 就绪时,Redis 才会处理对应事件。
2. 用户空间与内核空间
操作系统将内存和执行权限划分为:
- 用户空间:应用程序运行的区域,不能直接访问硬件和关键系统资源。
- 内核空间:操作系统内核运行的区域,可以管理 CPU、内存、网络和文件等资源。
Redis 接收网络数据时,数据通常经历:
1 | |
3. 阻塞 I/O
线程调用读取操作后,如果数据尚未到达,线程会一直等待,直到:
- 内核准备好数据;
- 数据从内核空间复制到用户空间;
- 读取操作返回。
1 | |
如果 Redis 主线程使用阻塞 I/O 处理某个客户端,就可能无法及时处理其他客户端。
4. 非阻塞 I/O
Socket 设置为非阻塞模式后,如果数据尚未准备好,读取操作会立即返回,不会一直等待。
应用程序需要不断轮询 Socket:
1 | |
这种方式虽然不会长期阻塞线程,但频繁轮询会浪费大量 CPU。
数据准备完成后,从内核空间复制到用户空间的过程通常仍需要同步完成。
5. I/O 多路复用
I/O 多路复用允许一个线程同时监听多个 Socket。
线程通过 select、poll、epoll 或 kqueue 等系统调用,等待多个 Socket 的事件。当某个 Socket 可读或可写时,系统通知 Redis,Redis 再处理该 Socket。
1 | |
它的核心特点是:
- 一个线程可以管理大量客户端连接;
- 不会阻塞在某一个客户端上;
- 不需要不断轮询每一个 Socket;
- 只有 Socket 就绪时才进行处理。
6. Redis 中的作用
Redis 的命令执行速度快,但网络连接数量可能非常多。
通过非阻塞 Socket + I/O 多路复用,Redis 主线程可以高效处理大量客户端连接:
1 | |
因此,Redis 的高并发能力并不依赖一个客户端对应一个线程,而是通过一个事件循环管理大量连接。
