一文掌握Redis常见八股

数据类型

  • 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. 线程 1 业务阻塞,锁的 TTL 到期,Redis key 自动删除,则锁释放。
  2. 线程 2 获取到了锁。在线程 2 正常执行时,线程 1 恢复运行。
  3. 如果线程 1 不判断当前锁是否是自己加上的,直接把锁删除了,就出现了并发问题。

第二步:释放锁。执行 delete 删除 key 的命令。

A 先 get key 拿到 value,判断当前锁是否是本机器本线程加上的:

  1. 如果是,正常删除;
  2. 如果不是,则不需要处理。
    B 用 Lua 脚本来完成"判断是否是当前线程的锁"和"删除 key 释放锁"这两个操作,保证原子性。

判断锁归属和删除 key 这两个操作应是原子操作。

举例: 当线程 1 判断完是自己的锁,准备去删除时,因为某个原因导致线程 1阻塞,Redis 锁的 TTL 到期,锁被迫释放。随后线程 2 获得了锁。此时线程 1 恢复执行去删除锁,就会出现误删。

问题:

  1. 由于业务线程执行耗时太长,TTL 到期锁被迫释放,存在多个线程并行的情况。
  2. setnx 线程不可重入。
  3. TTL 不好设置,业务到底要执行多长时间不确定。

基于Redisson

相较于setnx命令实现的分布式锁更加强大和完善。

Redisson可重入锁原理

可重入锁,即一个线程可以多次获取锁,利用哈希结构记录获取锁的线程和获取锁的次数。

  1. 获取锁的逻辑 A:
    判断 key 是否存在,即判断是否有线程持有锁:
    (a) 若不存在:代表没有线程持有锁,则获取锁,即设置一个哈希结构,field 是机器码加线程,value 是次数 1。
    (b) 若存在:代表有线程持有锁,此时判断持有锁的线程是否是当前线程。如果是当前线程,则重入次数加 1。
  2. 释放锁的逻辑:
    (a) 重入次数减 1。
    (b) 如果重入次数为 0,删除 key 释放锁。

Redisson分布式锁的数据未同步,导致线程安全

主从问题:

主节点宕机时,若从节点未同步 锁 数据 → 新主节点可能允许其他线程重复加锁 → 锁失效。

解决:

  1. 部署多个 Redis 主节点,采用多主多从架构,向多个主节点都去获取锁。
  2. 连锁方案:要求所有主节点加锁成功,否则立即失败并释放已获取的锁。
  3. 红锁方案:要求半数主节点以上加锁成功,否则立即失败并释放已获得的锁。

持久化

在Redis的默认配置文件(redis.conf)中,RDB快照是默认开启的,而AOF日志需要手动配置开启。

RDB

RDB:把内存中的所有数据都记录到磁盘文件中。当 Redis 实例故障重启后,读取磁盘快照文件恢复数据。

RDB触发机制:

手动执行命令:

  1. save 命令:由 Redis 主进程来执行,RDB 会阻塞所有命令。
  2. 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 的技术:

  1. 内存数据会被标记为 read only。
  2. 当主进程执行写操作时,则会拷贝一份数据,在拷贝的数据上执行写操作。

优缺点

优点:

宕机后恢复速度快,文件体积小。

缺点:

  1. 数据安全有问题:因为 RDB 执行间隔时间长,两次之间写入的数据有丢失风险。但是 RDB 的间隔时间又不能设置太短,因为 RDB 的过程十分耗时,如果间隔时间短,根本忙不过来。
  2. fork 子进程(复制页表)、写出 RDB 文件等都比较耗时。

AOF

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

AOF 执行的频率默认是每秒钟做一次。

  1. always:同步刷盘。写日志文件的操作是由主进程在写入内存数据后完成的,性能影响大。
  2. everysec:每秒刷盘。写内存后,先把命令放入 AOF 缓冲区,然后每隔一秒将缓冲区数据写入 AOF 文件。
  3. no:操作系统控制。写内存后把命令放 AOF 缓冲区,由操作系统决定何时将缓冲区内容写入 AOF 文件。

AOF文件重写

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

优点:

数据安全性更高,例如 everysecond 策略只会丢失一秒以内的数据。

缺点:

  1. 宕机后恢复速度慢,因为 AOF 文件记录的是命令,需要依次执行。
  2. 文件体积大,需要进行 AOF 文件重写,此时会占用大量资源。

混合持久化

Aof和RDB各有优缺,混合持久化同时拥有上述两种持久化的优点。

开启混合持久化后,当aof文件重写时:

  1. 将当前内存数据以RDB格式写入新aof文件的开头。
  2. 后续增量数据以aof格式追加到文件末尾。

