redis单节点问题
1.数据丢失问题,数据在内存中,重启就会丢失
解决方案: 实现redis数据持久化
2.并发访问问题,性能不错但是高并发情况下还是会有问题
解决方案: 搭建主从集群,实现读写分离
3.故障恢复问题,单节点部署,一旦出现问题,就出现整个微服务影响的范围非常大
解决方案: 利用redis哨兵实现健康检测和自动恢复
4.存储能力问题,单节点内存难以满足海量数据的存储
解决方案: 搭建分片集群,利用插槽机制实现动态扩容
问题一: redis持久化
有两种解决方式RDB和AOF
RDB: redis数据备份文件,也叫做redis数据快照,简单来说就是把内存中的所有数据都记录到磁盘中,当redis实例故障重启后,从磁盘读取快照文件,恢复数据
快照文件称为RDB文件,默认是保存是保存在当前的运行目录,默认文件名dump.rdb
执行save命令就会进行一次快照保存(save命令是主进程来执行,会影响效率)
一般情况下执行bgsave开启子进程执行RDB,避免住进程收到影响
主动关闭redis会默认自动执行一次RDB。
除了主动执行和关机执行,RDB还会根据配置文件中的配置来执行。
save 900 1 //900秒内,如果至少有1个key被修改,则执行bgsave,如果是save "" 则表示禁用RDB。以下两个同理
save 300 10
save 60 10000
RDB的底层执行流程: bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后读取读取内存数据并写入RDB文件中
RDB缺点: RDB执行间隔比较长,两次RDB之间写入的数据有丢失的风险。 fork主进程,压缩,写出RDB文件都比较耗时
AOF: redis处理的每一个命令都会记录在AOF文件中,可以看做是命令日志文件(默认关闭)
开启需要修改redis.config文件,将appendonly yes(默认为no),appendfilename "appendonly.aof"(修改aof文件名称)
aof记录日志的频率有三种,通过redis.config文件进行修改
第一种表示每执行一次写的命令,都记录在aof文件中 appendfsync always
第二种表示写命令执行完先放入aof缓存区,然后表示每隔一秒将缓存区数据写到aof文件,是默认方案 appendfsync everysec
第三种表示写命令执行完先放入aof缓存区,由操作系统决定何时将缓存区数据写会磁盘 appendfsync no
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。
通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
比如执行了三条语句 set name 123, set name 456, set name 789 执行了bgrewriteaof命令之后会合并为 mset name 123 456 789
Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:
auto-aof-rewrite-percentage 100 //AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-min-size 64mb //AOF文件体积最小多大以上才触发重写
问题二: 并发访问问题
单节点redis并发能力是有上限的,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离
因为redis主要的业务是读的操作,写的操作比较少,一般都是master节点接收读的操作,slave节点接收写的操作,然后master节点将数据同步到各个slave节点上,实现数据一致
集群搭建(一主两从):
1. 这里我们会在同一台虚拟机中开启3个redis实例,模拟主从集群,信息如下:
| 192.169.666.888 | 7001 | master |
| 192.169.666.888 | 7002 | slave |
| 192.169.666.888 | 7003 | slave |
2. 要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。
我们创建三个文件夹,名字分别叫7001、7002、7003:
# 进入/tmp目录
cd /home
# 创建目录
mkdir 7001 7002 7003
恢复原始配置
修改redis-6.2.4/redis.conf文件,将其中的持久化模式改为默认的RDB模式,AOF保持关闭状态。
# 开启RDB
# save ""
save 3600 1
save 300 100
save 60 10000
# 关闭AOF
appendonly no
拷贝配置文件到每个实例目录
然后将redis-6.2.4/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
修改每个实例的端口、工作目录
修改每个文件夹内的配置文件,将端口分别修改为7001、7002、7003,将rdb文件保存位置都修改为自己所在目录
sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/home\/7001\//g' 7001/redis.conf
sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/home\/7002\//g' 7002/redis.conf
sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/home\/7003\//g' 7003/redis.conf
虚拟机本身有多个IP,为了避免将来混乱,我们需要在redis.conf文件中指定每一个实例的绑定ip信息,格式如下:
# redis实例的声明 IP
replica-announce-ip 123.57.87.143
每个目录都要改,我们一键完成修改
sed -i '1a replica-announce-ip 192.169.666.888' 7001/redis.conf
sed -i '1a replica-announce-ip 192.169.666.888' 7002/redis.conf
sed -i '1a replica-announce-ip 192.169.666.888' 7003/redis.conf
为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令:
# 第1个
redis-server 7001/redis.conf
# 第2个
redis-server 7002/redis.conf
# 第3个
redis-server 7003/redis.conf
开启主从关系
现在三个实例还没有任何关系,要配置主从可以使用replicaof 或者slaveof(5.0以前)命令。
有临时和永久两种模式:
修改配置文件(永久生效)
在redis.conf中添加一行配置: slaveof <masterip> <masterport>
使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):
slaveof <masterip> <masterport>
注意: 在5.0以后新增命令replicaof,与salveof效果一致。
这里我们为了演示方便,使用方式二。
通过redis-cli命令连接7002,执行下面命令:
# 连接 7002
redis-cli -p 7002
# 执行slaveof
slaveof 192.169.666.888 7001
通过redis-cli命令连接7003,执行下面命令:
# 连接 7003
redis-cli -p 7003
# 执行slaveof
slaveof 192.169.666.888 7001
然后连接 7001节点,查看集群状态:
# 连接 7001
redis-cli -p 7001
# 查看状态
info replication
测试:
- 利用redis-cli连接7001,执行set num 123
- 利用redis-cli连接7002,执行get num,再执行set num 666
- 利用redis-cli连接7003,执行get num,再执行set num 888
可以发现,只有在7001这个master节点上可以执行写操作,7002和7003这两个slave节点只能执行读操作(完成^_^)
redis主从数据同步原理(全量同步):
1. slave节点请求增量同步
2. master节点判断replid,发现不一致,拒绝增量同步
3. master将完整内存数据生成RDB,发送RDB到slave
4. slave清空本地数据,加载master的RDB
5. master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
6. slave执行接收到的命令,保持与master之间的同步
redis主从数据同步原理(增量同步):
slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
什么时候执行全量同步?
slave节点第一次连接master节点时
slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
slave节点断开又恢复,并且在repl_baklog中能找到offset时
问题三:故障恢复问题
slave节点宕机后重启可以找到master节点同步数据,那master节点宕机了怎么办?
解决: redis提供了哨兵sentinel机制来实现主从集群的自动故障恢复。
监控: sentinel会不断检查master和slave是否按照预期工作
自动故障恢复: 如果master故障,sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
通知: sentinel充当redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给redis的客户端
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
1. 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点 (与master断开时间太长了,丢失的数据越多)
2. 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
3. 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高 (offset代表当前节点与master数据同步进度)
4. 最后是判断slave节点的运行id大小,越小优先级越高。
选取master结束后,故障转义的步骤
1. sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
2. sentinel给所有其它slave发送slaveof 192.169.666.888 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
3. 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
搭建哨兵集群
集群状态(哨兵集群3个实例,redis集群3个实例)
三个sentinel实例信息如下:
| s1 | 192.169.666.888 | 27001 |
| s2 | 192.169.666.888 | 27002 |
| s3 | 192.169.666.888 | 27003 |
1. 要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。
我们创建三个文件夹,名字分别叫s1、s2、s3:
# 进入/home目录
cd /home
# 创建目录
mkdir s1 s2 s3
2. 然后我们在s1目录创建一个sentinel.conf文件,添加下面的内容:
port 27001
sentinel announce-ip 192.169.666.888
sentinel monitor mymaster 192.169.666.888 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/home/s1"
配置解读:
port 27001: 是当前sentinel实例的端口
sentinel monitor mymaster 192.169.666.888 7001 2: 指定主节点信息
mymaster: 主节点名称,自定义,任意写
192.169.666.888 7001: 主节点的ip和端口
2: 选举master时的quorum值
然后将s1/sentinel.conf文件拷贝到s2、s3两个目录中(在/tmp目录执行下列命令):
cp s1/sentinel.conf s2
cp s1/sentinel.conf s3
修改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
3. 启动
为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令:
# 第1个
redis-sentinel s1/sentinel.conf
# 第2个
redis-sentinel s2/sentinel.conf
# 第3个
redis-sentinel s3/sentinel.conf
启动redis集群
# 第1个
redis-server 7001/redis.conf
# 第2个
redis-server 7002/redis.conf
# 第3个
redis-server 7003/redis.conf
4. 测试
将7001节点关机模拟宕机,sentinel会选取一个成为主节点,我这里测试,发现选取了7002为master。
连接7002 info replication查看集群状态,发现7002为master集群,在7002上执行set操作可以成功
连接7003 get 7002上set的值可以成功,7003 set数据提示失败。
以上证明,当master宕机之后,sentinel选取了新的节点成为了master。
将7001开启,再次连接到7002查看集群状态,发现7002还是master,7001和7003都是集群中的salve
springboot整合RedisTemplate连接redis集群与整合redis-sentinel集群:
1. 添加pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.添加配置(注意: 这里配置的不是redis集群地址,是sentinel集群地址。因为在sentinel模式下主从的地址是有可能变化的,所以不能写死)
spring:
redis:
sentinel:
master: mymaster #指定集群名称
nodes: #指定redis-sentinel集群信息
- 192.169.666.888:27001
- 192.169.666.888:27002
- 192.169.666.888:27003
3.配置读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这个配置表示优先从slave节点读,slave阶段不可用时再从master节点读
问题四:存储能力问题,和如果写的操作也很多怎么办?
这里主要用到了redis的分片集群来解决
这里可以学习到 1.redis分片集群 2.散列插槽的原理 3.集群伸缩 4故障转移 5.RedisTemplate访问分片集群
redis分片集群
主从和哨兵模式可以解决高可用,高并发问题,但是依然有两个问题没有解决
第一个问题海量数据存储问题
第二个问题高并发写的问题
使用分片集群可以解决以上的问题。
分片集群的特征(不在需要哨兵模式了,但是具备了哨兵模式的功能)
- 集群中有多个master,每个master保存不同的数据
- 每个master都可以有多个slave节点
- master之间通过ping来检测彼此健康状态
- 客户端请求可以访问集群任意节点,最终都会被转发到正确的节点上
redis分片集群的搭建
步骤一: 集群结构
分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群,包含3个master节点,每个master包含一个slave节点,结构如下:
这里我们会在同一台虚拟机中开启6个redis实例,模拟分片集群,信息如下:
| 192.169.666.888 | 7001 | master |
| 192.169.666.888 | 7002 | master |
| 192.169.666.888 | 7003 | master |
| 192.169.666.888 | 8001 | slave |
| 192.169.666.888 | 8002 | slave |
| 192.169.666.888 | 8003 | slave |
步骤二: 准备实例和配置
删除之前的7001、7002、7003这几个目录,重新创建出7001、7002、7003、8001、8002、8003目录:
# 进入/tmp目录
cd /home
# 删除旧的,避免配置干扰
rm -rf 7001 7002 7003
# 创建目录
mkdir 7001 7002 7003 8001 8002 8003
在/home下准备一个新的redis.conf文件,内容如下:
port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /home/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /home/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 192.169.666.888
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /home/6379/run.log
将这个文件拷贝到每个目录下:
# 进入/home目录
cd /home
# 执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf
修改每个目录下的redis.conf,将其中的6379修改为与所在目录一致:
# 进入/home目录
cd /home
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
步骤三: 启动
因为已经配置了后台启动模式,所以可以直接启动服务:
# 进入/home目录
cd /home
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
通过ps查看状态:
ps aux|grep redis 或 ps -ef | grep redis
如果要关闭所有进程,可以执行命令:
ps -ef | grep redis | awk '{print $2}' | xargs kill
或者(推荐这种方式):
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown
步骤四: 创建集群
虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。
我们需要执行命令来创建集群,在Redis5.0之前创建集群比较麻烦,5.0之后集群管理命令都集成到了redis-cli中。
Redis5.0之前:
Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境。
# 安装依赖
yum -y install zlib ruby rubygems
gem install redis
然后通过命令来管理集群:
# 进入redis的src目录
cd /tmp/redis-6.2.4/src
# 创建集群
./redis-trib.rb create --replicas 1 192.169.666.888:7001 192.169.666.888:7002 192.169.666.888:7003 192.169.666.888:8001 192.169.666.888:8002 192.169.666.888:8003
Redis5.0以后:
我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:
redis-cli --cluster create --cluster-replicas 1 192.169.666.888:7001 192.169.666.888:7002 192.169.666.888:7003 192.169.666.888:8001 192.169.666.888:8002 192.169.666.888:8003
命令说明:
redis-cli --cluster 或者 ./redis-trib.rb: 代表集群操作命令
create: 代表是创建集群
--replicas 1 或者 --cluster-replicas 1: 指定集群中每个master的副本(就是slave节点)个数为1,此时 节点总数 ÷ (replicas + 1) 得到的就是master的数量。
因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master
注意: 这里如果创建集群的时候遇到Waiting for the cluster to join ....................................... 然后无尽的等待。请参照下面这篇文章修改
https://www.jianshu.com/p/250f5da36b49/
通过命令可以查看集群状态:
redis-cli -p 7001 cluster nodes
步骤五: 测试
尝试连接7001节点,存储一个数据:
# 连接
redis-cli -p 7001
# 存储数据
set num 123
# 读取数据
get num
# 再次存储
set a 1
结果悲剧了: 会提示失败
集群操作时,需要给redis-cli 加上 -c 参数才可以: redis-cli -c -p 7001
散列插槽的原理
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应该在哪个实例?
将16384个插槽分配到不同的实例
根据key的有效部分计算哈希值,对16384取余
余数作为插槽,寻找插槽所在实例即可
如何将同一类数据固定的保存在同一个Redis实例?
这一类数据使用相同的有效部分,例如key都以{typeId}为前缀
集群伸缩问题
redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:
redis-cli --cluster help
比如,添加节点的命令:
add-node new_host:new_port existing_host:existing_port
--cluster-slave
--cluster-master-id <arg>
案例演示:
需求:
启动一个新的redis实例,端口为7004
添加7004到之前的集群,并作为一个master节点
给7004节点分配插槽,使得num这个key可以存储到7004实例
1. 新建一个文件7004
2. 拷贝redis.conf配置文件到7004中
3. 修改配置文件中的端口号
4. redis-server 7004/redis.conf 运行7004
5. 将7004加入到集群中 redis-cli --cluster add-node 192.169.666.888:7004 192.169.666.888:7001
6. 分配插槽,不然新增的节点没有任何意义 执行重新分配插槽的命令
redis-cli --cluster reshard 192.169.666.888:7001
然后选择移动的插槽数量输入: 3000
然后选择谁接收这部分插槽: 把7004的id复制过来,就代表7004来接收这部分插槽
然后选择从哪里作为数据源来拷贝: 把7001的id复制过来
然后输入done 表示结束
需求:
删除7004这个实例
1.将插槽移动到7001中(移动步骤同上)
2.然后删除7004 redis-cli --cluster del-node 192.169.666.888:7004 2e9ee91b50676fe00bd8471e91ada1d36a337264
故障转移(自动,发生意外宕机)
分片集群虽然没有哨兵,但是它也具备故障转移的功能,我们一起验证一下这个功能
当集群中有一个master宕机会发生什么呢?
首先是该实例与其它实例失去连接
然后是疑似宕机:
最后是确定下线,自动提升一个slave为新的master:
验证:
1. 通过命令监控集群: watch redis-cli -p 7001 cluster nodes
2. 使用命令redis-cli -p 7002 shutdown
3. 发现8001变成了master
4. 重启7002之后,7002会加入到集群中,但是还是slave节点
(手动故障转移, 机器升级,替换机器时使用)
利用cluster failover命令可以手动让集群中的某个master宕机,切换到新的redis机器上,连接redis执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:
手动的Failover命令支持三种不同模式(参数):
缺省:默认的流程,如图1~6歩
force:省略了对offset的一致性校验
takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
使用RedisTemplate访问分片集群
RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
步骤:
1.引入redis的starter依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置分片集群地址
3.配置读写分离
4.与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:
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
(完结散花)