目录
一 redis单节点故障导致数据丢失的问题---持久化(AOF,RDB)
3.2.2 Sentinel如何判断一个redis实例是否健康?
3.4.1 引入依赖,并在配置文件中配置哨兵信息以及读写分离
本节内容主要围绕上面4个问题进行学习。
注意:分片集群可以认为是主从结构的进阶版,分片集群中有多个主节点,每一个主节点又有各自的从节点,分片集群不需要哨兵来进行监控和故障转移,它自己就能实现
一 redis单节点故障导致数据丢失的问题---持久化(AOF,RDB)
解决方式:对redis中的数据进行持久化,持久化分为RDB和AOF两种方式。
1.1 RDB
1.1.1 RDB定义
基于快照方式实现,将当前内存中的数据记录到磁盘中,然后在重启的时候进行数据恢复。
1.1.2 启用及配置方式:
在reids.conf文件中找到save,然后,设置保存频率,如下:
# 900秒内,如果至少有1个key被修改,则执行bgsave , 如果是save "" 则表示禁用RDB
save 900 1
# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes
# RDB文件名称
dbfilename dump.rdb
# 文件保存的路径目录
dir ./
时间间隔太长,在这期间如果服务宕机,会丢失数据;
时间间隔太短,如果数据太多,会忙不过来。
1.1.3 RDB运行原理
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据,主线程就会去继续执行任务。完成fork后读取内存数据并写入 RDB 文件。
Linux系统中,进程是无法直接操作物理内存的,只能通过虚拟内存去操作页表,然后通过页表的映射关系去访问物理内存,所以在进行拷贝时,子线程fork的是页表的数据,然后将拷贝过来的页表中的数据写入RDB文件中,最后会替换旧的RDB文件;
fork采用的是copy-on-write技术: 当主进程执行读操作时,访问共享内存; 当主进程执行写操作时,则会拷贝一份数据(内存会翻倍),然后执行写操作,进行数据覆盖。
需要注意:
repl_baklog大小有上限的,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖了,则无法基于log做增量同步了,只能再次全量同步。
1.1.4 总结
RDB方式bgsave的基本流程?
fork主进程得到一个子进程,共享内存空间 子进程读取内存数据并写入新的RDB文件 用新RDB文件替换旧的RDB文件。
RDB会在什么时候执行?save 60 1000代表什么含义?
默认是服务停止时。 代表60秒内至少执行1000次修改则触发RDB
RDB的缺点?
RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险 fork子进程、压缩、写出RDB文件都比较耗时
1.2 AOF
1.2.1 AOF定义
Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。操作命令组成的文件。
1.2.2 启用及配置方式:
在启用AOF前,我们可以先将AOF的持久化方式注销,save "",设置save为空字符就行。
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
Aof命令记录频率,一般用默认即可
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
优化点:AOF文件中记录的是我们操作的每一个写命令,如果我们操作的是同一个KEY,按理讲,只需要最后一次的写命令就行了,这时,我们可以通过bgrewriteaof命令,可以让AOF文件执行重写功能,可以只保留最后一个命令,并且可以用过mset等命令进行批量写。
1.3 AOF和RDB对比
二:redis集群,提高redis性能和可用性
2.1 配置命令
有临时和永久两种模式:
- 修改配置文件(永久生效)
- 在redis.conf中添加一行配置:slaveof <masterip> <masterport>
- 使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):
slaveof <masterip> <masterport>
2.2 主从数据同步原理(全量和非全量同步) 
集群搭建成功后,会进行数据同步请求,master会根据从节点传递过来的Replication Id去判断,看传递过来的是不是master的数据集id,
不是:会进行全量同步,并且将master的信息返回,从节点进行记录,主节点会执行bgsave,进行数据快照,然后将生成的RDb文件发送给从节点,在bgsave期间,可能会有其他的命令,这些会保存在repl_baklog中,后续也会发送到从节点。
是:说明之前进行过数据同步,从节点会保存自己之前同步的记录,之前同步到了哪里,就是偏移量,后续主节点只需要根据从节点发送过来的offset偏移量,从repl_baklog进行增量同步,这时,会有一个坑,这个repl_baklog是环形存储,长时间未同步,并且这期间有大量的数据生成,数据会被覆盖。
所以会在1.1的时候,从节点会传递Replication Id和offset,主节点会判断Replication Id是不是自己的数据集id,是或者不是会走上面的两个逻辑。
全量同步的流程: slave节点请求增量同步 master节点判断replid,发现不一致,拒绝增量同步 master将完整内存数据生成RDB,发送RDB到slave slave清空本地数据,加载master的RDB master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave slave执行接收到的命令,保持与master之间的同步。
2.3 优化Redis主从就集群
在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
2.4 总结
2.4.1 简述全量同步和增量同步区别?
全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
2.4.2 什么时候执行全量同步?
slave节点第一次连接master节点时 slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
2.4.3 什么时候执行增量同步?
slave节点断开又恢复,并且在repl_baklog中能找到offset时
2.5 一主两从搭建
这里采用的方法会在重启时失效,也可采用在配置文件中直接配置slaveof <masterip> <masterport>,这样就是永久生效。
在mac上进行操作,设置不同的端口,模拟三台机器
1: 首先要安装redis相关命令,之前已安装brew,所以直接安装redis即可
brew install redis
2: 创建redis主从的存放目录temp,并且在temp下创建3个目录,用来模拟三台机器
cd docker-data
midir redisZhuCong
cd redisZhuCong
mkdir 7001 7002 7003
3: 修改配置文件,均在redisZhuCong下进行操作,执行命令
3.1:下载官方配置文件:https://raw.githubusercontent.com/redis/redis/6.2/redis.conf
然后拷贝到redisZhuCong目录下
3.2: # 开启RDB,默认就是
# save ""
3.3: # 关闭AOF
appendonly no
3.4: # 进入redisZhuCong目录,然后将redis.conf文件拷贝到三个目录中
echo 7001 7002 7003 | xargs -t -n 1 cp redis.conf
3.5: 修改端口
sed -i -e 's/6379/7002/g' 7002/redis.conf
sed -i -e 's/6379/7003/g' 7003/redis.conf
sed -i -e 's/6379/7001/g' 7001/redis.conf
3.6: 修改文件保存路径,这个使用命令没有成功,手动一个个替换,搜索《dir加一个空格》,然后将dir后的默认路径替换,/Users/***/docker-data/redisZhuCong/7003/,就是7001到3的pwd
3.7: # redis实例的声明 IP,实际情况每一个机器的ip是不一样的,这里我们用的是一个机器,只是端口不一样,也是手动替换,快捷命令没生效。
用的是本地ip,127.0.0.1
replica-announce-ip 自己的ip
快捷命令:printf '%s\n' 7001 7002 7003 | xargs -I{} -t sed -i '1a replica-announce-ip 自己的ip' {}/redis.conf
4: 分别启动三台机器,进入redisZhuCong目录,然后执行下面的启动命令
# 第1个
redis-server 7001/redis.conf
# 第2个
redis-server 7002/redis.conf
# 第3个
redis-server 7003/redis.conf
5: 分别连接三台机器
# 连接 7001, 7002, 7003
redis-cli -p 7001
redis-cli -p 7002
redis-cli -p 7003
6: 这里是将7001作为了主节点
# 在7002和7003的命令界面分别执行slaveof,
# 就可以看到三台机器的启动界面有命令变化,节点注册及数据同步请求
slaveof 127.0.0.1 7001
测试:在7001主节点set num 666,然后在两个从节点上就可以get num的值了
配置文件:
https://raw.githubusercontent.com/redis/redis/6.2/redis.conf
三 redis哨兵机制
3.1 哨兵基本概念
哨兵(Sentinel)机制来实现主从集群的自动故障恢复。主要有以下三个功能:
监控:Sentinel 会不断检查您的master和slave是否按预期工作,基于心跳机制监控集群中每一个redis节点的状态,每隔1秒向集群的每个实例发送ping命令,redis各健康节点会回复pong;
自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后会作为一个从节点,以新的master为主;
判断一个节点是分两步的:主观下线和客观下线;
- 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
- 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
并且如果是master节点挂了,在选举新的master节点时是有四个依据的:
- 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点;
- 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
- 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
- 最后是判断slave节点的运行id大小,越小优先级越高。
当选中了其中一个slave为新的master后(例如slave1),故障的转移的步骤如下:
- sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
- sentinel给所有其它slave发送( slaveof 新master节点IP 新master节点port )命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
- 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,我们在编写代码的时候,如果redis节点出现故障,哨兵在进行故障恢复时,我们是无感的。
3.2 总结
3.2.1 Sentinel的三个作用是什么?
监控 故障转移 通知
3.2.2 Sentinel如何判断一个redis实例是否健康?
每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线,如果大多数sentinel都认为实例下线了,就是客观下线,就会判定该服务下线。
3.2.3 故障转移步骤有哪些?
首先选定一个slave作为新的master,执行slaveof no one,让此节点成为主节点,然后让所有节点都执行slaveof 新master节点的ip和端口,并且修改之前的故障节点配置,都成为从节点。
3.3 搭建哨兵集群
1: 先创建3个文件夹,用来模拟三个redis哨兵
cd docker-data/redisZhuCong
mkdir s1 s2 s3
2: 然后在redisZhuCong目录下创建一个配置文件,sentinel.conf,添加下面的内容
port 27001
sentinel announce-ip 127.0.0.1
sentinel monitor mymaster 127.0.0.1 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/Users/*****/docker-data/redisZhuCong/s1"
配置解析:
- `port 27001`:是当前sentinel实例的端口
- `sentinel monitor mymaster 127.0.0.1 7001 2`:指定主节点信息
- `mymaster`:主节点名称,自定义,任意写
- `127.0.0.1 7001`:主节点的ip和端口
- `2`:选举master时的quorum值
3: 将配置文件复制到s1,s2,s3目录下,在redisZhuCong目录下执行下面的命令
cp sentinel.conf s1
cp sentinel.conf s2
cp sentinel.conf s3
4: 修改配置文件
修改port和dir的保存目录
5: 为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令:
在redisZhuCong目录下执行下面的命令
# 第1个
redis-sentinel s1/sentinel.conf
# 第2个
redis-sentinel s2/sentinel.conf
# 第3个
redis-sentinel s3/sentinel.conf
3.4 java客户端配置并使用哨兵集群
哨兵就是用来监控redis集群中各个节点的,所以我们只需要在配置文件中添加上哨兵的信息,就能自动找到我们的redis节点信息,不用我们来细化操作,直接运用就好。
3.4.1 引入依赖,并在配置文件中配置哨兵信息以及读写分离
1:引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2:配置文件中配置哨兵信息
spring:
redis:
sentinel:
master: mymaster # 指定master名称
nodes: # 指定redis-sentinel集群信息
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003
3:配置读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer( { return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);}
这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择:主要用第四个
MASTER:从主节点读取
MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
REPLICA:从slave(replica)节点读取
REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master
四 Redis分片集群
4.1 分片集群概念
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决: 海量数据存储问题 高并发写的问题;
使用分片集群可以解决上述问题,分片集群特征:
- 集群中有多个master,每个master保存不同数据
- 每个master都可以有多个slave节点
- master之间通过ping监测彼此健康状态
- 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
4.2 搭建分片集群
搭建三主三从的集群
1: 创建6个文件夹,8001,8002,8003,8004,8005,8006
cd docker-data
mkdir redisFenPian
mkdir 8001 8002 8003 8004 8005 8006
2: 分别在6个文件夹下创建一个配置文件,redis.conf,并且添加配置信息
vi redis.conf
解析:
port为对应文件夹名
更改配置文件,工作目录,日志保存位置
port 8006
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /Users/*****/docker-data/redisFenPian/8006/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /Users/*****/docker-data/redisFenPian/8006
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 127.0.0.1
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /Users/*****/docker-data/redisFenPian/8006/run.log
3: 运行,在redisfenpian目录下执行printf '%s\n' 8001 8002 8003 8004 8005 8006 | xargs -I{} -t redis-server {}/redis.conf
ps -ef | grep redis ,可以查看是否运行成功
4: 关闭命令,在redisfenpian目录下执行
ps -ef | grep redis | awk '{print $2}' | xargs kill
5: 我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:
(运行一次就行了,后续只需要运行上面的启动命令就可了,不需要再次进行分区)
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005 127.0.0.1:8006
还有一个Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境。
```sh
# 安装依赖
yum -y install zlib ruby rubygems
gem install redis
# 进入redis的src目录,创建集群
./redis-trib.rb create --replicas 1 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
解析:
- `redis-cli --cluster`或者`./redis-trib.rb`:代表集群操作命令
- `create`:代表是创建集群
- `--replicas 1`或者`--cluster-replicas 1` :指定集群中每个master的副本个数为1,此时`节点总数 ÷ (replicas + 1)` 得到的就是master的数量。因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master
6: 查看集群状态,节点可以是任意一个
redis-cli -p 8001 cluster nodes
7: 连接测试
redis-cli -c -p 7001
分片集群客户端连接需要注意, 加。-c 否则我们在set一个不在登陆客户端上对应的卡槽值时会报错,加上-c就会自动切换客户端。
4.3 散列插槽
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
- key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
- key中不包含“{}”,整个key都是有效部分
例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
注意:如果要将同一类数据固定的保存在同一个Redis实例,只需要这一类数据使用相同的有效部分,例如key都以{typeId}为前缀
4.4 集群伸缩
redis-cli --cluster提供了很多操作集群的命令,可以通过在命令后追加--help去查看更多的命令:
查看当前集群信息
redis-cli -p 8001 cluster nodes
查询操作命令
redis-cli --cluster help
一: 添加节点
1: 新加一个节点,在redisfenpian目录下操作
mkdir 8008
cp 8001/redis.conf 8008
然后修改配置文件中的8001改为为8008
2: 启动8008
redis-server 8008/redis.conf
3: 查询节点信息,这个时候只是8008还没加入集群中
redis-cli -p 8001 cluster nodes
4: 将8008加入到集群中,可查询帮助命令
redis-cli --cluster add-node 127.0.0.1:8008 127.0.0.1:8001
再次查询集群信息,8008就已经加入了,但是还没有分槽
5; 给8008节点分槽
redis-cli --cluster reshard 127.0.0.1:8001
5.1: 分多少槽
5.2: 哪个节点接受,集群信息查询命令可知道节点id,复制即可
5.3: 从哪个节点迁移,同上
5.4: 只需要一个节点迁移的话就可以done了
5.5: yes
二: 删除8008节点
要先将卡槽迁移到其他节点才能删除
1: 8008节点卡槽迁移同上面的第五步
2: 删除节点
redis-cli --cluster del-node 127.0.0.1:8008 节点id
4.5 故障转移
当集群中有一个master宕机会发生什么呢?
- 首先是该实例与其它实例失去连接
- 然后是疑似宕机,命令界面会有fail?
- 最后是确定下线,命令界面直接显示fail,自动提升一个slave为新的master。
4.6 数据迁移
利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:
手动的Failover支持三种不同模式:
- 缺省:默认的流程,如图1~6歩
- force:省略了对offset的一致性校验
- takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
4.6 java客户端访问分片集群
RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
- 引入redis的starter依赖
- 配置分片集群地址
- 配置读写分离
与哨兵模式相比,其中只有分片集群的配置方式略有差异,只有在配置文件中的配置数据时不同
只有第二步与配置哨兵时不一样
1:引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2:配置文件中配置哨兵信息
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
3:配置读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer( { return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);}
这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择:主要用第四个
MASTER:从主节点读取
MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
REPLICA:从slave(replica)节点读取
REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master