混合持久化结合了 RDB 和 AOF 持久化的优点:开头为 RDB 的格式,使 Redis 可以更快地启动;同时结合 AOF 的优点,降低数据丢失风险,提高数据安全性。

内存淘汰策略

一类是针对TTL

  • lru:淘汰最久未使用的key。
  • LFU:淘汰访问频率最低的 key。
  • Random:随机选择一个 key 删除。
  • TTL:选择剩余时间最短的 key 删除。
    一类是针对所有key进行淘汰:
  1. LRU:从所有 key 中淘汰最久未使用的 key。
  2. LFU:从所有 key 中淘汰访问频率最低的 key。
  3. Random:从所有 key 中随机删除一个 key。

淘汰流程:

  1. 客户端执⾏写⼊命令触发内存检查
  2. Redis 检查当前内存使⽤是否已超出 maxmemory(配置⽂件设置的值)
  3. 根据配置的策略选择待淘汰键
  4. 删除键并触发相关事件(如 evicted 通知)

缓存击穿/穿透/雪崩

缓存击穿

通常发生在高并发场景下,某个热点数据在缓存中过期,大量请求打到数据库,导致数据库压力过大,造成崩溃。

解决方案:

  1. 互斥锁思路:只允许一个线程去查询数据库并重建缓存,其他线程阻塞等待。这个方案可以避免大量请求都想重建缓存,给数据库带来压力。

  2. "永不过期"+逻辑过期:缓存 key 不设置 TTL,实际上在 value 中多存储一个属性------过期时间。

缓存穿透

数据在Redis缓存和数据库中都不存在。这样缓存永远不会生效。请求直接打到数据。

解决方法:

  • 缓存空值
  • 布隆过滤器:插入数据时:通过 N 个哈希函数依次对 key 进行计算,得到数据 key 的特征。key 的特征就是一长串二进制串,其中那些 1 就是特征。 通过布隆过滤器判断数据是否存在时:再用 N 个哈希函数对 key 进行计算,分别判断二进制串上对应位置的 0 或 1 是否是 1。如果都是 1,则认为数据存在;否则,数据一定不存在。

缓存雪崩

大量key同时过期或缓存服务宕机,大量请求打到数据库,导致数据库瞬时压力过大甚至崩溃。

解决方案:

  • 均匀设置过期时间(例如加随机数)
  • Redis集群提⾼服务可⽤性

热Key问题

时间内被高频访问的key。

影响:

  1. 导致 Redis 实例的负载激增,可能引发性能瓶颈或服务不可用。
  2. 甚至在 key 过期时,可能导致缓存击穿。

解决:

  1. 本地缓存:使用 Java 的 Caffeine 本地缓存减少 Redis 的压力,注意设置过期时间。

  2. Key 分片:将热 key 拆分成多个子 key,分散到不同的 Redis 实例上。

  3. 读写分离:读请求分流到多个从节点。

大Key问题

定义:⼤Key是指 Value体积过⼤(如String类型 > 2MB,Hash/List元素>5000个)的Key

影响:

  1. 对大 key 的操作导致主线程的操作阻塞和资源消耗过高。
  2. 若使用 RDB 持久化,可能内存峰值升高导致 OOM。
    解决:
  3. 数据超分哈希 list 拆分:将大 key 拆分成多个小 key。
  4. 异步删除命令:使用 unlink 代替 delete,由后台异步线程删除,避免影响主线程。

Redis高可用

Redis主从

主从分离,主节点负责写,从节点负责读

Redis哨兵

作用:实现主从集群的自动故障恢复。

  1. 监控:哨兵会不断地检查 master 和 slave 是否正常工作。
  2. 自动故障恢复:如果 master 故障,哨兵会将一个 slave 提升为 master,同时哨兵会去重启 master 尝试让其恢复。
  3. 通知:哨兵充当 Redis 客户端的服务发现来源。当集群发生故障转移时,会将新消息推送给 Redis 客户端,通知的信息包括主节点信息、从节点信息。Redis 客户端收到信息才能做读写分离。

Redis 集群

分片集群的特征

  1. 集群有多个 master,每个 master 保存不同数据。
  2. 每个 master 都可以有多个 slave 节点,并且之间存在主从数据同步。
  3. 数据监测和自动恢复:集群之间通过心跳机制互相监测彼此的健康状态。主节点之间互相心跳监测;主节点定期向它的从节点发送 ping 消息,并等待 pong 响应,以确认它所管理的从节点是否正常。
  4. 客户端如何访问?客户端可以请求任意节点,最终都会被转发到正确节点。

