欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 社会 > 【每日八股】复习 Redis Day5:集群(上)

【每日八股】复习 Redis Day5:集群(上)

2025/5/3 11:31:26 来源:https://blog.csdn.net/Coffeemaker88/article/details/147668902  浏览:    关键词:【每日八股】复习 Redis Day5:集群(上)

文章目录

  • 复习昨日内容
    • 缓存雪崩、击穿、穿透的问题描述及解决方案
    • 如何保证数据库和缓存的一致性
      • 普通方案
      • 进阶方案
    • 如何保证缓存删除一定成功?
    • 针对业务一致性要求高的场景,如何确保缓存与数据库的一致性?
    • 如何避免缓存失效?
    • 如何实现延迟队列?
    • 设计一个缓存策略,以动态缓存热点数据?
    • Redis 实现分布式锁?
    • Redis 如何解决集群模式下的分布式锁可靠性?
    • Redis 管道的作用?管道与事务有什么区别?
    • 介绍一个 Redis 的线程模型
      • 简述一下 I/O 多路复用机制
  • 复习 Redis Day5:集群
    • Redis 集群模式有哪些?
    • Redis 切片集群的工作原理
    • 哈希槽与 Redis 节点如何对应?
    • 主从模式的同步过程?

复习昨日内容

缓存雪崩、击穿、穿透的问题描述及解决方案

缓存雪崩
大量热点数据同时过期,或缓存宕机,此时恰逢大量请求访问缓存,在缓存查不到就去查数据库,导致数据库压力骤增,最终服务崩溃。

缓存雪崩的解决方案是:均匀设置热点数据的过期时间、缓存宕机时触发熔断降级停止服务、客户端请求访问数据库重建缓存时加互斥锁。

缓存击穿
当前客户端频繁请求访问的热点数据过期了,导致客户端请求直接穿过缓存访问数据库,导致数据库压力骤增,服务崩溃。

缓存击穿的解决方案是:不为热点数据设置过期时间,或是热点数据过期后当有请求访问数据库重建缓存时加互斥锁。

缓存穿透
大量恶意的用户请求访问缓存和数据库中不存在的数据,占用资源。

缓存穿透的解决办法是:在缓存中设置一个默认值或零值、将频繁恶意访问的用户拉入黑名单。

如何保证数据库和缓存的一致性

普通方案

  • Cache-Aside(旁路缓存):客户端请求访问缓存时,如果能查到数据就直接返回结果,否则查数据库并将结果放回缓存;客户端请求需要修改数据库时,先修改数据库,再删除缓存,写操作不会主动将数据库中修改的结果放回缓存,而是先修改数据库,再删除缓存(这种方式不可避免地会产生缓存中短暂地存在脏数据的问题,当写请求修改数据库时,并发的读请求可以读取到缓存删除前的旧数据)。
  • Read/Write Through:依赖于缓存的组件,比如 Redis 企业版。针对读操作,如果缓存命中则直接返回数据,如果缓存未命中则依赖于缓存组件查数据库并返回结果,在此期间会将数据同步到缓存;针对写操作,直接修改缓存,缓存组件会将修改的结果同步到数据库。
  • Write Behind:写操作会直接修改缓存,再由缓存自行异步地将数据同步到数据库。这种方式的速度很快,但是可能导致数据不一致的问题,适用于允许数据丢失的高并发场景。

进阶方案

双删策略 + 延迟消息
针对写操作,系统首先删除缓存,再向 MQ 发送一条延迟消息,然后系统到数据库当中修改数据库。延迟消息到期时,再次删除缓存。双删策略可以避免修改数据库时的并发读导致的脏数据缓存重建。

基于 binlog 的最终一致性方案

  1. 主库执行写操作,自动生成 binlog;
  2. 数据库的中间件(如 Canal)持续抓取 binlog;
  3. 将 binlog 的二进制日志翻译为可读事件;
  4. 通过 MQ 将事件广播到包括缓存在内的所有订阅方;
  5. 各系统(如缓存/搜索/统计)按顺序消费事件,但允许处理速度不同。

如何保证缓存删除一定成功?

重试机制
引入消息队列,将删除缓存操作封装为消息事件,使用支持持久化的消息队列来存储事件,将事件交由消费者来完成缓存删除。引入消息队列并将删除操作封装为消息事件之后,即使消费者崩溃,消息也不会丢失。

基于 binlog 的最终一致性
所有删除操作先记录到 binlog 日志,后台进程持续消费日志进行删除,定期检查日志和缓存状态。

针对业务一致性要求高的场景,如何确保缓存与数据库的一致性?

  • 旁路缓存或 Read/Write Through;
  • 延迟双删;
  • 基于 binlog 的最终一致性;
  • 旁路缓存优化:写操作前获取分布式锁,更新数据库并删除缓存后释放锁。缺点在于引入分布式锁会牺牲一定的并发性。

如何避免缓存失效?

业务上线之前进行「缓存预热」,提前将数据库中的数据加载到缓存当中。

业务上线之后,可以开一个后台线程不断检查缓存是否失效,如果检测到缓存失效,该线程就去数据库查数据并放回缓存。

