Redis集群的主从同步和集群主从同步的故障自动恢复-哨兵、异步切换数据丢失问题
主从架构
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力就需要搭建主从集群,实现读写分离
Master节点是可读写的,Slave节点是只读的
主从同步原理
主从同步第一次是全量同步
-
slave节点执行replicaof命令与master节点建立连接
-
判断replid是否一致,每一个master都有唯一的replid,slave会继承master的replid
-
replid不一致表示是第一次同步,返回master的replid和offset,offset是偏移量,随着记录在repl_bklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset,如果slave的offset小于master的offset说明slave数据落后于master需要更新
-
slave保存replid和offset
-
master执行bgsave生成rdb文件,并将rdb文件发送给slave
-
slave清空本地rdb数据,加载rdb文件
-
master会记录bgsave期间的所有命令到repl_baklog
-
master发送repl_baklog中命令到slave,slave执行收到的命令
slave重启后执行增量同步
-
建立连接判断replid是否一致
-
replid一致表示非第一次同步,回复contine
-
master去repl_baklog中获取offset后的数据,发送offset后的命令到slave
-
slave执行命令
注:repl_baklog大小有上限,写满后覆盖最早的数据,如果slave断开时间过久,导致未备份的数据被覆盖则无法基于offset做增量同步,只能再次全量同步。
.
.
单机配置redis集群
1.复制多个config到多个目录
2.分别修改config中的端口
3.为每个实例绑定ip,在redis.conf中添加replica-announce-ip 192.168.1.100
4.关闭aof打开rdb
5.修改rdb存储目录
6.分别启动各自的服务
.
.
开启主从关系
redis.conf配置文件中添加一行配置(永久生效): slaveof <masterip> <masterport>
使用redis-cli客户端连接redis服务执行命令(重启后失效):slaveof <masterip> <masterport>
注:redis5.0之前slaveof命令,5.0之后包含5.0使用replicaof命令
.
.
查看主从状态
执行INFO replication命令
.
.
Redis主从集群优化
-
在master修改redis.conf中repl-diskless-sync yes启用无磁盘复制避免全量同步时的磁盘IO,直接将数据通过网络发送给slave而不是先写入rdb,前提是网络带宽要高
-
在master修改redis.conf中repl-backlog-size 1mb适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
-
限制一个master上的slave数量,如果实在时太多slave,则可以采用主-从-从链式结构,减少master压力
.
.
.
Redis哨兵机制来实现主从集群的自动故障恢复,哨兵的结构和作用:
-
监控: Sentinel基于心跳机制监测服务状态,每间隔1秒向集群的每个实例发送ping命令,来检查master和slave是否按预期工作
-
故障自动恢复: 如果master故障,Sentinel会将一个slave提升为master,将故障master的redis.conf中添加slaveof
<masterip> <masterport>
使得故障master在恢复后成为新master的从节点,即故障master恢复后会变成新master的从节点 -
通知: Sentinel充当Redis客户端的服务发现来源,当集群发送故障转移时,会将最新信息推送给Redis的客户端
.
.
.
Master下线的判断
主观下线: 如果sentinel发现某个实列未在规定时间响应,则认为该实例主观下线。
客观下线: 若超过只当数量(quorum)的sentinel都认为该实例主观现象,则该实例客观下线。quorum值最好超过sentinel实例数量的一半
.
.
.
Master选举
一旦发现master客观下线,sentinel需要在salve中选择一个作为新的master,选择依据:
-
首先会判断slave与master断开时间长短,如果超过指定值(down-after-milliseconds*10)则会排除该slave节点
-
然后判断slave的slave-priority值,越小优先级越高,如果是0则永不参与选举,通常各个节点的slave-priority值是相同的选举不依赖此值
-
判断slave节点的offset值,越大说明数据越新,优先级越高
-
最后判断slave的运行id大小,越小优先级越高
.
.
.
如何实现故障转移
-
sentinel会给备选slave发送slaveof no one命令,让该节点成为master
-
sentinel给所有其他slave发送slaveof
<masterip> <masterport>
命令,让这些slave成为新master的从节点,开始从新master同步数据 -
最后sentinel将故障节点标记为slave,当故障恢复后会成为新master节点的slave节点
.
.
.
Redis哨兵模式搭建和代码实现
配置文件目录
修改sentinel.conf配置
-
创建多个目录
-
每个目录创建一个sentinel.conf文件
-
在sentinel.conf文件添加下面内容
port 27001
sentinel announce-ip 192.168.128.128
sentinel monitor mymaster 192.168.128.128 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/temp/27001"
解读
-
port 27001:是当前sentinel实例的端口
-
sentinel monitor mymaster 192.168.1.102 7001 2
-
mymaster:主节点名称,自定义,任意写
-
192.168.1.102 7001:主节点的ip和端口号
-
2:选举master时的quorum值
-
.
.
.
分别启动redis实例和sentinel实例
redis实例需配置好主从关系,即在redis.conf中添加 replicaof 192.168.128.128 7001
./redis-server /temp/7001/redis.conf
./redis-server /temp/7002/redis.conf
./redis-server /temp/7003/redis.conf
./redis-sentinel /temp/27001/sentinel.conf
./redis-sentinel /temp/27002/sentinel.conf
./redis-sentinel /temp/27003/sentinel.conf
关闭master节点之后
代码实现
- 在pom文件中引入redis的starter依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在application.yml中指定sentinel相关信息:
注:因为在sentinel模式下集群地址是可能发生变更的,所以我们不需要知道具体的redis集群的地址,只需要知道sentinel的地址即可
sentinel作为客户端的服务发现来源
spring:
redis:
sentinel:
master: mymaster #指定master名称
nodes: #指定redis-sentinel集群信息
- 192.168.128.128:27001
- 192.168.128.128:27002
- 192.168.128.128:27003
logging:
level:
io.lettuce.core: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
.
.
.
配置读写分离
配置类中添加下面Bean
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
ReadFrom是配置redis的读取策略,是一个枚举,包括下面选择:
MASTER:从主节点读取
MASTER_PREFERRED:优先从master节点读取,master不可用才读取slave
REPLICA:从slave读取
REPLICA_PREFERRED:优先从slave读取,所有的slave都不可用才从master读取
添加set和get接口用来测试
@RestController
@RequestMapping("/redis")
public class TestRedisController {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final ObjectMapper mapper=new ObjectMapper();
/**
* redis存储key,value
* */
@GetMapping("/setValue")
public String setValue(String key,String strValue){
redisTemplate.opsForValue().set(key,strValue);
return "success";
}
/**
* 根据key读取redis的value值
* */
@GetMapping("/getValue")
public String getValue(String key){
return (String)redisTemplate.opsForValue().get(key);
}
}
.
.
.
启动程序
启动之后打印的日志
分别调用set和get接口后日志
.
.
.
哨兵模式主从切换时可能导致数据丢失
异步复制导致数据丢失
主从节点数据复制是异步的,所以可能有部分数据没有复制到slave时,master宕机了,此时数据是不同步的。
.
.
脑裂导致数据丢失
当某个主节点所在的机器突然脱离正常的网络,不能与其他的从节点连接,但实际上主节点还在运行着,这时候哨兵就会认为主节点宕机了,然后开启选举,将其他的从节点推举为新节点,这时候集群中就会出现两个主机,这种现象就是脑裂
当一个从节点被推举为新的主节点时,但此时的客户端还没来得及切换到新的主节点,客户端仍然继续向旧的主节点中传输数据,当旧的主节点恢复的时候,会成为从节点,并挂在新的主节点上,自己的数据就会清空,重新从新的主节点复制数据
.
.
数据丢失解决
- redis.conf配置
#redis提供了可以让master停止写入的方式,如果配置了min-replicas-to-write,健康的slave的个数小于N,mater就禁止写入。
#master最少得有多少个健康的slave存活才能执行写命令。
#这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。
#设置为0是关闭该功能
min-replicas-to-write 3
# 延迟小于min-replicas-max-lag秒的slave才认为是健康的slave
min-replicas-max-lag 10
- 通过先将消息写到消息队列中,每间隔指定时间去队列中读取数据,来发给master节点