Redis分布式缓存与持久化 杂知识

  • RDB记录redis快照,一般定期(如60s)执行一次,由于定期间隔长安全性低性能好,压缩二进制文件大小小,恢复快。
  • AOF记录操作增量,也可以定期(如1s)执行一次,由于定期间隔短安全性高性能差,每次插入删除修改都要记录文件大,恢复需要逐个执行命令恢复慢
  • 由于AOF记录了很多冗余操作(比如插了又删),因此可以用rewrite命令重写AOF文件
  • AOF文件不清理一直追加

Redis异步持久化机制:如何利用写时复制保证异步并减少内存占用

  • 主进程持久化,调用fork,操作系统将主进程的虚拟内存页表(指向原始物理数据所在页)拷贝给子进程。
    • 注意:fork会阻塞主进程因此这里要操作够快
  • 将原始物理内存数据加上read-only,并开始持久化
  • 期间主进程要修改内存数据(如插入修改删除),OS在对应页表新建一个拷贝副本,并改变主进程虚拟内存页表指向,主进程在新的拷贝副本修改
  • 子进程持久化结束,期间被拷贝的原始页(主进程要修改)被回收

这样只将持久化期间需要修改的页进行额外拷贝,其它地方没有多余内存占用。此外fork时只拷贝了虚拟内存页表因此很快,基本不阻塞主进程


  • RDB的save 3600 1命令,代表如果 111 小时内有 111 次修改,就触发快照。这个意思是隔一个小时检查一次,如果过去一小时又修改则出发快照,而不是只要有修改就立马触发快照

Redis主从模式下的同步

  • Redis主从模式,最初主从节点建立连接,使用全量同步RDB。后续看情况,如果从节点的offset落后主节点太多则继续全量同步RDB,如果从节点offset没落后太多则使用复制积压缓冲区repl_baklog执行增量同步
  • 正常状态下,主库每执行一个写命令,就要将该命令发给从库的输出缓冲区,并往repl_backlog中存一份,从库来执行同步
  • 异常状态(如从库断开连接),从库恢复后,如果主从offset差距小,主库根据offset与主库的差异,将repl_backlog的对应写命令发给从库,执行增量同步
  • 异常状态(如从库断开连接),从库恢复后,如果主从offset差距大,主库直接将RDB发给从库来执行全量同步
复制积压缓冲区repl_baklog与AOF机制很类似,两者什么区别

AOF没有内容量限制,无限写。而repl_backlog是环形缓冲区(存的是主从库差异的写命令),有内容限制,且如果offset差距过大,超过了缓冲区限制则直接使用RDB全量同步

从库输出缓冲区和复制积压缓冲区区别

从库输出缓冲区用于正常状态下的同步,每个从库都有一个;复制积压缓冲区所有从库共享,环形结构


Redis哨兵模式下的消息通知

哨兵与哨兵之间
  • 只建立命令连接。在节点频道上打过招呼后可以建立命令连接,通过这个连接执行Gossip协议,判断对方是否还活着PING,用Raft选举领头哨兵等
哨兵与节点之间
  • 每个哨兵与所有主从节点之间都建立两个连接:命令连接和订阅连接
  • 命令连接用来发送命令,如确认节点是否在线PING,获取从节点信息INFO,故障恢复时领头哨兵发送的SALVEOF NO ONE以及SLAVEOF IP PORT
  • 订阅连接用来发送频道消息,各个哨兵都向其中发送消息并监听主节点的频道消息,以此来确认其它哨兵信息。节点只作为订阅连接的跳板,不对订阅连接的信息进行转发外的任何操作(甚至都不存储)。
  • 之所以每个主从节点都有订阅连接,是防止主节点挂掉导致频道消失
哨兵与客户端之间
  • 初始化发现 :客户端(如 Jedis, Lettuce)启动时不再直接配置 Redis 的 IP ,而是配置哨兵集群的 IP 列表 。客户端先连接哨兵,通过发送 SENTINEL get-master-addr-by-name [MasterName] 命令,从哨兵手中获取当前主节点的真实 IP 和端口。

  • 订阅通知机制 :客户端会与哨兵建立 订阅连接 ,监听特定的频道(最核心的是 +switch-master)。

    • 当哨兵集群完成故障转移、选出新主节点后,领头哨兵会向该频道发布消息。
    • 消息包含:旧主名称旧主IP旧主端口新主IP新主端口
  • 自动重连与切换 :客户端捕获到 +switch-master 事件后,会立即执行以下动作:

    1. 销毁旧连接:主动断开与原主节点(已挂掉或已降级)的所有连接池。
    2. 刷新配置:在本地更新主节点的内存地址。
    3. 重建连接:根据新地址重新建立连接池,确保后续业务请求发送到新主节点。

