数据类型
- String(可存储字符串,整数或浮点数):计数器/分布式锁;底层实现:SDS简单动态字符串
- Hash(可存储多个键值对):缓存对象;底层:listpack
- List(可重复,插入取出有序):朋友圈点赞。评论列表;底层:QuickList快速列表:一种双向链表,每个节点是一个压缩列表
- set(不可重复):去重
- Zset(有序集合,自动按分数排序元素):排行榜
- BitMap(存储⼆进制位):布隆过滤器,用户签到记录
- Stream:消息队列
重点讲一下跳表(SkipList)
跳表本质上是一个多层链表,底层链表保存所有元素,上层链表是下层的子集。通过这种分层索引结构,把链表的 O(n) 查找优化到 O(log n)。
查找:查找的时候从最高层开始,先往右走,遇到比目标值大的就往下一层。重复这个过程,直到找到目标或者确定不存在。

插入:先用查找的方式定位到插入位置,然后随机决定新节点要建几层索引。Redis用25%的概率往上加一层,所以大部分节点只在底层。
删除:跟普通列表删除一样。
分布式锁
基于Redis的setnx命令
Setnx:如果 key 存在就返回 0,如果 key 不存在就返回 1。
实现:
第一步:加锁
使用 set unique key NX ex TTL。
A. 加锁成功:执行命令,结果返回 1。
B. 加锁失败:执行命令,结果返回 0。
C. 能实现互斥,且设置了 TTL,可以通过 TTL 到期自动删除 key,自动释放锁,防止死锁。
判断锁是否是本线程加上的,是为了防止锁误删的情况。
举例:
- 线程 1 业务阻塞,锁的 TTL 到期,Redis key 自动删除,则锁释放。
- 线程 2 获取到了锁。在线程 2 正常执行时,线程 1 恢复运行。
- 如果线程 1 不判断当前锁是否是自己加上的,直接把锁删除了,就出现了并发问题。
第二步:释放锁。执行 delete 删除 key 的命令。
A 先 get key 拿到 value,判断当前锁是否是本机器本线程加上的:
- 如果是,正常删除;
- 如果不是,则不需要处理。
B 用 Lua 脚本来完成"判断是否是当前线程的锁"和"删除 key 释放锁"这两个操作,保证原子性。
判断锁归属和删除 key 这两个操作应是原子操作。
举例: 当线程 1 判断完是自己的锁,准备去删除时,因为某个原因导致线程 1阻塞,Redis 锁的 TTL 到期,锁被迫释放。随后线程 2 获得了锁。此时线程 1 恢复执行去删除锁,就会出现误删。
问题:
- 由于业务线程执行耗时太长,TTL 到期锁被迫释放,存在多个线程并行的情况。
- setnx 线程不可重入。
- TTL 不好设置,业务到底要执行多长时间不确定。
基于Redisson
相较于setnx命令实现的分布式锁更加强大和完善。
Redisson可重入锁原理
可重入锁,即一个线程可以多次获取锁,利用哈希结构记录获取锁的线程和获取锁的次数。
- 获取锁的逻辑 A:
判断 key 是否存在,即判断是否有线程持有锁:
(a) 若不存在:代表没有线程持有锁,则获取锁,即设置一个哈希结构,field 是机器码加线程,value 是次数 1。
(b) 若存在:代表有线程持有锁,此时判断持有锁的线程是否是当前线程。如果是当前线程,则重入次数加 1。 - 释放锁的逻辑:
(a) 重入次数减 1。
(b) 如果重入次数为 0,删除 key 释放锁。
Redisson分布式锁的数据未同步,导致线程安全
主从问题:
主节点宕机时,若从节点未同步 锁 数据 → 新主节点可能允许其他线程重复加锁 → 锁失效。
解决:
- 部署多个 Redis 主节点,采用多主多从架构,向多个主节点都去获取锁。
- 连锁方案:要求所有主节点加锁成功,否则立即失败并释放已获取的锁。
- 红锁方案:要求半数主节点以上加锁成功,否则立即失败并释放已获得的锁。
持久化
在Redis的默认配置文件(redis.conf)中,RDB快照是默认开启的,而AOF日志需要手动配置开启。
RDB
RDB:把内存中的所有数据都记录到磁盘文件中。当 Redis 实例故障重启后,读取磁盘快照文件恢复数据。
RDB触发机制:
手动执行命令:
- save 命令:由 Redis 主进程来执行,RDB 会阻塞所有命令。
- BGSAVE 命令:开启子进程执行 RDB,避免主进程受到影响。
save 900 1 # 900秒内,如果⾄少有1个key被修改,则执⾏ bgsave 命令 save 300 10 #
300秒内,如果⾄少有10个key被修改,则执⾏ bgsave 命令 save 60 10000 #
60秒内,如果⾄少有10000个key被修改,则执⾏ bgsave 命令
RDB异步持久化的底层原理
异步持久化(即 bgsave)就是开启一个子进程,由子进程读取内存数据并写入 RDB 文件。
BGC 对主进程几乎是 0 阻塞的,但有一个过程会阻塞主进程:那就是 BGC 刚开始时,fork 主进程得到子进程(主要是把⻚表复制给⼦进程),子进程共享主进程的内存数据。这个 fork 的过程是阻塞的,Redis 在 fork 中只能做这一件事,不能去接受用户请求。
进程在执行 bgsave 时读取共享内存的数据,主进程如果在写内存数据可能会造成冲突。因此,为了避免这个问题,底层会使用一种 copy on write 的技术:
- 内存数据会被标记为 read only。
- 当主进程执行写操作时,则会拷贝一份数据,在拷贝的数据上执行写操作。
优缺点
优点:
宕机后恢复速度快,文件体积小。
缺点:
- 数据安全有问题:因为 RDB 执行间隔时间长,两次之间写入的数据有丢失风险。但是 RDB 的间隔时间又不能设置太短,因为 RDB 的过程十分耗时,如果间隔时间短,根本忙不过来。
- fork 子进程(复制页表)、写出 RDB 文件等都比较耗时。
AOF
AOF 全称为 Append Only File(追加文件)。Redis 处理的每一个写命令都会记录在 aof 文件中,所以可以把 aof 文件看作命令日志文件。
AOF 执行的频率默认是每秒钟做一次。
- always:同步刷盘。写日志文件的操作是由主进程在写入内存数据后完成的,性能影响大。
- everysec:每秒刷盘。写内存后,先把命令放入 AOF 缓冲区,然后每隔一秒将缓冲区数据写入 AOF 文件。
- no:操作系统控制。写内存后把命令放 AOF 缓冲区,由操作系统决定何时将缓冲区内容写入 AOF 文件。
AOF文件重写
因为是记录命令,AOF 文件会比 RDB 大得多,而且 AOF 会记录对同一个 key 的多次写操作,但只有最后一次写操作才有意义。通过执行命令,可以让 AOF 文件执行重写功能,用最少的命令达到相同的效果,但这个过程会占用大量资源。
优点:
数据安全性更高,例如 everysecond 策略只会丢失一秒以内的数据。
缺点:
- 宕机后恢复速度慢,因为 AOF 文件记录的是命令,需要依次执行。
- 文件体积大,需要进行 AOF 文件重写,此时会占用大量资源。
混合持久化
Aof和RDB各有优缺,混合持久化同时拥有上述两种持久化的优点。
开启混合持久化后,当aof文件重写时:
- 将当前内存数据以RDB格式写入新aof文件的开头。
- 后续增量数据以aof格式追加到文件末尾。
混合持久化结合了 RDB 和 AOF 持久化的优点:开头为 RDB 的格式,使 Redis 可以更快地启动;同时结合 AOF 的优点,降低数据丢失风险,提高数据安全性。
内存淘汰策略
一类是针对TTL
- lru:淘汰最久未使用的key。
- LFU:淘汰访问频率最低的 key。
- Random:随机选择一个 key 删除。
- TTL:选择剩余时间最短的 key 删除。
一类是针对所有key进行淘汰:
- LRU:从所有 key 中淘汰最久未使用的 key。
- LFU:从所有 key 中淘汰访问频率最低的 key。
- Random:从所有 key 中随机删除一个 key。
淘汰流程:
- 客户端执⾏写⼊命令触发内存检查
- Redis 检查当前内存使⽤是否已超出 maxmemory(配置⽂件设置的值)
- 根据配置的策略选择待淘汰键
- 删除键并触发相关事件(如 evicted 通知)
缓存击穿/穿透/雪崩
缓存击穿
通常发生在高并发场景下,某个热点数据在缓存中过期,大量请求打到数据库,导致数据库压力过大,造成崩溃。
解决方案:
-
互斥锁思路:只允许一个线程去查询数据库并重建缓存,其他线程阻塞等待。这个方案可以避免大量请求都想重建缓存,给数据库带来压力。
-
"永不过期"+逻辑过期:缓存 key 不设置 TTL,实际上在 value 中多存储一个属性------过期时间。
缓存穿透
数据在Redis缓存和数据库中都不存在。这样缓存永远不会生效。请求直接打到数据。
解决方法:
- 缓存空值
- 布隆过滤器:插入数据时:通过 N 个哈希函数依次对 key 进行计算,得到数据 key 的特征。key 的特征就是一长串二进制串,其中那些 1 就是特征。 通过布隆过滤器判断数据是否存在时:再用 N 个哈希函数对 key 进行计算,分别判断二进制串上对应位置的 0 或 1 是否是 1。如果都是 1,则认为数据存在;否则,数据一定不存在。
缓存雪崩
大量key同时过期或缓存服务宕机,大量请求打到数据库,导致数据库瞬时压力过大甚至崩溃。
解决方案:
- 均匀设置过期时间(例如加随机数)
- Redis集群提⾼服务可⽤性
热Key问题
时间内被高频访问的key。
影响:
- 导致 Redis 实例的负载激增,可能引发性能瓶颈或服务不可用。
- 甚至在 key 过期时,可能导致缓存击穿。
解决:
-
本地缓存:使用 Java 的 Caffeine 本地缓存减少 Redis 的压力,注意设置过期时间。
-
Key 分片:将热 key 拆分成多个子 key,分散到不同的 Redis 实例上。
-
读写分离:读请求分流到多个从节点。
大Key问题
定义:⼤Key是指 Value体积过⼤(如String类型 > 2MB,Hash/List元素>5000个)的Key
影响:
- 对大 key 的操作导致主线程的操作阻塞和资源消耗过高。
- 若使用 RDB 持久化,可能内存峰值升高导致 OOM。
解决: - 数据超分哈希 list 拆分:将大 key 拆分成多个小 key。
- 异步删除命令:使用 unlink 代替 delete,由后台异步线程删除,避免影响主线程。
Redis高可用
Redis主从
主从分离,主节点负责写,从节点负责读
Redis哨兵
作用:实现主从集群的自动故障恢复。
- 监控:哨兵会不断地检查 master 和 slave 是否正常工作。
- 自动故障恢复:如果 master 故障,哨兵会将一个 slave 提升为 master,同时哨兵会去重启 master 尝试让其恢复。
- 通知:哨兵充当 Redis 客户端的服务发现来源。当集群发生故障转移时,会将新消息推送给 Redis 客户端,通知的信息包括主节点信息、从节点信息。Redis 客户端收到信息才能做读写分离。
Redis 集群

