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→ 访问 Array[10] →\rightarrow→ 发现指针指向一个字典 →\rightarrow→ 将 Key-Value 直接存入该字典。
  • 为什么用全量数组,明明大部分位置为Null?因为全量数组也不占用多少空间,且重新分片易于实现,接管一个槽位只需要执行 slot_to_dict[i] = 新字典地址。

相关推荐
陈皮糖..4 小时前
Docker Compose 学习之多容器应用编排与运维实践 —— 基于 Nginx+MySQL+Redis 服务栈的部署与管理
运维·redis·学习·mysql·nginx·docker
老毛肚4 小时前
Redis八股
数据库·redis·缓存
fengxin_rou4 小时前
黑马点评实战篇|第六篇:秒杀优化
java·开发语言·数据库·redis·分布式
Francek Chen4 小时前
【大数据存储与管理】分布式数据库HBase:04 HBase的实现原理
大数据·数据库·hadoop·分布式·hbase
ErizJ4 小时前
面试 | Redis八股
redis·面试
独断万古他化4 小时前
【抽奖系统开发实战】Spring Boot 项目的用户模块设计:注册登录、权限管控与敏感数据加密
java·spring boot·redis·后端·mvc·jwt·拦截器
霖霖总总4 小时前
[Redis小技巧14]深入 Redis RDB 快照机制:原理、配置与实战指南
数据库·redis
尝&试5 小时前
中间件--redis集群 大批量查询删除 优化及相关异常解决
redis·中间件·bootstrap
七夜zippoe5 小时前
Redis高级数据结构实战:从Stream到HyperLogLog的深度解析
数据结构·数据库·redis·python·缓冲