Redis哨兵模式下的故障恢复

  • 主节点挂掉
  • 首先哨兵之间存在命令连接,在该连接上首先使用Gossip协议判断主节点是不是真的挂了,如果挂了利用Raft选举算法选取领头哨兵
  • 领头哨兵判断哪个从节点为新的主节点,选取规则是:1.长时间PING不同的不选;2.配置中priority最高的从节点;3.选offset最新的从节点;4.选ID最小的从节点
  • 领头哨兵选举该从节点为新的主节点:利用命令连接让该从节点执行SLAVEOF NO ONE
  • 领头哨兵将其他节点变为该节点的从节点:利用命令连接让这些节点执行SLAVEOF 主节点IP 主节点PORT

Redis哨兵模式下的两套频道系统

1.集群元数据频道(sentinel:hello)
  • 存在于每个Redis数据节点
  • 由所有哨兵进程订阅
  • 用于哨兵之间自动发现,同步等
2.事件通知频道
  • 存在于每个哨兵进程中
  • 由外部客户端(如Jedis)订阅
  • 当故障转移完成后,推送消息给每一个订阅的客户端(如主节点变动)

Redis集群模式下的槽

  • 槽不对应任何的实际物理内存空间,它的作用其实就是:在插入或读取数据时判断数据位于哪个redis节点中,之后就正常将redis数据存入或读取即可;以及解决数据迁移时的粒度问题(以一个槽为粒度将其对应的所有key和value迁移)
  • 为了迁移时对槽整体迁移,需要知道某个槽对应的所有key的位置,这个对应的数据结构就是Slot-to-Dict 数组
  • 在Redis7之前Slot-to-Dict 数组采用跳表实现,查找初始槽是logN的复杂度,顺序遍历出后续的数据位置
  • Redis7之后的Slot-to-Dict 数组采用顺序表+哈希的结构,比如节点A对应槽0-15,那么A有全量16384行记录的顺序表,前16行为有效数据,每行第一个元素为slotid,第二个元素为对应所有数据所在哈希表的地址
  • 在 Redis 7 之后,写入流程:计算槽位 Slot=10Slot=10Slot=10 →\rightarrow→ 访问 Array10 →\rightarrow→ 发现指针指向一个字典 →\rightarrow→ 将 Key-Value 直接存入该字典。
  • 为什么用全量数组,明明大部分位置为Null?因为全量数组也不占用多少空间,且重新分片易于实现,接管一个槽位只需要执行 slot_to_dicti = 新字典地址。

相关推荐
liulilittle3 小时前
论双机热备:分布式
服务器·网络·分布式·通信·竞态
足球中国3 小时前
nuget把缓存搬离C盘
c语言·windows·缓存
爬也要爬着前进4 小时前
redis哨兵模式搭建
数据库·redis·bootstrap
liulilittle6 小时前
论无知:分布式
服务器·网络·分布式·并发·通信·竞态
万亿少女的梦1686 小时前
基于微信小程序、Spring Boot与Vue3的智慧养老管理系统设计与实现
spring boot·redis·微信小程序·mybatis·vue3
Wang's Blog8 小时前
Go-Zero项目开发5: 数据库与缓存一致性问题及 go-zero 缓存机制解析
数据库·缓存·golang
蚰蜒螟8 小时前
从 Jedis 到内核:深入剖析 Redis BRPOP 的阻塞与唤醒机制
数据库·redis·缓存
脱胎换骨-军哥8 小时前
C++ 与 Go 生成质量比拼,谁是分布式主力语言
c++·分布式·golang
糖果店的幽灵9 小时前
【langgraph 从入门到精通graphApi 篇】节点与边 —— 图的骨架
java·数据库·人工智能·redis·langgraph
后端观测站9 小时前
第八篇:Redis 为什么不会每次修改都写磁盘?
数据库·redis·缓存