分布式缓存——Redis高级篇

单节点Redis问题

  1. 数据丢失问题:实现Redis数据持久化
  2. 并发能力问题:搭建主从集群,实现读写分离
  3. 故障恢复问题:Redis哨兵,实现健康检测和自动恢复
  4. 存储能力问题:搭建分片集群,利用插槽机制实现动态扩容

一、持久化

方式之一:RDB持久化

RDB全称RedisDatabaseBackup file(Redis数据备份文件)

有两个命令可以进行RDB持久化

  1. save命令:由主进程执行RDB,会阻塞所有命令
  2. bgsave命令:开子进程执行RDB,避免主进程受影响

默认Redis停机时会执行一次RDB

(停机≠宕机)

可以通过修改redis.conf文件来配置相关的RDB参数

1
2
3
4
5
6
7
save 900 1 #900秒内由1次修改,就会执行bgsave。参数可以进行修改
#是否压缩,建议不开启,压缩也会消耗CPU,磁盘的话相对不值钱
rdbcompression yes
#RDB文件名称
dbfilename dump.rdb
#文件保存的路径目录
dir ./

bgsave流程

bgsave开始时会fork主进程得到子进程(在fork过程中主进程会被阻塞),子进程共享主进程的内存数据。fork后主进程可以正常读写,子进程会读取内存数据并写入RDB文件。

如果只是这样可能会有脏数据的问题,就是说在子进程读的时候主进程可能更改了数据。

要理解这个问题,需要理解内存结构。

image-20250719194405703

如图是我参考课程使用visio绘制的fork框图,下面结合这个来解释一下fork的流程。

首先是关于linux的结构,在linux中,进程都是不能直接更改物理内存的,它在虚拟内存中,通过一个页表来维护关系,进程通过修改页表修改内存中的数据。

当使用bgsave的时候,首先会进行fork,会开启子进程,子进程复制主进程的页表,复制完后,为避免脏读的情况,会将数据A和数据B设为只读的共享内存,这样子进程在存储RDB时就不会被主进程干扰。

相应的主进程要更改数据,数据B会被拷贝为数据B的副本,主进程通过修改副本实现修改的功能。

因为内存进行了拷贝,所以极限情况下可能会占用两倍的内存,这也就是为什么使用Redis都要预留一定的内存空间。

RDB缺点:

  1. RDB间隔较长,两次RDB间由数据丢失风险
  2. fork子进程、压缩写出RDB比较耗时

方式二:AOF持久化

AOF全程Append Only File(追加文件),可以看成命令日志文件

可以通过redis.conf配置文件开启和配置AOF

1
2
3
4
5
6
7
#开启
appendonly yes
appendfilename "名字"
#频率
appendfsync always#每执行一次命令,立即写到AOF
appendfsync everysec#写命令执行完先放入AOF缓冲区,每隔一秒将缓冲数据写到AOF
appendfsync no#写命令执行完先放入AOF缓冲区,由操作系统决定什么时候将缓冲数据写到AOF

AOF体积压缩

因为AOF记录的是每条命令,多条命令叠加下可能很多命令是无效的,所以体积会很大。

可以使用bgrewriteaof命令将AOF文件进行重写以减小体积

什么时候进行重写?

可以在redis.conf文件中配置

1
2
auto-aof-rewrite-percentage 100#AOF文件增长超过多少百分比触发重写
auto-aof-rewrite-min-size 64mb#AOF文件体积最小多大以上触发重写

RDB和AOF对比

各有优缺点,如果对数据安全性要求较高,在实际开发中往往会将两种方式结合起来使用

RDB AOF
持久化方式 定时对整个内存做快照 记录每一次执行的命令
数据完整性 不完整,两次备份之间会丢失 相对完整,取决于刷盘策略
文件大小 会有压缩,文件体积小 记录命令,文件体积大
宕机恢复速度 很快
数据恢复优先级 低,因为数据完整性不如AOF 高,因为数据完整性高
系统资源占用 高,大量CPU和内存消耗 低,主要是磁盘IO资源,但AOF重写时会占用大量CPU和内存资源
使用场景 可以容忍数分钟的数据丢失,追求更快的启动速度 对数据安全性要求较高

