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客户端只需要连接哨兵集群,就可以动态的获取到主节点信息与从节点信息。

相关推荐
A_cot28 分钟前
Redis 的三个并发问题及解决方案(面试题)
java·开发语言·数据库·redis·mybatis
一叶飘零_sweeeet2 小时前
为什么 Feign 要用 HTTP 而不是 RPC?
java·网络协议·http·spring cloud·rpc·feign
芊言芊语2 小时前
分布式缓存服务Redis版解析与配置方式
redis·分布式·缓存
bug菌¹2 小时前
滚雪球学SpringCloud[4.1讲]: Spring Cloud Gateway详解
java·spring cloud·微服务
bug菌¹2 小时前
滚雪球学SpringCloud[4.2讲]: Zuul:Netflix API Gateway详解
spring·spring cloud·gateway
攻城狮的梦3 小时前
redis集群模式连接
数据库·redis·缓存
小筱在线4 小时前
SpringCloud微服务实现服务熔断的实践指南
java·spring cloud·微服务
Amagi.6 小时前
Redis的内存淘汰策略
数据库·redis·mybatis
无休居士7 小时前
【实践】应用访问Redis突然超时怎么处理?
数据库·redis·缓存
.Net Core 爱好者7 小时前
Redis实践之缓存:设置缓存过期策略
java·redis·缓存·c#·.net