或者当业务线程发现缓存失效之后,通过 MQ 发送消息给后台线程更新缓存,后台线程收到消息后,在更新缓存之前会检查缓存是否存在,如果存在就不执行放回操作,否则查数据库将数据放回缓存。

如何实现延迟队列?

通过 Redis 的 ZSET 可以实现延迟队列。ZSet 有一个 Score 属性存储延迟执行的事件。使用 zadd score1 value1 可以新建延迟事件,再通过 zrangebyscore 可以符合条件的待处理任务。

设计一个缓存策略,以动态缓存热点数据?

通过 Redis 的 ZSET 数据结构来实现,可以通过数据的最新访问时间来对数据进行排名,当有新的请求访问数据,就为该数据增加相应的分数,通过 zrangebyscore 返回基于分数排序的结果。

Redis 实现分布式锁?

Redis 可以通过一个全局可见的共享内存来实现跨进程/节点的分布式锁。具体来说,通过命令 SET key unique_value NX PX timeout 命令来完成。NX 确保仅当 key 不存在时设置成功,PXtimeout 为锁设置了过期时间,unique_value 是唯一标识(如 UUID),确保只有锁的持有者能够释放锁。

避免死锁
加锁时通过设置 NX 字段确保只有 key 不存在时才能过加锁。

如何防止其他客户端误删当前客户端的锁?
在删除锁时需要通过 unique_value 进行校验。通过 lua 脚本确保检验操作与锁删除操作的原子性。

锁超时和业务执行时间冲突怎么办?
可以引入锁续期机制(Watchdog),客户端获取锁后,会启动一个后台线程,定期检查锁是否被持有。

Redis 的单点故障问题?
指的是 Redis 宕机时,锁丢失会导致多个客户端同时持有分布式锁,破坏互斥性。

解决办法是引入 RedLock 算法,具体来说是客户端申请加分布式锁时需要同时向多个 Redis 主节点请求加锁,多数节点同意才算加锁成功。如果失败,则向所有节点发送释放锁请求。

高并发下如何处理锁竞争?

  1. 减小锁的粒度,在业务维度细分;
  2. 等待队列;
  3. 超时与重试机制;

为什么选 Redis 实现分布式锁,而不是 zookeeper 或 etcd?

  • Redis 的优势:性能高,实现简单,社区成熟;
  • Zookeeper / etcd 的优势:高一致性,但是性能较低,需要维护临时会话。

Redis 如何解决集群模式下的分布式锁可靠性?

通过 RedLock 算法。

具体的加锁步骤如下:

  1. 客户端获取当前时间( t 1 t_1 t1);
  2. 客户端同时向多个 Redis 主节点发送加锁请求;
  3. 一旦超过半数 Redis 主节点同意加锁,则加锁成功,记录当前时间( t 2 t_2 t2);
  4. 计算总时耗 t 2 − t 1 t_2 - t_1 t2t1,如果总时耗小于锁的过期时间,则认为客户端加锁成功,否则加锁失败,加锁失败时需要向所有 Redis 主节点发送锁删除请求,通过 lua 脚本确保锁校验与锁删除的原子性。

Redis 管道的作用?管道与事务有什么区别?

Pipeline

  • 用于优化网络性能:客户端将 Redis 操作写入 Pipeline 打包发送给 Redis,Redis 处理完之后将处理结果一并返回给客户端,批量操作减少了端到端传输在网络中的 RTT;
  • 非原子性:管道中的命令顺序执行,一条失败不会影响其他命令,执行期间其他客户端的命令可以插入。

Transaction

  • 通过 MULTIEXEC 将多个命令打包为原子性操作;
  • 通过 WATCH 实现乐观锁;
  • 事务中如果存在语法错误会导致事务执行失败;
  • 事务执行之后,如果存在某条命令执行失败,不会影响后续命令的执行,因此 Redis Transaction 不具有原子性和回滚机制。
  • Lua 脚本具有原子性,支持事务回滚,因此应该优先使用 Lua 脚本。

Redis 事务的乐观锁
主要用途

  1. 并发控制:允许客户端在执行事务前监视一个或多个键,如果这些键在事务执行之前被修改,那么事务执行失败;
  2. CAS(Check-And-Set)操作:假设冲突不发生,只在提交时检查数据是否被修改。

介绍一个 Redis 的线程模型

Redis 6.0 之前,Redis 使用单线程来处理所有的任务,包括连接请求、网络 I/O 以及实际的命令执行。Redis 6.0 之后引入了多线程来辅助网络 I/O,但 Redis 的命令执行仍然是单线程执行。

具体来说,Redis 内部使用一个「文件事件处理器」来监控并处理所有请求,它是单线程的,因此 Redis 也是单线程的。文件事件处理器采用 I/O 多路复用机制来监听多个 event,将产生事件的 socket 压入内存队列当中,事件分派器会将 socket 根据事件类型派发给具体的事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)处理具体的事件。

简述一下 I/O 多路复用机制

I/O 多路复用是一种高效的 I/O 处理机制,它允许单个进程/线程同时监视多个文件描述符(如 socket),并在任何一个或多个文件描述符就绪时得到通知。

