Spring Cloud分布式缓存

目录

单点Redis

Redis数据持久化

RDB持久化

bgsave细节

RDB的缺点

AOF持久化

AOF的问题

RDB与AOF对比

搭建Redis主从架构

数据同步原理

全量同步

增量同步

主从同步优化

Redis哨兵

集群检测

选举主节点

故障转移

搭建哨兵集群

RedisTemplate的哨兵模式


单点Redis

单点Redis存在如下问题:

  • Redis是内存存储,服务重启可能会造成数据丢失。
  • 并发问题,虽然是内存存储,并发能力很强,但在单节点下,不适用于高并发的场景。
  • 故障恢复问题,Redis服务宕机,导致某些服务不可用。需要一种自动恢复的手段。
  • 存储能力问题,Redis基于内存,单节点的存储数量难以满足海量数据需求。

对应的解决方案:

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

Redis数据持久化

一共有两种持久化方式:RDB与AOF

RDB持久化

简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件成为RDB文件,默认保存在当前运行目录。

执行命令为

save

该命令由Redis主进程去执行,但由于Redis是单线程的,因此在持久化期间,其他所有命令都会被阻塞。一般不推荐这种方式。而推荐下面这个命令

bgsave

开启子进程执行RDB,不影响主进程。

Redis在主动停机前,会自动执行一次RDB。

但是在Redis内部数据比较多的时候,RDB时间可能会很久,在save期间,如果服务宕机,仍然可能导致数据丢失。因此我们可以在配置文件中修改RDB触发机制。

需要注意的是,以上save实际上都是bgsave,如果是 save "",则代表禁用RDB。

其他配置如下

修改RDB触发条件为5秒内至少有一个key被修改后,重启Redis服务,再进行一个添加操作观察redis服务器,会自动进行RDB。

bgsave细节

bgsave是开启一个子进程去对数据进行持久化操作,虽然实现了异步持久化,但是在fork主进程得到子进程期间是一个阻塞式的操作,为了减少阻塞时间,fork底层实现如下。

主进程是无法直接对物理内存进行操作的,开启主进程时,操作系统会对主进程分配一个虚拟内存,并维护页表,而页表中记录了虚拟内存与物理内存的映射关系,主进程开启子进程过程中仅仅是拷贝了一个页表,并不是拷贝了内存中的数据给子进程去做持久化。因此,主进程和子进程实际上共享同一个内存。

共享同一个内存也存在一个缺点,就是在RDB过程中,主进程需要对数据进行修改。这样就造成了读写冲突。为此,fork底层采用了copy-on-write技术:

  • 当主进程执行读操作时,访问共享内存
  • 当主进程执行写操作时,则会拷贝一份数据,执行写操作。

修改数据前,将原有数据拷贝一份后再进行写操作,同时将主进程中的页表映射关系进行修改。

RDB的缺点

  1. RDB执行间隔时间长,两次RDB之间写入数据有丢失风险。
  2. fork子进程,压缩,写出RDB文件都比较耗时。

AOF持久化

Redis处理的每一个写命令都会记录在AOF文件中,可以看做命令日志文件

当服务重启后,会从AOF文件中将所有命令再执行一边。而AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF

三种刷盘机制对比

|----------|--------|--------------|----------------|
| 配置项 | 刷盘时机 | 优点 | 缺点 |
| Always | 同步 | 可靠性高,几乎不丢失数据 | 性能影响较大 |
| everysec | 每秒刷盘 | 性能适中 | 最多丢失1秒数据 |
| no | 操作系统控制 | 性能最好 | 可靠性较差,可能丢失大量数据 |

开启AOF功能后,执行一次写操作,观察AOF文件

重启Redis服务,会进行一次DB加载

AOF的问题

AOF会记录所有的写操作,但是对于同一个key,记录多次set操作是无意义的,只需要最后一次的set值就满足了,如果执行了delete操作,那么之前的set操作也无意义。因此可以通过执行bgrewriteaof命令,对AOF文件进行重写。可以通过修改配置文件来控制重写时机

RDB与AOF对比

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

搭建Redis主从架构

Reids搭建集群主要是为了实现读写分离,由于大多数使用Redis做缓存,因此是读多写少,也就是说,主节点去实现写操作,从节点去实现读操作。

接下来我们在同一台虚拟机上创建3个Redis实例,实现主从集群

bash 复制代码
#创建3个目录,分别存放不同启动端口的Redis实例
cd /tmp
mkdir 7001 7002 7003
#将redis中的redis.conf文件拷贝到这三个文件当中
# 方式一:逐个拷贝
cp redis-6.2.4/redis.conf 7001
cp redis-6.2.4/redis.conf 7002
cp redis-6.2.4/redis.conf 7003
# 方式二:管道组合命令,一键拷贝
echo 7001 7002 7003 | xargs -t -n 1 cp redis-6.2.4/redis.conf

