目录
单点Redis
Redis数据持久化
RDB持久化
bgsave细节
RDB的缺点
AOF持久化
AOF的问题
RDB与AOF对比
搭建Redis主从架构
数据同步原理
全量同步
增量同步
主从同步优化
Redis哨兵
集群检测
选举主节点
故障转移
搭建哨兵集群
RedisTemplate的哨兵模式
单点Redis存在如下问题:
对应的解决方案:
一共有两种持久化方式:RDB与AOF
简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件成为RDB文件,默认保存在当前运行目录。
执行命令为
save
该命令由Redis主进程去执行,但由于Redis是单线程的,因此在持久化期间,其他所有命令都会被阻塞。一般不推荐这种方式。而推荐下面这个命令
bgsave
开启子进程执行RDB,不影响主进程。
Redis在主动停机前,会自动执行一次RDB。
但是在Redis内部数据比较多的时候,RDB时间可能会很久,在save期间,如果服务宕机,仍然可能导致数据丢失。因此我们可以在配置文件中修改RDB触发机制。
需要注意的是,以上save实际上都是bgsave,如果是 save "",则代表禁用RDB。
其他配置如下
修改RDB触发条件为5秒内至少有一个key被修改后,重启Redis服务,再进行一个添加操作观察redis服务器,会自动进行RDB。
bgsave是开启一个子进程去对数据进行持久化操作,虽然实现了异步持久化,但是在fork主进程得到子进程期间是一个阻塞式的操作,为了减少阻塞时间,fork底层实现如下。
主进程是无法直接对物理内存进行操作的,开启主进程时,操作系统会对主进程分配一个虚拟内存,并维护页表,而页表中记录了虚拟内存与物理内存的映射关系,主进程开启子进程过程中仅仅是拷贝了一个页表,并不是拷贝了内存中的数据给子进程去做持久化。因此,主进程和子进程实际上共享同一个内存。
共享同一个内存也存在一个缺点,就是在RDB过程中,主进程需要对数据进行修改。这样就造成了读写冲突。为此,fork底层采用了copy-on-write技术:
修改数据前,将原有数据拷贝一份后再进行写操作,同时将主进程中的页表映射关系进行修改。
Redis处理的每一个写命令都会记录在AOF文件中,可以看做命令日志文件
当服务重启后,会从AOF文件中将所有命令再执行一边。而AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF
三种刷盘机制对比
配置项 |
刷盘时机 |
优点 |
缺点 |
Always |
同步 |
可靠性高,几乎不丢失数据 |
性能影响较大 |
everysec |
每秒刷盘 |
性能适中 |
最多丢失1秒数据 |
no |
操作系统控制 |
性能最好 |
可靠性较差,可能丢失大量数据 |
开启AOF功能后,执行一次写操作,观察AOF文件
重启Redis服务,会进行一次DB加载
AOF会记录所有的写操作,但是对于同一个key,记录多次set操作是无意义的,只需要最后一次的set值就满足了,如果执行了delete操作,那么之前的set操作也无意义。因此可以通过执行bgrewriteaof命令,对AOF文件进行重写。可以通过修改配置文件来控制重写时机
RDB |
AOF |
|
持久化方式 |
定时对整个内存做快照 |
记录每一次执行的命令 |
数据完整性 |
不完整,两次备份之间会丢失 |
相对完整,取决于刷盘策略 |
文件大小 |
会有压缩,文件体积小 |
记录命令,文件体积很大 |
宕机恢复速度 |
很快 |
慢 |
数据恢复优先级 |
低,因为数据完整性不如AOF |
高,因为数据完整性更高 |
系统资源占用 |
高,大量CPU和内存消耗 |
低,主要是磁盘IO资源 但AOF重写时会占用大量CPU和内存资源 |
使用场景 |
可以容忍数分钟的数据丢失,追求更快的启动速度 |
对数据安全性要求较高常见 |
Reids搭建集群主要是为了实现读写分离,由于大多数使用Redis做缓存,因此是读多写少,也就是说,主节点去实现写操作,从节点去实现读操作。
接下来我们在同一台虚拟机上创建3个Redis实例,实现主从集群
#创建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方法
slaveof 主节点ip 主节点端口
永久方式:修改配置文件添加如下配置
slaveof 主节点ip 主节点端口
连接后主节点会打印节点同步信息。将7002、7003将7001作为主节点后,输入如下命令查看集群信息
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哨兵(Sentinel)机制来实现主从集群的自动故障恢复,结构如下:
Redis哨兵作用如下:
Redis哨兵通常集群搭建,基于心跳检测来监控所有节点的状态,每隔一秒向集群每个节点发送ping,如果超过时间没有接收到响应,则认定为主观下线,如果Redis哨兵集群超过指定数量(建议是节点数量的一半)的节点都没有接收到响应,则认定为客观下线(真的下线了),移除下线节点,如果是主节点宕机,需要及时选举新的主节点。其次就是通知java客户端,告知客户端去访问哪个节点。
当主节点宕机后,需要在slave中选举一个主节点,选举依据:
当主节点(7001)宕机后,选举slave(7002)为主节点后,故障转移的步骤如下:
我们还是在同一台虚拟机上去搭建哨兵集群。具体命令如下
# 进入/tmp目录
cd /tmp
# 创建目录
mkdir s1 s2 s3
# 添加配置文件
vi s1/sentinel.conf
配置如下信息
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目录下的配置文件
# 方式一:逐个拷贝
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
启动
# 第1个
redis-sentinel s1/sentinel.conf
# 第2个
redis-sentinel s2/sentinel.conf
# 第3个
redis-sentinel s3/sentinel.conf
接下来主动断开7001的服务,模拟主节点宕机。观察哨兵集群打印的消息
去查看7003的打印信息
接着查看sentinel信息
观察7002节点信息
重启7001节点,观察主节点信息
将资料中的redis-demo文件使用IDEA打开
在pom文件中引入redis的starter依赖
org.springframework.boot
spring-boot-starter-data-redis
在配置文件中application.yml中指定sentinel相关信息
spring:
redis:
sentinel:
master: mymaster #指定集群名称
nodes: # 配置sentinel集群信息
- 192.168.116.131:27001
- 192.168.116.131:27002
- 192.168.116.131:27003
配置读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
return configBuilder ->configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这里的ReadFrom是配置Redis的读取策略,是一个枚举类,包括如下选择:
确保redis集群中存在数据后,启动并访问get/{key}(key为redis中的key名称),并观察控制台
接着执行一次set操作,访问/set/{key}/{value}接口
接下来测试故障转移,将7003宕机,观察sentinel控制台
可以看到又将7001作为主节点了。接下来看到Java客户端又输出很多打印信息
可以看到,Java客户端只需要连接哨兵集群,就可以动态的获取到主节点信息与从节点信息。