首頁  >  文章  >  資料庫  >  深入淺析Redis中的sentinel故障轉移

深入淺析Redis中的sentinel故障轉移

青灯夜游
青灯夜游轉載
2021-10-28 10:34:101977瀏覽

這篇文章帶大家了解一下Redis中的故障轉移(sentinel),希望對大家有幫助!

深入淺析Redis中的sentinel故障轉移

當兩台以上的Redis實例形成了主備關係,它們所組成的叢集就具備了一定的高可用性:當master發生故障的時候,slave可以成為新的master對外提供讀寫服務,這種運作機製成為failover。 【相關推薦:Redis影片教學

那麼誰來發現master的故障做failover決策?

一種方式是,維持一個daemo進程,監控所有的master-slave節點,如下圖:

深入淺析Redis中的sentinel故障轉移

##一個Redis叢集裡面有一個master和兩個slave,這個daemon行程監控這三個節點。但daemon為單一節點,本身可用性無法保證。需要引入多daemon,如下圖所示:

深入淺析Redis中的sentinel故障轉移

多個daemon解決了可用性問題,但又出現了一致性問題,如何就某個master是否可用達成一致?例如上圖兩個daemon1和和master網路不通,daemon和master連接暢通,此時mater節點是否需要failover那?

Redis的

sentinel提供了一套多daemon間的交互機制,多個daemon間組成一個集群,成為sentinel集群,daemon節點也稱為sentinel節點。如下圖所示:

深入淺析Redis中的sentinel故障轉移

這些節點

相互間通訊、選舉、協商,在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建立如下圖所示的實例結構:

深入淺析Redis中的sentinel故障轉移

Sentinel狀態以及masters字典的機構如下:

深入淺析Redis中的sentinel故障轉移

##創建連向主伺服器的網路連接建立連向被監視主伺服器的網路連接,Sentinel將成為主伺服器的客戶端,向主伺服器發送命令並從命令回復獲取資訊.

Sentinel會建立兩個連向主伺服器的非同步網路連接:

命令連接,用於向主伺服器發送命令並接收命令回复
  • 訂閱連接,訂閱主伺服器的_sentinel_:hello頻道

#

Sentinel发送信息和获取信息

  • Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的master和slave发送INFO命令

    通过master的回复可获取master本身信息,包括run_id域记录的服务器运行ID,以及role域记录的服务器角色。另外还会获取到master下的所有的从服务器信息,包括slave的ip地址和port端口号。Sentinel无需用户提供从服务器的地址信息,由master返回的slave的ip地址和port端口号,可以自动发现slave。

    当Sentinel发现master有新的slave出现时,Sentinel会为这个新的slave创建相应的实例外,Sentinel还会创建到slave的命令连接和订阅连接。

    根据slave的INFO命令的回复,Sentinel会提取如下信息:

    1.slave的运行ID run_id

    2.slave的角色role

    3.master的ip地址和port端口

    4.master和slave的连接状态master_link_status

    5.slave的优先级slave_priority

    6.slave的复制偏移量slave_repl_offset

  • Sentinel在默认情况下会以每两秒一次的频率,通过命令连接向所有被监视的master和slave的_sentinel_:hello频道发送一条信息

    发送以下格式的命令:

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

以上命令相关参数意义:

参数 意义
s_ip Sentinel的ip地址
s_port Sentinel的端口号
s_runid Sentinel的运行ID
s_runid Sentinel的运行ID
m_name 主服务器的名字
m_ip 主服务器的IP地址
m_port 主服务器的端口号
m_epoch 主服务器当前的配置纪元
  • Sentinel与master或者slave建立订阅连接之后,Sentinel就会通过订阅连接发送对_sentinel_:hello频道的订阅,订阅会持续到Sentinel与服务器的连接断开为止

命令如下所示:

SUBSCRIBE sentinel:hello

深入淺析Redis中的sentinel故障轉移

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

  • sentinel间会相互感知,新加入的sentinel会向master的_sentinel_:hello频道发布一条消息,包括自己的消息,其它该频道订阅者sentinel会发现新的sentinel。随后新的sentinel和其它sentinel会创建长连接。

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

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

深入淺析Redis中的sentinel故障轉移

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

master的故障发现

主观不可用

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

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

  • 有效回复:实例返回 +PONG、-LOADING、-MASTERDOWN三种回复的一种

  • 无效回复:除上面有效回复外的其它回复或者在指定时限内没有任何返回

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

深入淺析Redis中的sentinel故障轉移

客观不可用

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

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

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

各个参数的意义如下:

  • ip:被sentinel判断为主观下线的主服务器的ip地址
  • port: 被sentinel判断为主观下线的主服务器的port地址
  • current_epoch:sentinel的配置纪元,用于选举领头Sentinel
  • runid:可以为*号或者Sentinel的运行ID,*号代表检测主服务器客观下线状态。Sentinel的运行ID用于选举领头Sentinel

接受到以上命令的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如下图所示:

深入淺析Redis中的sentinel故障轉移

選舉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 ,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執行故障轉移:

  • sentinel leader對已下線的master的所有slave中,選出一個狀態良好、資料完整的slave,然後向這個slave發送:SLAVEOF no one 指令,將這個slave轉換為master。

    我們來看下新的master是怎麼挑的? Sentinel leader會將已下線的所有slave儲存到一個列表,然後依照下列規則過濾篩選:

  • 優先順序最高的slave,redis.conf配置中replica-priority選項來標識,預設為100,replica-priority較低的優先權越高。 0為特殊優先級,標誌為無法升級為master。

  • 如果存在多個優先權相等的slave,則會選擇複製偏移量(offset)最大的slave(資料更完整)

  • 如果存在多個優先權相等,最大複製偏移量最大的slave,選擇執行ID最小的slave

選出需要升級為新的master的slave後,Sentinel Leader會傳送SLAVEOF no one 指令到這個slave。 之後Sentinel會以每秒一次頻率(平時是十秒一次)向被升級slave發送INFO,當回复的role由slave變為master時Sentinel Leader就會知道已升級為master。

  • sentinel leader 向已下線的master屬下的slave發送SLAVEOF指令(SLAVEOF ),去複製新的master# 。

  • 將舊的master設定為新的master的slave,並繼續對其監視,當其重新上線時Sentinel會執行命令讓其成為新的master的slave。

總結

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中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除