# 修改配置文件中的启动端口以及文件保存位置
sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/tmp\/7001\//g' 7001/redis.conf
sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/tmp\/7002\//g' 7002/redis.conf
sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/tmp\/7003\//g' 7003/redis.conf

#修改每个实例的声明IP
# 逐一执行
sed -i '1a replica-announce-ip 192.168.150.101' 7001/redis.conf
sed -i '1a replica-announce-ip 192.168.150.101' 7002/redis.conf
sed -i '1a replica-announce-ip 192.168.150.101' 7003/redis.conf
# 或者一键修改
printf '%s\n' 7001 7002 7003 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.150.101' {}/redis.conf

#启动
redis-server 7001/redis.conf 
redis-server 7002/redis.conf 
redis-server 7003/redis.conf 

接下来搭建主从关系:

临时方式(重启失效)

客户短连接redis服务后,执行slaveof方法

bash 复制代码
slaveof 主节点ip 主节点端口

永久方式:修改配置文件添加如下配置

bash 复制代码
slaveof 主节点ip 主节点端口

连接后主节点会打印节点同步信息。将7002、7003将7001作为主节点后,输入如下命令查看集群信息

bash 复制代码
info replication

测试是否可以同步信息,在7001加入数据,在7002查询数据(在从节点无法写入数据)

数据同步原理

全量同步

主从第一次同步也叫全量同步,具体流程如下

解释:当从节点发起数据同步请求时,主节点会判断该节点是否是第一次进行数据同步,如果是第一次,主节点会返回自己数据的版本信息给从节点保存,同时执行一个bgsave操作,去生成RDB文件后发送给从节点,在生成RDB文件期间会将所有的写操作保存在repl_baklog命令缓冲区。从节点接收到RDB文件后,会清空自身数据后加载RDB文件,加载完成后,主节点会将缓冲区的所有命令发送给从节点去执行,从而保证主从信息保持一致。

master如何判断slave是不是第一次同步数据?

Replication Id:简称replid,是数据集的标记,id一致说明是同一数据集,每一个master都存在唯一的replid,而slave会继承master的id用来识别主从节点属于同一数据集。

offset:偏移量。随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset,如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。

slave做数据同步时,必须向master声明自己的replication id(用来判断是否使用同一个数据集)和offset(用来判断同一个数据集下的同步进度),master才会知道有哪些数据需要同步。

因此第一阶段就变成如下流程

查看Redis服务器打印信息

增量同步

当slave宕机重启后,再次与主节点连接时,进行的是增量同步(局部同步)

解释:slave发送自己的数据集id和偏移量信息给主节点,主节点判断不是第一次连接后,就进行增量同步。将repl_baklog命令缓冲区获取自身偏移量与主节点记录的偏移量之间的数据。

由于repl_baklog的文件大小固定,当写满后,会覆盖最早的数据(可以理解为环形数组)。如果slave断开过久,导致未备份的数据被覆盖,则无法基于repl_baklog做增量同步,只能进行全量同步。

主从同步优化

由于全量同步耗时比较久,因此我们要尽可能的减少Redis进行全量同步的次数

  • 在主节点的配置文件中配置repl-dishless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。简单来说就是在写RDB文件时,不写入磁盘,而是通过网络IO流直接写给从节点(适用于网络带宽快的场景)
  • Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO(数据少,那么IO流传输数据就少)
  • 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
  • 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力

Redis哨兵

Redis哨兵(Sentinel)机制来实现主从集群的自动故障恢复,结构如下:

Redis哨兵作用如下:

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

集群检测

Redis哨兵通常集群搭建,基于心跳检测来监控所有节点的状态,每隔一秒向集群每个节点发送ping,如果超过时间没有接收到响应,则认定为主观下线,如果Redis哨兵集群超过指定数量(建议是节点数量的一半)的节点都没有接收到响应,则认定为客观下线(真的下线了),移除下线节点,如果是主节点宕机,需要及时选举新的主节点。其次就是通知java客户端,告知客户端去访问哪个节点。

选举主节点

当主节点宕机后,需要在slave中选举一个主节点,选举依据:

  • 首先会判断slave结点与master结点断开时间长短,如果超过指定值(down-after-milliseconds),直接排除该slave结点(与主节点断开时间过长,缺失数据过多,不适合选举主节点)
  • 判断slave节点的slave-priority值(配置文件中配置。默认一样),越小优先级越高,如果为0则不参加选举
  • 如果slave-priority一样,则判断slave结点的offset值,越大说明数据越新,优先级越高
  • 最后判断运行id(启动顺序),越小优先级越高

