Redis集群的主从同步和集群主从同步的故障自动恢复-哨兵、异步切换数据丢失问题

主从架构
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力就需要搭建主从集群,实现读写分离
Master节点是可读写的,Slave节点是只读的

主从同步原理

主从同步第一次是全量同步

  1. slave节点执行replicaof命令与master节点建立连接

  2. 判断replid是否一致,每一个master都有唯一的replid,slave会继承master的replid

  3. replid不一致表示是第一次同步,返回master的replid和offset,offset是偏移量,随着记录在repl_bklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset,如果slave的offset小于master的offset说明slave数据落后于master需要更新

  4. slave保存replid和offset

  5. master执行bgsave生成rdb文件,并将rdb文件发送给slave

  6. slave清空本地rdb数据,加载rdb文件

  7. master会记录bgsave期间的所有命令到repl_baklog

  8. master发送repl_baklog中命令到slave,slave执行收到的命令

slave重启后执行增量同步

  1. 建立连接判断replid是否一致

  2. replid一致表示非第一次同步,回复contine

  3. master去repl_baklog中获取offset后的数据,发送offset后的命令到slave

  4. 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主从集群优化

  1. 在master修改redis.conf中repl-diskless-sync yes启用无磁盘复制避免全量同步时的磁盘IO,直接将数据通过网络发送给slave而不是先写入rdb,前提是网络带宽要高

  2. 在master修改redis.conf中repl-backlog-size 1mb适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步

  3. 限制一个master上的slave数量,如果实在时太多slave,则可以采用主-从-从链式结构,减少master压力

    .
    .
    .
    Redis哨兵机制来实现主从集群的自动故障恢复,哨兵的结构和作用:

  1. 监控: Sentinel基于心跳机制监测服务状态,每间隔1秒向集群的每个实例发送ping命令,来检查master和slave是否按预期工作

  2. 故障自动恢复: 如果master故障,Sentinel会将一个slave提升为master,将故障master的redis.conf中添加slaveof <masterip> <masterport>使得故障master在恢复后成为新master的从节点,即故障master恢复后会变成新master的从节点

  3. 通知: Sentinel充当Redis客户端的服务发现来源,当集群发送故障转移时,会将最新信息推送给Redis的客户端
    .
    .
    .

Master下线的判断

主观下线: 如果sentinel发现某个实列未在规定时间响应,则认为该实例主观下线

客观下线: 若超过只当数量(quorum)的sentinel都认为该实例主观现象,则该实例客观下线。quorum值最好超过sentinel实例数量的一半
.
.
.

Master选举

一旦发现master客观下线,sentinel需要在salve中选择一个作为新的master,选择依据:

  1. 首先会判断slave与master断开时间长短,如果超过指定值(down-after-milliseconds*10)则会排除该slave节点

  2. 然后判断slave的slave-priority值,越小优先级越高,如果是0则永不参与选举,通常各个节点的slave-priority值是相同的选举不依赖此值

  3. 判断slave节点的offset值,越大说明数据越新,优先级越高

  4. 最后判断slave的运行id大小,越小优先级越高
    .
    .
    .

如何实现故障转移

  1. sentinel会给备选slave发送slaveof no one命令,让该节点成为master

  2. sentinel给所有其他slave发送slaveof <masterip> <masterport>命令,让这些slave成为新master的从节点,开始从新master同步数据

  3. 最后sentinel将故障节点标记为slave,当故障恢复后会成为新master节点的slave节点

.
.
.

Redis哨兵模式搭建和代码实现
配置文件目录

修改sentinel.conf配置

  1. 创建多个目录

  2. 每个目录创建一个sentinel.conf文件

  3. 在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节点之后

代码实现

  1. 在pom文件中引入redis的starter依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 在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宕机了,此时数据是不同步的。
.
.

脑裂导致数据丢失

当某个主节点所在的机器突然脱离正常的网络,不能与其他的从节点连接,但实际上主节点还在运行着,这时候哨兵就会认为主节点宕机了,然后开启选举,将其他的从节点推举为新节点,这时候集群中就会出现两个主机,这种现象就是脑裂

当一个从节点被推举为新的主节点时,但此时的客户端还没来得及切换到新的主节点,客户端仍然继续向旧的主节点中传输数据,当旧的主节点恢复的时候,会成为从节点,并挂在新的主节点上,自己的数据就会清空,重新从新的主节点复制数据
.
.

数据丢失解决

  1. 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
  1. 通过先将消息写到消息队列中,每间隔指定时间去队列中读取数据,来发给master节点
posted @ 2023-01-09 17:18  big-strong-yu  阅读(2008)  评论(0编辑  收藏  举报