前述

本篇先讲解Redis主从同步原理及流程,然后讲解Sentinel工作机制。最后,基于Docker部署Redis主从和验证Sentinel模式。相关实现已经放到我的GitHub博客源,Docker搭建Redis主从和Sentinel

Redis主从同步原理

1,增量同步

主从服务器同步的是执行的指令流。当主服务器收到指令时,会将指令写到buffer(buffer大小固定,可配置更改),然后异步将执行指令同步到从服务器。 当buffer空间不足时,新收到指令会导致旧数据覆盖,这时会触发快照同步。

2,快照同步

Redis主服务会生成快照文件dump.rdb,当从服务首次连接到主服务器上或主服务重启时,主服务将dump.rdb文件传送给从服务器,从服务清空旧数据,然后利用dump.rdb文件 恢复最新的数据,恢复完成后,再和主服务器行增量同步保持数据一致。

3,主从同步流程

(1)从服务根据配置的ip和port连接到主服务,如果主服务设置了口令需要提供相应口令。

(2)从服务和主服务成功匹配后,先进行一次快照同步。在主服务生成快照并同步到从服务之前,新访问主服务的命令会被放到主服务的buffer。

(3)快照同步完成后,主从服务进行增量同步。从服务会维护一个数据offset,并与主服务保持同步。

(4)主从服务会按一定频率给对方发送heartbeat,用于检测对方是不是正常在线。如果网络断开然后恢复,会自动重连,并通过offset判断是否需要快照同步。

(5)从服务不处理过期key。当某个key过期时,主服务会模拟一个DEL指令发送给从服务器。

dockers_vm

Sentinel模式简介

Redis主从服务采取读写分离,主服务负责写,从服务负责读。当主服务宕机时,Redis主从并不会从新选择主服务,这将导致整个主从服务无法正常工作。官方推荐的 主从同步高可用方案是Sentinel,它会监控和复制服务状态,当主服务宕机后,会根据选举算法从后端从服务选择新的主服务。Sentinel本身也有单点瓶颈的问题,因此也需要集群。

dockers_vm

工作机制

Sentinel会运行三个定时任务(监控,通知,故障迁移)。当Redis主服务宕机后,会根据一定的机制选择一个领导Sentinel来执行故障迁移。主服务选择出来并配置成功后,领导Sentinel 通过slaveof ip port让其他从服务同步新的主服务。原来的主服务如果重新加入,则会成为新主服务的从服务。

如何确认一个服务已经故障

当一个Sentinel ping不通一个服务节点时,并不会立即认为该服务节点故障(主观下线)。只有当所有Sentinel协商一致,才认为该服务节点故障(客观下线)。

领导选举机制
  • 每个做出主观下线的Sentinel将向其它Sentinel发送命令,要求将自己设为领导Sentinel。
  • 收到命令的Sentinel如果没有同意过其它Sentinel的领导请求命令,那就同意,否则拒绝。
  • 如果该Sentinel发现自己票数超过Sentinel集合的一半且达到quorum,该Sentinel将成为领导Sentinel。
  • 如果此过程有多个Sentinel成为领导Sentinel,那么将等待一段时间重新进行选举。
从服务节点选择机制
  • 选择slave-priority(slave优先级)最高的slave节点,如果有返回,没有继续。
  • 选择复制偏移量最大的slave节点(复制最完整),如果有返回,没有继续。
  • 选择runId最小的slave节点。

测试

为了方便验证和部署Redis主从,我基于Docker-compose构建了验证环境,实现查看文章开头的信息。docker-compose.yml文件内容如下。 其中RedisA作为主服务节点,RedisB和RedisC作为从服务节点。

version: '3'
services:
    redisA:
        image: redis
        restart: always
        ports:
            - "6376:6379"
        volumes:
            - ./config/redis1/:/usr/local/etc/redis/
            - ./logs/redis1/:/var/log/redis/
        command: redis-server /usr/local/etc/redis/redis.conf
    redisB:
        image: redis
        restart: always
        ports:
            - "6377:6379"
        volumes:
            - ./config/redis2/:/usr/local/etc/redis/
            - ./logs/redis2/:/var/log/redis/
        command: redis-server /usr/local/etc/redis/redis.conf
    redisC:
        image: redis
        restart: always
        ports:
            - "6378:6379"
        volumes:
            - ./config/redis3/:/usr/local/etc/redis/
            - ./logs/redis3/:/var/log/redis/
        command: redis-server /usr/local/etc/redis/redis.conf
    sentinelA:
        image: redis
        restart: always
        ports:
            - "6381:6379"
        volumes:
            - ./config/sentinel/:/usr/local/etc/redis/
            - ./logs/sentinel1/:/var/log/redis/
        command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    sentinelB:
        image: redis
        restart: always
        ports:
            - "6382:6379"
        volumes:
            - ./config/sentinel/:/usr/local/etc/redis/
            - ./logs/sentinel2/:/var/log/redis/
        command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    sentinelC:
        image: redis
        restart: always
        ports:
            - "6383:6379"
        volumes:
            - ./config/sentinel/:/usr/local/etc/redis/
            - ./logs/sentinel3/:/var/log/redis/
        command: redis-sentinel /usr/local/etc/redis/sentinel.conf

修改RedisB的配置文件./config/redis2/redis.conf,加入如下配置指定主服务节点地址。RedisC配置相同。

slaveof redis-replication_redisA_1 6379

增加上面的配置后,在终端docker-compose.yml文件所在目录执行docker-compose up启动容器,这样一主两从的Redis主从服务就搭建好了。 可以在主服务节点set一个key验证从服务节点是否正常复制。

我们在终端执行命令docker ps查看容器运行情况,我机器上显示如下,每个人的CONTAINER ID不一样。

CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS              PORTS                      NAMES
ce0c4d42c6a0        redis                  "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:6376->6379/tcp     redis-replication_redisA_1
d21706b6ee24        redis                  "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:6378->6379/tcp     redis-replication_redisC_1
b515bb2db9b5        redis                  "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:6377->6379/tcp     redis-replication_redisB_1
bb546bd4834c        redis                  "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:6382->6379/tcp     redis-replication_sentinelB_1
297d8af73a96        redis                  "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:6383->6379/tcp     redis-replication_sentinelC_1
86883db74e7d        redis                  "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:6381->6379/tcp     redis-replication_sentinelA_1

在Sentinel配置文件.config/sentinel/sentinel.conf加入下面配置。

protected-mode no
sentinel monitor redis-replication_redisA_1 192.168.11.128 6379 2

重新启动容器,这时Sentinel服务会自动更新配置文件,增加一些必要配置信息,并且将主机名和端口用一个hash字符串表示。

执行下面命令停止主服务节点。

docker stop redis-replication_redisA_1

执行了上面的命令后,我们验证,RedisB已经成为新的主服务节点。如果尝试在RedisC写入,则会提示下面错误。

(error) READONLY You can't write against a read only replica.

执行下面的命令重启RedisA,可以看到现在它成为RedisB的从服务器。

docker restart redis-replication_redisA_1

ps: 如果客户端与旧主服务器分隔在一起,写入的数据在恢复后由于旧主会复制新主的数据会造成数据丢失。

后述

主从复制有了,也用Sentinel模式来保证高可用了。但是对于客户端来说,访问的时候如何调度呢?后面会探索Redis负载均衡调度的主要解决方案(针对集群或主从复制),然后抽空写一篇文章。


Published

Category

Redis

Tags

Stay in Touch

Friendship Links