分片集群的特征:
- 集群有多个 master,每个 master 保存不同数据。
- 每个 master 都可以有多个 slave 节点,并且之间存在主从数据同步。
- 数据监测和自动恢复:集群之间通过心跳机制互相监测彼此的健康状态。主节点之间互相心跳监测;主节点定期向它的从节点发送 ping 消息,并等待 pong 响应,以确认它所管理的从节点是否正常。
- 客户端如何访问?客户端可以请求任意节点,最终都会被转发到正确节点。
数据分片-散列插槽的原理
Redis 会有 16384 个插槽,分别分配给不同的 master 节点
存入 Redis 的数据。key 是与插槽绑定的,而不是与从节点绑定。
Redis 会根据 key 的有效部分计算插槽值,以此判断 key 是和哪个插槽绑定的。
计算插槽值:使用CRC16算法根据key有效部分计算出hash值,然后对16384取余,达到slot插槽值
为什么将数据与插槽绑定来实现分布式存储
-
负载均衡:当集群伸缩时,Redis 集群将哈希槽重新分配,保证数据和负载的均衡分布。
-
快速查找:通过将数据与插槽绑定,可以计算 key 对应的插槽值,快速定位到数据所在的 Redis 节点,避免全局查询。并且即使集群伸缩或故障恢复后,都可以根据插槽值找到数据所在的新 Redis 节点。
Redis集群的优点
-
实现了数据分片:通过多 master 和散列插槽的机制,允许承载更大的数据量。
-
实现了故障检测和自动恢复:通过心跳机制监测集群节点健康状态。
-
支持集群伸缩:可新增 master 节点及其 slave 节点,可应对更高的读写需求。
总结
Redis 主从是一主多从架构
Redis 哨兵是一主多从加哨兵集群架构
Redis 集群实现的是一个多主多从架构。
主从数据同步原理与实践优化
全量同步原理
主从建立连接后,第一次数据同步即为全量同步。

