Redis持久化
什么是持久化?
之前去面试,面试官问了一个问题,既然内存IO速度非常快,假设内存可以做的和磁盘一样大的话,那么可以用内存代替磁盘吗?我当时的回答是:不可以,只用内存的话不能持久化。
redis的持久化说的简单点,就是将将redis中的数据进行备份,方便redis在下次运行时数据不会出现丢失的情况。这也就回答了上面问题了,为什么内存IO那么快却不能只用内存呢?就是因为内存中的数据如果不进行持久化的话,下一次就没了。redis采用的持久化主要是RDB和AOF
RDB持久化
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。
RDB的执行时机
1、使用save指令执行,但是save指令执行是主线程去执行,会阻塞其他的命令
2、 使用bgsave命令执行,这个命令会再单开一个线程异步执行持久化操作,主线程还可以处理用户请求,不受影响
3、 redis停机的时候自动执行,可以看到关闭redis时,默认使用RDB的持久化
4、 触发RDB条件时执行,可以在redis.conf进行配置,下面是对应的格式
# 900秒内,如果至少有1个key被修改,则执行bgsave , 如果是save "" 则表示禁用RDB
save 900 1
save 300 10
save 60 10000
5、 也可以通过redis.conf来配置一些其他的参数
# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes
# RDB文件名称
dbfilename dump.rdb
# 文件保存的路径目录
dir ./
RDB原理
bgsave开始时会fork主进程得到子进程,子进程可以共享主进程中的内存数据。完成fork后读取内存数据写入.rdb文件中去。
fork采用的是copy-on-write技术:
-
当主进程执行读操作时,访问共享内存;
-
当主进程执行写操作时,则会拷贝一份数据,执行写操作。
AOF持久化
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。当需要进行数据还原的时候就会依次执行这些.aof文件的命令
AOF的配置
AOF默认是关闭的,想要开启就得在配置文件中手动的去开启
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
AOF命令记录频率也可以通过配置文件进配置
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
AOF文件的重写
AOF文件是通过将命令写入AOF文件实现数据备份的,那么如果我对同一个变量设置了多次值,那么只有最后一次的设置生效,但是AOF还是会执行每一次的命令,那么就非常耗时,可以通过执行bgrewriteaof命令,让AOF文件执行重写功能,用最少的命令达到相同效果。
Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
AOF和RDB的对比
Redis主从
为什么要搭建主从?
单节点的redis服务并发能力是有限的,想要提升并发能力,那么就可以通过搭建主从的方式来实现读写分离,来分担单节点redis的压力
主从同步的原理
全量同步
主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点,流程如下图所示
大致分为三个阶段:
第一阶段:从节点请求主节点,然后主节点返回版本信息给从节点
第二阶段:主节点发送给从节点RDB文件,并且从节点要清除本地的数据,若主节点在此期间还进行了写操作,那么保存在repl_baklog文件中
第三阶段:发送repl_baklog给从节点进行同步
这里还可以思考一个问题:主节点是如何判断从节点是否是第一次连接呢?
有几个概念,可以作为判断依据:
-
Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
-
offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
所以要看是否是第一次连接就是看replid是否一致
增量同步
全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
什么是增量同步?就是只更新slave与master存在差异的部分数据。如图所示
那么master怎么知道slave与自己的数据差异在哪里呢?
repl_backlog原理
主要是通过repl_backlog文件来进行判断的,repl_backlog文件是一个大小固定的数组,并且还是环形数组也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset,如图所示,红色的是master的offset,绿色的是slave的offset,只要master的offset没有覆盖到slave的offset都可以进行增量同步
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset,那么就会出现数据覆盖的现象,此时就无法进行增量同步,只能进行全量同步
主从同步的优化
主从同步可以保证主从数据的一致性,非常重要。
可以从以下几个方面来优化Redis主从就集群:
-
在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
-
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
-
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
-
限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
-
Redis哨兵
哨兵的原理
先说明一下什么是哨兵,哨兵顾名思义就是来监听redis中的节点是否有在认真工作的一个机制,会将故障的信息发送给客户端,并且如果master节点宕机了,那么哨兵会在其余的slave节点中再选举一个节点来充当master节点。
哨兵的集群结构
集群监控原理
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
•主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
•客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
集群故障恢复原理
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
-
首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
-
然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
-
如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
-
最后是判断slave节点的运行id大小,越小优先级越高。
RedisTemplate
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。
1、首先新建一个demo工程
2、在pom文件中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3、配置redis地址
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003
4、在项目的启动类中,添加一个新的bean:
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这个bean中配置的就是读写策略,包括四种:
-
MASTER:从主节点读取
-
MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
-
REPLICA:从slave(replica)节点读取
-
REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master
Redis分片集群
为什么要使用分片集群?
redis哨兵确实解决了redis主节点故障的问题,但是还有两个问题没有解决
1、高并发的读操作
2、海量数据的存储问题
使用分片集群可以解决上述问题,如图所示
分片集群特征:
-
集群中有多个master,每个master保存不同数据
-
每个master都可以有多个slave节点
-
master之间通过ping监测彼此健康状态
-
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
散列插槽
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
-
key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
-
key中不包含“{}”,整个key都是有效部分
例如:key是num,那么就根据num计算,如果是{Allen}num,则根据Allen计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
如图所示,在7001这个节点执行set a 1时,对a做hash运算,对16384取余,得到的结果是15495,因此要存储到7003节点。
到了7003后,执行get num
时,对num做hash运算,对16384取余,得到的结果是2765,因此需要切换到7001节点
集群伸缩
如果我们想要添加一个节点,并且将其他节点的数据分配到这个节点中去的话,应该如何去实现呢?
可以使用下面的命令来添加节点
redis-cli --cluster add-node 192.168.150.101:7004 192.168.150.101:7001
但是这样的节点分配的插槽数量为0,那么我们想要将其他节点的数据转移到这个节点上的话就得将插槽进行重新分配,可以通过reshard命令来对插槽进行重新分配
故障转移
我们提到了哨兵机制,哨兵会把故障的主节点去除,并且重新选举出一个主节点出来,那么分片集群有没有类似的机制呢?有的兄弟有的,这就是故障转移机制:当master宕机后,自动选取一个slave作为master节点,这就是当master突然宕机后的故障转移机制。
如果这个节点重新连接回来后,如果我还想要这个节点作为master节点又该如何呢?那就可以利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:
这种failover命令可以指定三种模式:
-
缺省:默认的流程,如图1~6歩
-
force:省略了对offset的一致性校验
-
takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
RedisTemplate访问分片集群
RedisTemplate底层同样基于lettuce实现了分片集群的支持,使用的步骤与哨兵模式基本一致
(1)引入redis的starter依赖
(2)配置分片集群地址
(3)配置读写分离
在yaml配置文件中进行配置
spring:
redis:
cluster:
nodes:
- 192.168.150.101:7001
- 192.168.150.101:7002
- 192.168.150.101:7003
- 192.168.150.101:8001
- 192.168.150.101:8002
- 192.168.150.101:8003