核心思想是:

  • 单线程处理多连接:用一个线程管理多个 I/O 流,避免为每个连接创建线程的开销;
  • 非阻塞 + 就绪通知:将文件描述符设为非阻塞模式,当某个描述符就绪时得到通知;
  • 避免忙等:通过系统调用阻塞等待,直到有描述符就绪。

实际上 I/O 多路复用机制与 Golang 当中的「select」机制非常像。

复习 Redis Day5:集群

在这里插入图片描述

Redis 集群模式有哪些?

主从:选择一台主服务器,将数据同步到多台从服务器,以构建一主多从模式,主从之间读写分离。主服务器可读可写,从服务器一般只读。主从服务器之间的命令复制是异步的,因此无法保证强一致性。需要注意的是,Redis 的主从复制模式不局限于“一主多从”结构,可能的配置包括:

  • 标准一主多从:一个主节点(Master)+ 多个从节点(Slave);
  • 级联复制(主-从-从):复制的顺序为 主节点 -> 从节点1 -> 从节点2,可以减轻主节点的复制压力;
  • 多主节点架构:通过 Redis Sentinel 或 Cluster 实现多主节点,每个主节点有自己的从节点,数据分片存储在不同的主节点当中。

哨兵:当 Redis 主从服务器出现故障宕机时,需要手动恢复,哨兵模式(Redis Sentinel)解决了这个问题,Redis Sentinel 是 Redis 官方提供的高可用性(HA)方案,用于管理 Redis 主从集群,实现自动故障检测和故障转移。Redis Sentinel 的核心功能包括:

  1. 监控:持续检查主从节点是否正常运行;
  2. 通知:当被监控的 Redis 节点出现问题时,可以通过 API 通知管理员;
  3. 自动故障转移:主节点故障时,自动将一个从节点升级为新的主节点;
  4. 配置提供:作为客户端服务的发现源,提供当前可用的主节点地址。

Redis Sentinel 节点本身是一个特殊的 Redis 实例,一个 Sentinel 集群需要三个或以上的 Sentinel 节点,使用 Raft 算法实现 Sentinel 节点之间的共识。

Sentinel 集群当中包含至少 1 个主节点和 1 个或多个从节点。

切片集群:当数据量大到一台服务器不能承载时,需要使用 Redis 切片集群(Redis Cluster)方案,它将数据分布到不同的服务器上,以此来降低系统对单主节点的依赖,提高 Redis 服务的读写性能

Redis 切片集群的工作原理

切片集群采用哈希槽来进行数据和节点的映射,一个切片集群一共有 16384 个操作,每个存储数据的 key 会经过运算映射到 16384 个槽位中,映射关系如下:

  • 由 key 通过 CRC 16 算法计算出一个 16 bit 的数字;
  • 根据得到的数字,对 16384 取模,确定对应的哈希槽。

「注意」:Redis 的所有键是二进制安全的字符串(可以包含任何数据,比如文本、序列化对象),CRC16 算法的输入是任意长度的字符串,输出是固定的 16 位无符号整数,相同的字符串永远得到相同结果,不同字符串会均匀分布在输出空间。16384 恰位 65536 的 1 4 1 \over 4 41

哈希槽与 Redis 节点如何对应?

  • 平均分配:集群创建时,Redis 自动将哈希槽平均分到集群节点上。
  • 手动分配:使用命令指定没个节点上的哈希槽数目。

主从模式的同步过程?

一. 同步建立阶段

  1. 从节点执行命令连接到主节点;
  2. 从节点保存主节点信息到自己的内存;
  3. 建立与主节点的 socket 连接。

如果需要进行身份验证,那么从节点会向主节点发送 AUTH 命令,验证成功后继续后续流程。

二. 全量同步(初次同步)
首先,从节点向主节点发送 PSYNC ? -1 命令,表示初次同步。之后主节点返回 FULLRESYNC <runid> <offset> 响应:

  • runid:主节点的唯一 ID;
  • offset:当前复制偏移量。

然后:

  1. 主节点执行 bgsave 生成 RDB 快照;
  2. 生成期间的新命令存入复制缓冲区(replication buffer);
  3. RDB 通过 socket 传输到从节点;
  4. 从节点清空旧数据,加载 RDB 文件;
  5. 主节点将复制缓冲区当中的写命令发送给从节点,从节点执行这些命令以同步主节点状态。

三. 增量同步(断线重连)
首先进行条件检查,从节点需满足:

  • 主节点 runid 未变化;
  • 从节点 offset 仍在主节点的复制积压缓冲区中。

之后:

  1. 从节点发送 PSYNC <runid> <offset>
  2. 主节点返回 CONTINUE 响应;
  3. 主节点发送从 offset 之后的所有写命令。

四. 持续同步阶段
命令传播:主节点每执行一个写命令

  • 就将命令发送给所有从节点;
  • 自身和从节点的 offset 增加;

心跳检测

  • 主从节点默认每隔一段时间(比如 10 秒)互相发送 PING;
  • 从节点每秒上报自身复制的 offset。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词