故障转移

当主节点(7001)宕机后,选举slave(7002)为主节点后,故障转移的步骤如下:

  • sentinel给备选节点发送slaveof no one命令,让该节点成为新的master
  • sentinel给其他slave节点发送slaveof 新的主节点ip 新的主节点端口 命令,让其他slave节点成为新的主节点的从节点,开始从新的主节点上同步数据。
  • 将原来的主节点标记为slave(实际上是在配置文件中添加了slaveof命令),再重启后自动成为新的主节点的从节点

搭建哨兵集群

我们还是在同一台虚拟机上去搭建哨兵集群。具体命令如下

bash 复制代码
# 进入/tmp目录
cd /tmp
# 创建目录
mkdir s1 s2 s3

# 添加配置文件
vi s1/sentinel.conf

配置如下信息

bash 复制代码
port 27001 #端口后面两个设置为27002 27003
sentinel announce-ip 192.168.150.101 # 声明Sentinel的IP地址
sentinel monitor 集群名称(自定义) 192.168.150.101 7001 2 # 监控主节点的地址 2代表指定的数量来决定节点主观下线
sentinel down-after-milliseconds 集群名称 5000 # 指定slave与master断开超过时间失去选举权
sentinel failover-timeout mymaster 60000 # slave故障恢复的时间
dir "/tmp/s1" # 工作目录

接着配置s2、s3目录下的配置文件

bash 复制代码
# 方式一:逐个拷贝
cp s1/sentinel.conf s2
cp s1/sentinel.conf s3
# 方式二:管道组合命令,一键拷贝
echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf

# 修改s2、s3两个文件夹内的配置文件,将端口分别修改为27002、27003
sed -i -e 's/27001/27002/g' -e 's/s1/s2/g' s2/sentinel.conf
sed -i -e 's/27001/27003/g' -e 's/s1/s3/g' s3/sentinel.conf

启动

bash 复制代码
# 第1个
redis-sentinel s1/sentinel.conf
# 第2个
redis-sentinel s2/sentinel.conf
# 第3个
redis-sentinel s3/sentinel.conf

接下来主动断开7001的服务,模拟主节点宕机。观察哨兵集群打印的消息

去查看7003的打印信息

接着查看sentinel信息

观察7002节点信息

重启7001节点,观察主节点信息

RedisTemplate的哨兵模式

将资料中的redis-demo文件使用IDEA打开

在pom文件中引入redis的starter依赖

XML 复制代码
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在配置文件中application.yml中指定sentinel相关信息

bash 复制代码
spring:
  redis:
    sentinel:
      master: mymaster #指定集群名称
      nodes: # 配置sentinel集群信息
        - 192.168.116.131:27001
        - 192.168.116.131:27002
        - 192.168.116.131:27003

配置读写分离

java 复制代码
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
    return configBuilder ->configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}

这里的ReadFrom是配置Redis的读取策略,是一个枚举类,包括如下选择:

  • MASTER:从主节点读取
  • MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
  • REPLICA:从slave节点读取
  • REPLICA_PREFERRED:优先从slave节点读取,所有的slave都不可用时才读取master

确保redis集群中存在数据后,启动并访问get/{key}(key为redis中的key名称),并观察控制台

接着执行一次set操作,访问/set/{key}/{value}接口

接下来测试故障转移,将7003宕机,观察sentinel控制台

可以看到又将7001作为主节点了。接下来看到Java客户端又输出很多打印信息

可以看到,Java客户端只需要连接哨兵集群,就可以动态的获取到主节点信息与从节点信息。

相关推荐
sthnyph几秒前
docker compose安装redis
redis·docker·容器
KmSH8umpK20 分钟前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第六篇
数据库·redis·分布式
budingxiaomoli2 小时前
多机部署,负载均衡-LoadBalancer
运维·spring cloud·负载均衡
KmSH8umpK3 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第四篇
数据库·redis·分布式
KmSH8umpK3 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第五篇
数据库·redis·分布式
phltxy5 小时前
告别繁琐URL!Spring Cloud OpenFeign 优雅实现微服务远程调用
spring·spring cloud·微服务
贾红平5 小时前
Redis缓存策略深度解析2026
redis
yuweiade5 小时前
GO 快速升级Go版本
开发语言·redis·golang
运维全栈笔记18 小时前
K8S部署Redis高可用全攻略:1主2从3哨兵架构实战
redis·docker·云原生·容器·架构·kubernetes·bootstrap
凯瑟琳.奥古斯特21 小时前
Redis是什么及核心特性
前端·css·redis·缓存