本篇文章带大家了解一下Redis中的故障转移(sentinel),希望对大家有所帮助!
当两台以上的Redis实例形成了主备关系,它们组成的集群就具备了一定的高可用性:当master发生故障的时候,slave可以成为新的master对外提供读写服务,这种运营机制成为failover。【相关推荐:Redis视频教程】
那么谁来发现master的故障做failover决策?
一种方式是,保持一个daemo进程,监控着所有的master-slave节点,如下图所示:
一个Redis集群里面有一个master和两个slave,这个daemon进程监控着这三个节点。但daemon为单节点,本身可用性无法保证。需要引入多daemon,如下图所示:
多个daemon解决了可用性问题,但又出现了一致性问题,如何就某个master是否可用达成一致?例如上图两个daemon1和和master网络不通,daemon和master连接畅通,那此时mater节点是否需要failover那?
Redis的sentinel提供了一套多daemon间的交互机制,多个daemon间组成一个集群,成为sentinel集群,daemon节点也称为sentinel节点。如下图所示:
这些节点相互间通信、选举、协商,在master节点的故障发现、failover决策上表现出一致性。
sentinel集群监视任意多个master以及master下的slave,自动将下线的master从其下的某个slave升级为新的master代替继续处理命令请求。
启动一个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创建如下图所示的实例结构:
Sentinel状态以及masters字典的机构如下:
创建连向主服务器的网络连接
创建连向被监视主服务器的网络连接,Sentinel将成为主服务器的客户端,向主服务器发送命令并从命令回复获取信息。
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 | 主服务器当前的配置纪元 |
命令如下所示:
SUBSCRIBE sentinel:hello
如上图所示,对于每个与Sentinel连接的服务器 ,Sentinel既可以通过命令连接向服务器频道_sentinel_:hello频道发送信息,又通过订阅连接从服务器的_sentinel_:hello频道接收信息。
相互连接的各个Sentinel可以进行信息交换。Sentinel为master创建的实例结构中的sentinels字典保存了除Sentinel本身之外,所有同样监视这个主服务器的其它Sentinel信息。
前面也讲到sentinel会为slave创建实例(在master实例的slaves字典中)。现在我们也知道通过sentinel相互信息交换,也创建了其它sentinel的实例(在master实例的sentinels字典中)。我们将一个sentinel中保存的实例结构大概情况理一下,如下图所示:
从上图可以看到slave和sentinel字典的键由其ip地址和port端口组成,格式为ip:port,其字典的值为其对应的sentinelRedisInstance实例。
主观不可用
默认情况下Sentinel会以每秒一次的频率向所有与它创建了命令连接的master(包括master、slave、其它Sentinel)发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。
PING命令回复分为下面两种情况:
有效回复:实例返回 +PONG、-LOADING、-MASTERDOWN三种回复的一种
无效回复:除上面有效回复外的其它回复或者在指定时限内没有任何返回
Sentinel配置文件中的设置down-after-milliseconds毫秒时效内(各个sentinel可能配置的不相同),连续向Sentinel返回无效回复,那么sentinel将此实例置为主观下线状态,在sentinel中维护的该实例flags属性中打开SRI_S_DOWN标识,例如master如下所示:
客观不可用
在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如下图所示:
マスターがダウンすると、複数のセンチネル ノードが相互作用を通じて同時に互いの「主観的な可用性ステータス」を検出および確認し、同時に「目的」を達成することができます。利用不可ステータス」となり、フェイルオーバーを開始しようとしています。ただし、最終的にフェイルオーバー イニシエーターとして存在できるセンチネル ノードは 1 つだけであるため、センチネル リーダー を選出し、センチネル リーダーの選出プロセスを開始する必要があります。
Redis の Sentinel メカニズムは、Raft プロトコルと同様にこの選挙アルゴリズムを実装します:
1. SentinelState のエポック変数は、Raft プロトコルの用語 (選挙ラウンド) に似ています。
2. マスターが「客観的に利用できない」ことを確認した各センチネル ノードは、自身の選出リクエストを周囲にブロードキャストします(SENTINEL is-master-down-by-addr
3. 選挙リクエストを受信した各センチネル ノードはまだそれを受信していません他の候補者が要求された場合、このラウンドの意図を最初の候補者の番兵として設定し、それに応答します (先着順 )。このラウンドで意図が表明されている場合、他の候補者は拒否され、意図を持って応答します (上で紹介した 3 つのパラメータを含むマルチ バルク応答、down_state は 1、leader_runid は初めて受信した選挙リクエストを開始したソース センチネルの実行 ID、leader_epoch は最初に受信した選挙リクエストです)リクエストのソースであるセンチネルの構成エポック)
4. 候補リクエストを開始する各センチネル ノードが 候補センチネルに同意する意向の半分以上を受信した場合(たぶんそれ自体), そして、センチネルがリーダーであることが決定されます。このラウンドが十分に長く続き、リーダーが選出されない場合、次のラウンドが開始されます。ルール。
フェイルオーバー フェイルオーバー
sentinelリーダーは、オフライン マスターのすべてのスレーブの中で、状態が良好で完全なデータを持つ
スレーブコマンドを送信して、このスレーブを変更します。マスターに変換されます。 新しいマスターがどのように選択されるかを見てみましょう。 Sentinel リーダーはすべてのオフライン スレーブをリストに保存し、次のルールに従ってフィルタリングします:
同じ優先度を持つ複数のスレーブがある場合、最大のコピー オフセットを持つスレーブが
選択されます同じ優先順位と最大の最大レプリケーション オフセットを持つ複数のスレーブがある場合は、
実行 ID が最も小さいスレーブを選択します新しいマスターにアップグレードする必要があるスレーブの場合、Sentinel リーダーは SLAVEOF no one コマンドをスレーブに送信します。
その後、Sentinel はアップグレードされたスレーブに 1 秒に 1 回 (通常は 10 秒に 1 回)、Sentinel は INFO##センチネル リーダーは、SLAVEOF コマンド (SLAVEOF
古いマスターを新しいマスターのスレーブとして設定し、監視を続けます。オンラインに戻ると、Sentinel はコマンドを実行して新しいマスターのスレーブにします。
デフォルトでは、sentinel はマスター、スレーブを送信します。 、および他のセンチネルは、相手がオフラインかどうかを判断するために PING コマンドを送信します。
センチネル リーダーは、特定のルールに従って選出されます。
Sentinel リーダーはフェイルオーバー操作を実行し、オフライン マスターを置き換える新しいマスターを選択します。
プログラミング関連の知識について詳しくは、
プログラミング入門をご覧ください。 !
以上がRedis における Sentinel フェイルオーバーの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。