下面针对这四个方面逐一介绍
RDB(Redi Database file-redis数据备份文件),也叫做Redis数据快照,利用快照技术,将内存快照记录到磁盘中。当Redis出现故障后,可以从磁盘中的快照快速恢复到出现故障前的状态。(快照文件就是RDB文件,默认保存在Redis当前运行目录)
Redis使用save命令,保存当前内存快照,注意save命令是Redis主进程来实现的,而Redis本身又是单线程的,所以在执行Sava命令时,其它所有的命令都会进入阻塞状态,所以这种方式消耗是非常大的,所以推荐使用bgsave命令,bgsave命令痛殴开辟另外的子线程来执行RDB,而不会影响到Redis主进程,所以性能会更高。最后Redis在关闭前会自动执行一次RDB
测试
redis-server
同时也可以在当前运行文件中也可以查看到redis的快照
总结:我们可以看出Redis在退出服务会自动做一次RDB,而我们的需求时在Redis运行过程中(毕竟实际业务中redis一般不会退出)做数据持久化(以预防服务器出现宕机等情况),实际上Redis内部有触发RDB的机制,可以在redis.conf中进行配置,格式如下:
save 900 1 #900s内,如果至少有一个key被修改,则执行bgsave,如果是sava “” 则表示禁用RDB
save 300 1 #300s内,如果至少有一个key被修改,则执行bgsave,如果是sava “” 则表示禁用RDB
save 60 10000 #60s内,如果至少有一万个key被修改,则执行bgsave,如果是sava “” 则表示禁用RDB
RDB其它配置也可以在Redis.conf文件中设置
rdbcompression yes #是否压缩,建议不开启,压缩会消耗cpu资源
dbfilename dump.rdb #RDB文件名
dir ./ #文件保存的路径目录
测试
redis-server /usr/local/redis-6.2.5/redis.conf
前面我们知道bgsave是主线程fork出来的子进程来执行数据持久化,下面我细化其持久化过程:
问题:由于fork了一个bgsave进程,现在就会引入多线程中的问题,思考如果子线程在读数据的同时主线程又在写数据,这种情况下就可能出现脏读的问题,而redis采用的解决方案是copy-on-write技术:
总结:RDB缺点
AOF(Append Only File-追加文件):Redis处理的每一个命令都会记录在AOF文件,可以看作是命令日志文件
AOF数据持久化的原理就是,在Redis接收到的每一条命令都会有序的保存到一个AOF文件中,一旦服务器宕机数据丢失,我们只需要重新执行之前的命令就可以达到恢复数据的效果。在Redis中,AOF是默认关闭的,需要修改redis.conf配置文件来开启AOF
appendonly yes #是否开启AOF功能,默认是no
appendfilename "appendonly.aof #AOF文件名
AOF的命令记录的频率也可以通过redis.conf来配置
appendfsync always #表示每执行一条写命令,立即记录AOF文件(同步刷盘-可靠性高,几乎不丢失数据-性能差)
appendfsync eveysec #写命令执行完放入AOF缓冲区,然后表示每隔一秒将缓冲区数据写入AOF文件,是默认方案(每秒刷盘-性能适中-最多丢失1s数据)
appendfsync no #写命令执行完放入AOF缓冲区,由操作系统觉得何时将缓冲区内容写入磁盘(操作系统控制-性能最好-可靠性差,可能丢失大量数据)
测试
save "" #禁用rdb
appendonly yes
set jack chai
出现aof文件(里面记录了写命令)
3. 重启redis并发现可以看到刚刚插入的数据,说明数据持久化成功
问题:设想一下这种常见,我对redis中key为jack的数据又进行了多次set赋值操作,此时AOF会将所有set都记录下来,事实上我们知道,set只有最后一次是有意义的,这就导致了AOF记录了很多无效的指令,所以一般AOF会比RDB大的多。而redis是通过bgrewirteaof
(它与主线程也是异步的)来解决这个问题的,该命令可以让AOF文件执行重写功能,用最少的命令达到相同效果。
2.使用bgrewirteaof
重写aof文件,查看效果(BGREWRITEAOF对aof进行了特殊编码)
BGREWRITEAOF
问题:什么时候触发写AOF文件,前面在介绍RDB持久化方案时,我们知道RDB的执行时间可以时Redis退出时自动执行,或者在通过在配置文件中配置save
命令来执行RDB操作。而AOF在Redis中会触发一个阈值区执行操作,阈值同样可以在redis.conf中配置:
auto-aof-rewrite-percentage 100 #AOF文件比上次文件增长超过多少百分比则触发重写
auto-aof-rewrite-min-size 64mb #AOF文件体积最小多大以上才触发重写
RDB | AOF | |
---|---|---|
持久化方式 | 定时对整个内存做快照 | 记录每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会丢失 | 相对完整,取决于刷盘策略 |
文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积很大 |
宕机恢复速度 | 很快 | 慢 |
数据恢复速度 | 低,因为数据完整性不如AOF | 高,因为数据完整性更高 |
系统资源占用 | 高,大量CPU和内存消耗 | 低,主要是磁盘IO资源,但AOF重写时会占用大量CPU和内存资源 |
使用场景 | 可以容忍数分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求较高常见 |
RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者使用
前面说到单节点的Redis并发能力是有限的,要进一步提高Redis的并发能力,需要搭建Redis主从集群,实现读写业务分离。
搭建Redis集群实战
集群共包括三个Redis节点,一个主节点,两个从节点(由于资源有限,我这里在一台centos7中搭建三个redis服务,分别开启三个不同的端口来模拟)
cd /tmp
mkdir 7011 7012 7013 #分别对应每个redis服务的端口号
这里我这台centos 7中已经安装了一个redis服务,所以我直接拷贝其配置文件即可
cp redis.conf /tmp/7011
cp redis.conf /tmp/7012
cp redis.conf /tmp/7013
修改每个文件夹的配置文件,将端口修改为7001、7002、7003,将rdb文件保存位置都修改为自己所在的目录
sed -i -e 's/6379/7011/g' --e 's/dir .\//dir \/tmp\/7011\//g' 7011/redis.conf
sed -i -e 's/6379/7012/g' --e 's/dir .\//dir \/tmp\/7012\//g' 7012/redis.conf
sed -i -e 's/6379/7013/g' --e 's/dir .\//dir \/tmp\/7013\//g' 7013/redis.conf
其实上面的命令就是执行了文本中指定内容的替换
replica-announce-ip 172.16.23.146 #这是我虚拟机的ip地址
sed -i '1a replica-announce-ip 172.16.23.146' 7011/redis.conf #1a表示在配置文件第一行后面追加一行
sed -i '1a replica-announce-ip 172.16.23.146' 7012/redis.conf
sed -i '1a replica-announce-ip 172.16.23.146' 7013/redis.conf
redis-server 7011/redis.conf
redis-server 7012/redis.conf
redis-server 7013/redis.conf
netstat -tnlp | grep :70
上面已经准备好了三个redis服务,但现在这三个服务没有任何关系,要配置主从可以使用replicaof或者slaveof(5.0以前)命令,而主从关系存在着两种模式:
- 修改配置文件永久生效: 在redis.conf中添加一行配置
slaveof
- 使用redis-cli客户端连接到redis服务,执行slaveof命令
slaveof
(重启后生效)
(1) 模式二演示:
redis-cli -p 7012 -a 123321 #用密码的形式连接到7012端口
slaveof 172.16.23.146 7011 #添加从属关系
注意如果你的主节点设置了密码的话要给每个从节点授权密码,具体方法是在从节点的配置文件中添加如下命令(不然可能连接不上):
masterauth *****
其它一台机器同理(这里将7011作为了master主机)
查看集群状态
INFO replication
测试集群
我现在主节点中添加一条num :1
的数据,看从节点是否可以访问到
发现访问成功,说明主节点的数据已经成功拷贝到从节点上
(2)模式一只是将slaveof信息写入了配置文件中(从节点的),只需要向配置文件中添加下面的命令,然后重启从节点即可,这里就不操作了:
replicaof 127.0.0.1 7011
masterauth 123321
在上面已经完成了一个简单的redis集群了,也成功测试了从主节点添加信息并从节点可以读到信息了(注意在集群中只有主节点可以写入数据,从节点只能读数据,redis集群默认实现了读写分离业务),下面来分析一下主从数据同步的原理所在:
思考:master如何知道slave是不是第一次来?
这里就要介绍两个重要概念:
Replication ID
:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replidoffset
:偏移量,随着记录在real_backlog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新
因此slave做数据同步时,必须向master声明自己的Replication ID
和offset
,master才可以判断到底需要同步哪些数据
总结:全量同步流程
注意:repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间太久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。
可以从以下几个方面来优化redis主从集群:
思考:slave节点宕机恢复后可以找master节点同步数据,那master节点宕机了怎么办?
解决方案:我们可以对集群状态实时进行监控,当master宕机后我们可以立即找一个从节点来作为新的主节点(主从替换操作),而这个监控任务就交到了Redis哨兵手里
Redis提供了哨兵(Sentinel)机制来实现主从几圈的自动故障恢复。哨兵的结构和作用如下:
问题:sentinel是如何监控服务状态的?
sentinel其实是基于心跳机制监控服务状态的,每隔1s向集群的每个实例发送ping命令(和nacos的原理类似):
问题:如果选举新的master?
一旦发现master故障,sentinel需要在slave中选择一个新的作为master节点,依据如下:
问题:选中slave后如何做故障转移?
当选中一个slave后,故障转移的步骤如下:
说明:这里在之前搭建的集群的基础上搭建哨兵集群,这里我们搭建一个三个节点组成的sentinel集群,来监管Redis集群
cd /tmp
mkdir s1 s2 s3
touch sentinal.conf #创建文件
#################################
#sentinel monitor mymaster 127.0.0.1 7011 2:监控名为mymaster的主节点,主节点地址为127.0.0.1,端口号为7011,至少需要2个哨兵同意主节点不可用。
sentinel monitor mymaster 172.16.23.146 7011 2
#主节点的密码(注意从节点的密码要和主节点保持一致)
sentinel auth-pass mymaster 123321
#sentinel down-after-milliseconds mymaster 5000:如果在5000毫秒内无法与主节点建立连接,Sentinel将标记该主节点为不可用。
sentinel down-after-milliseconds mymaster 5000
#sentinel failover-timeout mymaster 30000:故障切换的超时时间为30秒。
sentinel failover-timeout mymaster 30000
#在故障切换期间,每个从节点只能与新的主节点同步一次。
sentinel parallel-syncs mymaster 1
port 27001
#logfile "/var/log/redis/sentinel_27001.log":Sentinel实例的日志文件
logfile "/tmp/s1/sentinel.log"
dir "/tmp/s1"#Sentinel实例的工作目录。
redis-sentinel s1/sentinel.conf
redis-sentinel s2/sentinel.conf
redis-sentinel s3/sentinel.conf
shutdown
#sdown表示主观下线
6531:X 31 Mar 2023 19:02:40.992 # +sdown master mymaster 127.0.0.1 7011
#当odown客观下线
6531:X 31 Mar 2023 19:02:41.070 # +odown master mymaster 127.0.0.1 7011 #quorum 2/2
6531:X 31 Mar 2023 19:02:41.070 # +new-epoch 1
6531:X 31 Mar 2023 19:02:41.070 # +try-failover master mymaster 127.0.0.1 7011
#vote-for-leader,选主节点
6531:X 31 Mar 2023 19:02:41.071 # +vote-for-leader bfb12a5712f4dc8bee97ac10ba963ba47fc70c78 1
6531:X 31 Mar 2023 19:02:41.073 # 20134ef7d28b05a8a0c4b8ac0dfef38b09d98bc3 voted for bfb12a5712f4dc8bee97ac10ba963ba47fc70c78 1
6531:X 31 Mar 2023 19:02:41.133 # +elected-leader master mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:41.133 # +failover-state-select-slave master mymaster 127.0.0.1 7011
#7012被选为主节点
6531:X 31 Mar 2023 19:02:41.234 # +selected-slave slave 172.16.23.146:7012 172.16.23.146 7012 @ mymaster 127.0.0.1 7011
#发送slaveof-noone命令给7012
6531:X 31 Mar 2023 19:02:41.235 * +failover-state-send-slaveof-noone slave 172.16.23.146:7012 172.16.23.146 7012 @ mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:41.301 * +failover-state-wait-promotion slave 172.16.23.146:7012 172.16.23.146 7012 @ mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:42.081 # +promoted-slave slave 172.16.23.146:7012 172.16.23.146 7012 @ mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:42.081 # +failover-state-reconf-slaves master mymaster 127.0.0.1 7011
#广播消息
6531:X 31 Mar 2023 19:02:42.150 * +slave-reconf-sent slave 172.16.23.146:7013 172.16.23.146 7013 @ mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:43.158 * +slave-reconf-inprog slave 172.16.23.146:7013 172.16.23.146 7013 @ mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:43.159 * +slave-reconf-done slave 172.16.23.146:7013 172.16.23.146 7013 @ mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:43.213 # -odown master mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:43.214 # +failover-end master mymaster 127.0.0.1 7011
6531:X 31 Mar 2023 19:02:43.214 # +switch-master mymaster 127.0.0.1 7011 172.16.23.146 7012
6531:X 31 Mar 2023 19:02:43.214 * +slave slave 172.16.23.146:7013 172.16.23.146 7013 @ mymaster 172.16.23.146 7012
6531:X 31 Mar 2023 19:02:43.214 * +slave slave 127.0.0.1:7011 127.0.0.1 7011 @ mymaster 172.16.23.146 7012
从上面日志文件中可以看出sentinel的故障处理的过程,同时我们可以进入7012的控制台看现在的集群状况
现在恢复之前宕机的主节点,查看是否会变成从节点,下面进入7011后,执行set num2 1命令发现已经没有权限了
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化(前面已经演示)及时更新链接信息。Spring的RedisTemplate底层利用lettuce实现李节点的感知和自动切换。
Lettuce 是一个高级 Java Redis 客户端,它提供了基于 Java NIO(非阻塞 I/O)的连接和编码基础设施。在 Spring 应用程序中,Lettuce 通常被用作 Redis 的底层客户端库,与 Spring Data Redis 集成,以提供对 Redis 数据结构、缓存和其他功能的支持。
下面在idea中进行测试
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
logging:
level:
io.lettuce.core: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
spring:
redis:
sentinel:
master: mymaster #指定主节点
nodes: #指定redis-sentinal集群信息
- 172.16.23.146:27001
- 172.16.23.146:27002
- 172.16.23.146:27003
password: 123321 #redis密码
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomize(){
//对lettuce的自定义配置
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择
我们现在就可以在日志中读取关于Redis集群的相关信息了
问题:前面介绍了主从以及哨兵可以解决高可用、高并发读写问题,但是依然有两个问题没有解决:
使用分片集群可以解决上述问题,分片集群特征:
分片集群需要的节点数量比较多,这里我们搭建一个最小的分片集群,保护3个master节点,每个master节点保护一个slave节点。
mkdir 7001 7002 7003 8001 8002 8003
port 6379
# 开启集群功能
cluster-enabled yes
#集群的配置文件名称,不需要我们自己创建,由redis自己维护(这是个只读文件)
cluster-config-file /tmp/6379/nodes.conf
#节点心跳失败的超时时间
cluster-node-timeout 5000
#持久化文件存放目录
dir /tmp/6379
#绑定地址
bind 0.0.0.0
#让redis后台运行
daemonize yes
#主从的实例ip
replica-announce-ip 172.16.23.146
#保护模式
protected-mode no
#数据库数量
datebases 1
#日志
logfile /tmp/6379/run.log
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf #管道
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
关闭所有进程用这种方式:
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown
这里我用的redis 6所有用的5.0之后版本的创建方式,5.0之前有别的创建方式
redis-cli --cluster create --cluster-replicas 1 172.16.23.146:7001 172.16.23.146:7002 172.16.23.146:7003 172.16.23.146:8001 172.16.23.146:8002 172.16.23.146:8003
redis-cli --cluster
:代表集群操作命令
create
:表示创建集群
--cluster-replicas
:指定集群中每个master的副本的个数为1,此时节点总数/(replicas+1)
得到的是master的数量。因此节点列表中的前n个是master,其它节点都是slave节点,随机分配到不同master
redis-cli -p 7001 cluster nodes
集群搭建就完成了
redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,为什么要插槽?
思考当我们需要向redis中插入数据的时候,数据该插到哪里,这其实是和插槽有关的(数据和插槽绑定,就不会因为节点的宕机而数据发生丢失了)!
数据的key不是和节点绑定的,而是和插槽绑定的,redis会根据key的有效部分计算插槽值,分两种情况:
eg:key是num,那么根据num计算,如果是{jakie}num,则根据jakie计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值
(1) 连接一个master节点
redis-cli -c -p 7001 #-c表示集群模式
(2) 加入数据
(3) 结论:redis分片集群操作数据的过程:
- 根据key利用hash算法计算key值,即插槽值
- 根据插槽值找到对应插槽的节点
- 重定向到相应的节点,对数据进行操作
思考:如何将同一类数据固定保存到一个redis实例(节点)?
利用前面介绍到 的{}机制
集群的伸缩功能是指实现集群的动态增加或移除节点
add-node new_host:new_port existing_host:existing_port
--cluster-slave #不指定默认添加是主节点
--cluster-master-id <arg>
案例:向集群中添加一个新的master节点,并向其存储num=10
redis-cli --cluster add-node 172.16.23.146:7004 172.16.23.146:7001
redis-cli --cluster reshard 172.16.23.146:7001
查看插槽分配情况:
redis-cli -p 7001 cluster nodes
虽然分片集群不存在哨兵机制,但是也可以实现故障转移功能。当一个集群的master宕机后,同样会选择一个slave节点来充当主节点,然后宕机的节点恢复后就会成为新的主节点的slave节点,具体的流程如下:
上面上redis自动故障转移的过程
利用cluster failover命令可以实现手动让集群的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移,具体流程如下:
手动的Failover支持三种不同的模式:
测试:让8001成为master
redis-cli -c -p 8001
CLUSTER FAILOVER #默认执行上面六个流程
redis-cli -p 7001 cluster nodes
Redis Template底层同样基于lettuce实现了分片集群的支持,使用的步骤于哨兵模式基本一致,下面简单使用一下
spring:
redis:
cluster:
nodes: #指定分片集群的每一个节点信息
- 172.16.23.146:7001
- 172.16.23.146:7002
- 172.16.23.146:7003
- 172.16.23.146:7004
- 172.16.23.146:8001
- 172.16.23.146:8002
- 172.16.23.146:8003