• 技术文章 >数据库 >Redis

    深入浅析Redis中的sentinel故障转移

    青灯夜游青灯夜游2021-10-28 10:34:10转载174
    本篇文章带大家了解一下Redis中的故障转移(sentinel),希望对大家有所帮助!

    当两台以上的Redis实例形成了主备关系,它们组成的集群就具备了一定的高可用性:当master发生故障的时候,slave可以成为新的master对外提供读写服务,这种运营机制成为failover。【相关推荐:Redis视频教程

    那么谁来发现master的故障做failover决策?

    一种方式是,保持一个daemo进程,监控着所有的master-slave节点,如下图所示:

    1.png

    一个Redis集群里面有一个master和两个slave,这个daemon进程监控着这三个节点。但daemon为单节点,本身可用性无法保证。需要引入多daemon,如下图所示:

    2.png

    多个daemon解决了可用性问题,但又出现了一致性问题,如何就某个master是否可用达成一致?例如上图两个daemon1和和master网络不通,daemon和master连接畅通,那此时mater节点是否需要failover那?

    Redis的sentinel提供了一套多daemon间的交互机制,多个daemon间组成一个集群,成为sentinel集群,daemon节点也称为sentinel节点。如下图所示:

    3.png

    这些节点相互间通信、选举、协商,在master节点的故障发现failover决策上表现出一致性。

    sentinel集群监视任意多个master以及master下的slave,自动将下线的master从其下的某个slave升级为新的master代替继续处理命令请求。

    启动并初始化Sentinel

    启动一个Sentinel可以使用命令:

    ./redis-sentinel ../sentinel.conf

    或者命令:

    ./redis-server ../sentinel.conf --sentinel

    当一个Sentinel启动时,它需要执行以下步骤:

    初始化服务器

    Sentinel本质上是运行在特殊模式下的Redis服务器,它和普通的Redis服务器执行的工作不同,初始化过程也不完全相同。如普通的Redis服务器初始化会载入RDB或者AOF文件来恢复数据,而Sentinel启动时不会载入,因为Sentinel并不使用数据库。

    将普通Redis服务器使用的代码替换成Sentinel专用代码

    将一部分普通Redis服务器使用的代码替换成Sentinel专用代码。如普通Redis服务器使用server.c/redisCommandTable作为服务器的命令表:

    truct redisCommand redisCommandTable[] = {
        {"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0},
        {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
        {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
        {"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
        {"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
        {"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},
        {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
        .....
        {"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
        {"unlink",unlinkCommand,-2,"wF",0,NULL,1,-1,1,0,0},
        {"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0},
        {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
        {"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},
        {"bitfield",bitfieldCommand,-2,"wm",0,NULL,1,1,1,0,0},
        {"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
        {"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
        {"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
        {"incr",incrCommand,2,"wmF",0,NULL,1,1,1,0,0},
        {"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0},
        {"mget",mgetCommand,-2,"rF",0,NULL,1,-1,1,0,0},
        {"rpush",rpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
        {"lpush",lpushCommand,-3,"wmF",0,NULL,1,1,1,0,0}
        ......
        }

    Sentinel使用sentinel.c/sentinelcmds作为服务器列表,如下所示:

    struct redisCommand sentinelcmds[] = {
        {"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
        {"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},
        {"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},
        {"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
        {"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
        {"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
        {"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
        {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
        {"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
        {"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
        {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
        {"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0}
    }

    初始化Sentinel状态

    服务器会初始化一个sentinel.c/sentinelState结构(保存服务器中所有和Sentinel功能有关的状态)。

    struct sentinelState {
     
        char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */
        
        //当前纪元,用于实现故障转移
        uint64_t current_epoch;         /* Current epoch. */
        
        //监视的主服务器
        //字典的键是主服务器的名字
        //字典的值则是一个指向sentinelRedisInstances结构的指针
        dict *masters;      /* Dictionary of master sentinelRedisInstances.
                               Key is the instance name, value is the
                               sentinelRedisInstance structure pointer. */
        //是否进入tilt模式
        int tilt;           /* Are we in TILT mode? */
        
        //目前正在执行的脚本数量
        int running_scripts;    /* Number of scripts in execution right now. */
        
        //进入tilt模式的时间
        mstime_t tilt_start_time;       /* When TITL started. */
        
        //最后一次执行时间处理器的时间
        mstime_t previous_time;         /* Last time we ran the time handler. */
        
        // 一个FIFO队列,包含了所有需要执行的用户脚本
        list *scripts_queue;            /* Queue of user scripts to execute. */
        
        char *announce_ip;  /* IP addr that is gossiped to other sentinels if
                               not NULL. */
        int announce_port;  /* Port that is gossiped to other sentinels if
                               non zero. */
        unsigned long simfailure_flags; /* Failures simulation. */
        int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
                                      paths at runtime? */
    }

    根据给定的配置文件,初始化Sentinel的监视主服务器列表

    对Sentinel状态的初始化将引发对masters字典的初始化,而master字典的初始化是根据被载入的Sentinel配置文件来进行的。

    字典的key是监视主服务器的名字,字典的值则是被监控主服务器对应的sentinel.c/sentinelRedisInstance结构。

    sentinelRedisInstance结构部分属性如下:

    typedef struct sentinelRedisInstance {
        //标识值,记录了实例的类型,以及该实例的当前状态
        int flags;      /* See SRI_... defines */
        
        //实例的名字
        //主服务器的名字由用户在配置文件中设置
        //从服务器以及Sentinel的名字由Sentinel自动设置
        //格式为ip:port,例如“127.0.0.1:26379”
        char *name;     /* Master name from the point of view of this sentinel. */
        
        //实例运行的ID
        char *runid;    /* Run ID of this instance, or unique ID if is a Sentinel.*/
        
        //配置纪元,用于实现故障转移
        uint64_t config_epoch;  /* Configuration epoch. */
        
        //实例的地址
        sentinelAddr *addr; /* Master host. */
        
        //sentinel down-after-milliseconds选项设定的值
        //实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)
        mstime_t down_after_period; /* Consider it down after that period. */
        
        //sentinel monitor <master-name> <ip> <redis-port> <quorum>选项中的quorum
        //判断这个实例为客观下线(objective down)所需的支持投票的数量
        unsigned int quorum;/* Number of sentinels that need to agree on failure. */  
        //sentinel parallel-syncs <master-name> <numreplicas> 选项的numreplicas值
        //在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
        int parallel_syncs; /* How many slaves to reconfigure at same time. */
        
        //sentinel failover-timeout <master-name> <milliseconds>选项的值
        //刷新故障迁移状态的最大时限
        mstime_t failover_timeout;      /* Max time to refresh failover state. */
    }

    例如启动Sentinel时,配置了如下的配置文件:

    # sentinel monitor <master-name> <ip> <redis-port> <quorum>
    sentinel monitor master1 127.0.0.1 6379 2
    
    # sentinel down-after-milliseconds <master-name> <milliseconds>
    sentinel down-after-milliseconds master1 30000
    
    # sentinel parallel-syncs <master-name> <numreplicas>
    sentinel parallel-syncs master1 1
    
    # sentinel failover-timeout <master-name> <milliseconds>
    sentinel failover-timeout master1 900000

    则Sentinel则会为主服务器master1创建如下图所示的实例结构:

    4.png

    Sentinel状态以及masters字典的机构如下:

    5.png

    创建连向主服务器的网络连接

    创建连向被监视主服务器的网络连接,Sentinel将成为主服务器的客户端,向主服务器发送命令并从命令回复获取信息。

    Sentinel会创建两个连向主服务器的异步网络连接:

    6.png

    Sentinel发送信息和获取信息

         PUBLISH _sentinel_:hello   "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"

    以上命令相关参数意义:

    参数意义
    s_ipSentinel的ip地址
    s_portSentinel的端口号
    s_runidSentinel的运行ID
    s_runidSentinel的运行ID
    m_name主服务器的名字
    m_ip主服务器的IP地址
    m_port主服务器的端口号
    m_epoch主服务器当前的配置纪元

    命令如下所示:

    SUBSCRIBE sentinel:hello

    7.png

    如上图所示,对于每个与Sentinel连接的服务器 ,Sentinel既可以通过命令连接向服务器频道_sentinel_:hello频道发送信息,又通过订阅连接从服务器的_sentinel_:hello频道接收信息。

    相互连接的各个Sentinel可以进行信息交换。Sentinel为master创建的实例结构中的sentinels字典保存了除Sentinel本身之外,所有同样监视这个主服务器的其它Sentinel信息。

    前面也讲到sentinel会为slave创建实例(在master实例的slaves字典中)。现在我们也知道通过sentinel相互信息交换,也创建了其它sentinel的实例(在master实例的sentinels字典中)。我们将一个sentinel中保存的实例结构大概情况理一下,如下图所示:

    8.png

    从上图可以看到slave和sentinel字典的键由其ip地址和port端口组成,格式为ip:port,其字典的值为其对应的sentinelRedisInstance实例。

    master的故障发现

    主观不可用

    默认情况下Sentinel会以每秒一次的频率向所有与它创建了命令连接的master(包括master、slave、其它Sentinel)发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。

    PING命令回复分为下面两种情况:

    Sentinel配置文件中的设置down-after-milliseconds毫秒时效内(各个sentinel可能配置的不相同),连续向Sentinel返回无效回复,那么sentinel将此实例置为主观下线状态,在sentinel中维护的该实例flags属性中打开SRI_S_DOWN标识,例如master如下所示:

    9.png

    客观不可用

    在sentinel发现主观不可用状态后,它会将“主观不可用状态”发给其它sentinel进行确认,当确认的sentinel节点数>=quorum,则判定该master为客观不可用,随后进入failover流程。

    上面说到将主观不可用状态发给其它sentinel使用如下命令:

    SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>

    各个参数的意义如下:

    接受到以上命令的sentinel会反回一条包含三个参数的Multi Bulk回复

    1)<down_state> 目标sentinel对该master检查结果,1:master已下线 2:master未下线

    2)<leader_runid> 两种情况,*表示仅用于检测master下线状态 ,否则表示局部领头Sentinel的运行ID(选举领头Sentinel)

    3)<leader_epoch> 当leader_runid为时,leader_epoch始终为0。不为时则表示目标Sentinel的局部领头Sentinel的配置纪元(用于选举领头Sentinel)

    其中节点数量限制quorum为sentinel配置文件中配置的

    sentinel monitor <master-name> <ip> <redis-port> <quorum>

    quorum选项,不同的sentinel配置的可能不相同。

    当sentinel认为master为客观下线状态,则会将master属性中的flags的SRI_O_DOWN标识打开,例如master如下图所示:

    10.png

    选举Sentinel Leader

    当一台master宕机时,可能多个sentinel节点同时发现并通过交互确认相互的“主观不可用状态”,同时达到“客观不可用状态”,同时打算发起failover。但最终只能有一个sentinel节点作为failover发起者,那么就需要选举出Sentinel Leader,需要开始一个Sentinel Leader选举过程。

    Redis的Sentinel机制采用类似于Raft协议实现这个选举算法:

    1.sentinelState的epoch变量类似于raft协议中的term(选举回合)。

    2.每一个确认了master“客观不可用”的sentinel节点都会向周围广播自己的参选请求(SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <run_id> ,current_epoch为自己的配置纪元,run_id为自己的运行ID)

    3.每一个接收到参选请求的sentinel节点如果还没接收到其它参选请求,它就将本回合的意向置为首个参选sentinel并回复它(先到先得);如果已经在本回合表过意向了,则拒绝其它参选,并将已有意向回复(如上所介绍的三个参数的Multi Bulk回复,down_state为1,leader_runid为首次接收到的发起参选请求的源sentinel的运行ID,leader_epoch为首次接收到的发起参选请求的源sentinel的配置纪元)

    4.每个发起参选请求的sentinel节点如果收到超过一半的意向同意某个参选sentinel(可能是自己),则确定该sentinel为leader。如果本回合持续了足够长时间未选出leader,则开启下一个回合

    leader sentinel 确定之后,leader sentinel从master所有的slave中依据一定规则选取一个作为新的master

    故障转移failover

    在选举出Sentinel Leader之后,sentinel leader对已下线master执行故障转移:

    选出需要升级为新的master的slave后,Sentinel Leader会向这个slave发送SLAVEOF no one 命令。之后Sentinel会以每秒一次频率(平时是十秒一次)向被升级slave发送INFO,当回复的role由slave变为master时Sentinel Leader就会知道已升级为master。

    总结

    Sentinel是Redis高可用的解决方案,Sentinel集群的节点数需要>=3.

    默认每十秒Sentinel对master和slave执行info,用于发现master变更信息,主从关系以及发现新的slave节点。

    默认每两秒sentinel通过命令连接向所有被监视的master和slave的_sentinel_:hello频道发送一条信息,来和其它sentinel交互信息

    默认每一秒sentinel向master,slave,其它sentinel发送PING命令来判断对方是否下线

    Sentinel Leader是按照一定规则选举出来的。

    由Sentinel Leader进行故障转移操作,选举出新的master来替代已下线的master。

    更多编程相关知识,请访问:编程入门!!

    以上就是深入浅析Redis中的sentinel故障转移的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:Redis sentinel 故障转移
    上一篇:一起聊聊Redis缓存的淘汰策略 下一篇:聊一聊分布式系统下基于Redis的分布式锁

    相关文章推荐

    • 深入解析Redis中的Info指令• 带你吃透Redis中的主从复制、Sentinel、集群• 2022年Redis高频面试题分享(附答案分析)• Redis中必须要掌握的20个问题,快来收藏吧!!• Redis中为什么需要分布式锁?如何实现?• Redis中的两种持久化方式,为什么需要两种持久化?

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网