数据分片-散列插槽的原理

Redis 会有 16384 个插槽,分别分配给不同的 master 节点

存入 Redis 的数据。key 是与插槽绑定的,而不是与从节点绑定。

Redis 会根据 key 的有效部分计算插槽值,以此判断 key 是和哪个插槽绑定的。

计算插槽值:使用CRC16算法根据key有效部分计算出hash值,然后对16384取余,达到slot插槽值

为什么将数据与插槽绑定来实现分布式存储

  1. 负载均衡:当集群伸缩时,Redis 集群将哈希槽重新分配,保证数据和负载的均衡分布。

  2. 快速查找:通过将数据与插槽绑定,可以计算 key 对应的插槽值,快速定位到数据所在的 Redis 节点,避免全局查询。并且即使集群伸缩或故障恢复后,都可以根据插槽值找到数据所在的新 Redis 节点。

Redis集群的优点

  1. 实现了数据分片:通过多 master 和散列插槽的机制,允许承载更大的数据量。

  2. 实现了故障检测和自动恢复:通过心跳机制监测集群节点健康状态。

  3. 支持集群伸缩:可新增 master 节点及其 slave 节点,可应对更高的读写需求。

总结

Redis 主从是一主多从架构

Redis 哨兵是一主多从加哨兵集群架构

Redis 集群实现的是一个多主多从架构。

主从数据同步原理与实践优化

全量同步原理

主从建立连接后,第一次数据同步即为全量同步。

流程:

  1. Slave 请求数据同步,Master 判断是否为第一次同步。
  2. Master 判断是第一次同步,返回数据版本信息。
  3. Master 执行 BG save 生成 RDB 文件,并且在生成文件期间,将主进程收到的所有命令记录到内存缓冲区。
  4. Master 向 Slave 发送 RDB 文件,Slave 清空本地数据并加载 RDB 文件。
  5. Master 向 Slave 发送内存缓冲区中的命令,Slave 收到命令后执行。

Master如何判断slave节点是否第一次来做数据同步?

  1. Replayed 是数据集的标记。如果两个节点的 replayed 一致,则说明两个节点是同一个数据集。每一个 master 节点都有唯一的 replayed,slave 节点则会继承 master 节点的 replayed。

  2. Offset 是偏移量,随着记录在内存缓冲区中的数据增多而逐渐增大。slave 完成同步时也会记录当前同步的 offset,如果 slave 的 offset < master 的 offset,说明 slave 数据落后于 master,需要更新结论。

因此,slave 做数据同步必须向 master 声明自己的 replayed 和 offset,master 才可以判断到底是全量同步还是增量同步,以及增量同步需要同步哪些数据。

使⽤ Replication_id 来判断 slave 节点是不是第⼀次来做数据同步,如果 replid ⼀样,说明不是第⼀次,如果 replid 不⼀样说明是第⼀次做数据同步。

offset量只能说明 repl_baklog 数据缓冲区中的数据同步的进度。

增量同步

增量同步:在 slave 节点断开又恢复连接主节点时,会执行增量同步。

流程:

  1. Slave 请求数据同步,Master 判断是否为第一次同步。
  2. Master 判断不是第一次同步,返回 continue。
  3. Master 根据 offset 向 Slave 发送内存缓冲区中的命令,Slave 收到命令并执行。
相关推荐
Cloud云卷云舒2 小时前
HaishanDB(海山)|磐维数据库|YashanDB(崖山)深度对比分析
数据库·人工智能·海山数据库·haishandb·移动云海山数据库
weixin_460443562 小时前
企业考试系统如何对接OA、钉钉和企业微信?SSO单点登录、组织同步与权限一致性设计
java·开发语言·数据库
xywww1682 小时前
真实后台页实测:Opus 5 看图写前端的可用边界在哪
linux·服务器·前端·数据库·人工智能·gpt
上海云盾商务经理杨杨3 小时前
SQL 盲注入渗透实战!无报错页面也能成功注入
数据库·sql
秋田君4 小时前
QT_绘图原理双缓冲机制
服务器·数据库·qt
一条闲鱼_mytube5 小时前
深入理解 Pinecone 向量数据库:从云服务到索引原理,一篇讲透
数据库
许彰午5 小时前
政务低代码平台实战⑤:双输出模式——存DB与导出JSP的完整链路
数据库·低代码·政务
南京码讯光电技术有限公司5 小时前
What Does Ex tb IIIC T80°C Db Mean?
服务器·网络·数据库
2501_937860946 小时前
MySQL CRUD 增删改查
数据库·mysql