二、主从集群

  • 搭建主从架构

image-20250721000603573

开启主从关系

1
replicaof/slaveof(5.0之前) host port(主节点的)

开启后主从搭建就完成了,主节点写,从节点只读

  • 数据同步原理

全量同步

首次请求进行全量同步

image-20250721003505684

增量同步

在上边全量同步的流程中,如果发现replid一致,就会进行增量同步。主节点会从log文件中获取offset以后的数据,发送offset之后的命令给从节点。

上边暂存的lrepl_baklog可以理解为是一个环形的结构,而这个环形的结构是有存储上限的。当slave和master之间差的offset超过repl——baklog的大小时,不能进行增量同步

优化:

  1. 在master中配置repl-diskless-sunc yes,启用无磁盘复制,避免全量同步时的磁盘IO
  2. Redis单节点的内存占用不要太大,减少RDB导致的过多磁盘IO
  3. 提高log的大小,发现slave宕机时尽快恢复,尽可能避免全量同步
  4. 限制一个master的slave数量,如果是在太多可以使用主从从结构

三、Redis哨兵

哨兵的作用:

  • 监控:Sentinel会不断检查您的master和slave是否按预期工作
  • 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master,当故障实例恢复后也会以新的master为主
  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新消息推送给Redis的客户端

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令

主观下线:如果Sentinel节点发现某实例未在规定时间内响应,则认为该实例主观下线

客观下线:若超过指定数量(quorum)的Sentinel都认为该实例主观下线,则该实例客观下线。quorum的值最好超过Sentinel实例数量的一半。

选择新的master的优先级:

  1. 断开时间长短
  2. slave-priority
  3. offset
  4. 运行id,越小优先级越高

故障转移流程

  1. 选择新的master,执行slave of no one
  2. 其他节点slave of新的master
  3. 修改故障节点配置,添加slave of新的master

搭建哨兵集群

  1. 新建sentinel.conf
  2. 配置ip、主从集群、目录

RedisTemplate的哨兵模式

  1. 导入依赖
  2. 在yaml文件中配置sentinel集群地址
  3. 配置主从读写分离

四、Redis分片集群

分片集群的作用:

海量数据存储

高并发写操作

分片集群说明:

  • 集群中有多个master
  • 每个master都有多个slave
  • master之间通过ping监测批次的健康状态
  • 客户端请求可访问集群的任意节点,最终都会被转发到正确节点

搭建

redis-cli –-cluster create --cluster-replicas 1 X X X Y Y Y

因为设置每个master的slave个数为1,所以前三个为master,后三个是slave

散列插槽

Redis会把每一个master节点映射到0~16383共16384个插槽(hashslot)上,查看集群信息就能看到

数据key不是与节点绑定,而是与插槽绑定,redis会根据key的有效部分计算插槽值,分两种情况

  • key中含有”{}“且{}中至少包含一个字符,则{}内的部分有效
  • key不是不含{},整个key有效

计算方法是有CRC16算法的一个hash值,然后对16384取余。

如何将同一类数据固定的保存在同一个Redis实例?

使用相同的有效部分,也就是{}内的部分相同

添加一个节点到集群

例:想集群中添加一个master节点,向其中添加一个num=10数据

  1. 添加 使用addnode指令
  2. 插槽分配 使用redis-cli —cluster reshard host port

故障转移

当集群中有一个master宕机会发生什么?

自动故障转移:

  1. 该实例失去连接
  2. 疑似宕机
  3. 确定下线,自动提升一个slave为新的master

如果想要手动更换master节点,使用failover指令进行故障转移

具体流程如下:

  1. slave节点告诉master节点拒绝任何客户端请求
  2. master返回当前的数据offset给slave
  3. slave等待offset与master一致
  4. slave、master开始故障转移
  5. slave标记自己为master,广播故障转移的结果
  6. 原master收到广播,开始处理客户端读的请求

failover有3种模式:

  1. 缺省,就是上述的默认模式
  2. force,省略一致性检验的过程
  3. takeover,直接执行步骤5

RedisTemplate访问分片集群

与哨兵的配置类似

只是在yaml文件的配置不同

今天试了试用图床来插入图片,我已经受够了糟糕的mermaid流程图,还是visio画着舒服❤