流程:
- Slave 请求数据同步,Master 判断是否为第一次同步。
- Master 判断是第一次同步,返回数据版本信息。
- Master 执行 BG save 生成 RDB 文件,并且在生成文件期间,将主进程收到的所有命令记录到内存缓冲区。
- Master 向 Slave 发送 RDB 文件,Slave 清空本地数据并加载 RDB 文件。
- Master 向 Slave 发送内存缓冲区中的命令,Slave 收到命令后执行。
Master如何判断slave节点是否第一次来做数据同步?
-
Replayed 是数据集的标记。如果两个节点的 replayed 一致,则说明两个节点是同一个数据集。每一个 master 节点都有唯一的 replayed,slave 节点则会继承 master 节点的 replayed。
-
Offset 是偏移量,随着记录在内存缓冲区中的数据增多而逐渐增大。slave 完成同步时也会记录当前同步的 offset,如果 slave 的 offset < master 的 offset,说明 slave 数据落后于 master,需要更新结论。
因此,slave 做数据同步必须向 master 声明自己的 replayed 和 offset,master 才可以判断到底是全量同步还是增量同步,以及增量同步需要同步哪些数据。
使⽤ Replication_id 来判断 slave 节点是不是第⼀次来做数据同步,如果 replid ⼀样,说明不是第⼀次,如果 replid 不⼀样说明是第⼀次做数据同步。
offset量只能说明 repl_baklog 数据缓冲区中的数据同步的进度。
增量同步
增量同步:在 slave 节点断开又恢复连接主节点时,会执行增量同步。

流程:
- Slave 请求数据同步,Master 判断是否为第一次同步。
- Master 判断不是第一次同步,返回 continue。
- Master 根据 offset 向 Slave 发送内存缓冲区中的命令,Slave 收到命令并执行。