Amazing ArchOpsDevAmazing Redis

(六)Redis的哨兵(Sentinel)机制

2019-04-15  本文已影响48人  纸中圆

  当主机宕机后出现故障无法及时恢复,可以在从机执行slave no one命令使其上位变为主机,其他主机会自动跟随,原主机恢复后也会变为从机跟随新的主机。但这样的人工操作带来一个问题,难道半夜主机宕机还要通知运维起来输命令吗?这显然不符合常理,而且有个弊端,即宕机后的主机恢复后就是孤零零的一个普通服务器,主从服务器中就少了一个服务器,降低了集体性能。为了自动化管理这个过程,引入了哨兵机制。

思维导图

什么是哨兵(Sentinel)?

  简单来讲,Sentinel是一个特殊的Redis服务器,负责监控其他服务器的在线状态
  复杂一点来说,Sentinel(哨兵、哨岗)是Redis的高可用性解决方案:由一个个Sentinel组成的Sentinel系统(至少由3个哨兵组成)可以监视多个主服务器以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。如下图:

哨兵系统监视主从服务器

  因为有哨兵机制,所以当主服务器发生故障时,会发生下面的过程:


  当然其中的往细了讲挺复杂的,比如新的主服务器该选哪个从服务器啊,旧主服务器怎么变成新主服务器的从服务器啊等等各种问题具体实现。简单来说,哨兵建立的过程包括:启动并初始化哨兵、获取主/从服务器信息、检测主观/客观下线状态、故障转移工作。

哨兵建立及后续分析

启动并初始化Sentinel

  启动一个Sentinel需要使用redis-sentinel命令:

redis-sentinel /usr/locat/etc/redis-sentinel.conf

  其中redis-sentinel.confSentinel的配置信息,和Redis提供的默认redis.conf文件一样,Redis也提供了一份默认的名为redis-sentinel.conf的哨兵配置文件,部分内容如下:

port 26379  //端口号
daemonize no  //是否守护

sentinel monitor mymaster 127.0.0.1 6379 2  
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1

  sentinel monitor mymaster 127.0.0.1 6379 2代表哨兵监控名为mymaster的主服务器,它位于地址127.0.0.1和端口6379,仲裁人数为2,仲裁代表当主服务器挂掉后:

  down-after-milliseconds:主服务器主观下线时间
  failover-timeout
  parallel-syncs

初始化的Sentinel

  因为Sentinel本质上是一个运行在特殊模式的Redis服务器,所以启动的第一步,就是初始化一个普通的Redis服务器,但它的初始化过程和普通服务器的初始过程不同,它们功能不同:

普通Redis服务器和Sentinel服务器
使用Sentinel专用代码

  启动Sentinel的第二步就是将一部分Redis服务器使用的代码换成Sentinel的专用代码。比如普通服务器的getsetsetnx等等命令不再使用,而是使用它专属的代码处理其他事情,如sentinelsubscribeinfo等等命令负责监控相关的功能。本质就是原先某些函数库不再使用,而是使用新的函数库,毕竟旧功能咱用不到,新的功能普通Redis服务器原来没有,而Sentinel就加上,如下图:
  这也解释了为什么Sentinel为什么不能执行原先Redis服务器里的setget等等命令,因为Sentinel根本没有载入这些命令

初始化Sentinel状态结构

  在应用了Sentinel的专用代码之后,接下来,服务器会初始化一个叫做哨兵状态(sentinelState)的结构,保存了服务器中所有和Sentinel功能相关的状态。

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

  初始Sentinel的最后一步是创建与被监视主服务器的网络连接,Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中回去相关的信息。
  对于每个被Sentinel监视的主服务器,Sentinel都会创建2个与主服务器的异步网络连接:

获取主/从服务器信息

闻一知三

  如上图,对主服务器:Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送info命令来获取主服务器的当前信息(包括主服务器本身的信息,及其属下所有从服务器的信息),根据这些信息来更新主服务器的实例结构(若主服务器重启的ID将和实例结构不一致,将其更新)及从服务器的实例结构。

万千触刃

  如上图,对从服务器:Sentinel发现主服务器有新的从服务器出现时,其出了会为新的从服务器创建对应的实例结构之外,还会创建连接到从服务器的命令连接和订阅连接。
  在创建命令连接,Sentinel在默认情况下,以每十秒一次的频率通过命令连接向从服务器发送info命令,获取其信息,并对从服务器的实例结构进行更新。

向主/从服务器发送信息到频道

  默认情况下,Sentinel会以每两秒一次的频率,通过命令向所有被监控的主和从服务器发送以下格式的命令:


  这个命令包含的信息包括Sentinel本身的信息,也包括主服务器的信息。举个例子,一个Sentinel通过publish命令向主服务器发送的信息实例:
"127.0.0.1,26379,e955b4c85598ef5b5f055bc7ebfd5e828dbed4fa,0,mymaster,127.0.0.1,6379,0"

  这些信息包含:

接受主/从服务器的频道信息

  当Sentinel与一个主服务器或从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令:

SUBSCRIBE _sentinel_:hello

  Sentinel_sentinel_:hello频道的订阅会一致持续到Sentinel与服务器的连接断开为止。
  这也就是说,对于每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的_sentinel_:hello频道发送信息,又通过订阅连接服务器的_sentinel_:hello频道接受信息,如下图:

Sentinel同时向服务器发送和接受信息

  对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel接收到,这些信息会被其他Sentinel用于更新对发送信息的Sentinel的认知,也会被其他Sentinel用于更新对被监视服务器的认知,如下图:

向服务器发送信息
  当一个Sentinel_sentinel_:hello频道接收到一条信息时,会对该信息进行分析(提取信息中的SentinelIP、port、run ID等等信息),并进行下列检查:
更新sentinels字典

  Sentinel为主服务器创建的实例结构中的sentinels字典除了保存Sentinel本身之外,还保存了所有同样监视这个主服务器的其他Sentinel资料(sentinels字典的键为Sentinel名字,值为对应的Sentinel的实例结构)
  当一个Sentinel(目标Sentinel)接收到其他Sentinel(Sentinel)发来的信息时,目标Sentinel会从信息分析并提取相关参数(Sentinel相关参数如IP、端口号、run ID和配置纪元,被监视主服务器参数如名字、IP、端口号和配置纪元),对自身的实例结构进行更新

创建连向其他Sentinel的命令连接

  当Sentinel通过频道信息发现一个新的Sentinel时,它不仅会为新Sentinelsentinels字典中创建相应的实例结构,还会创建一个连接向新Sentinel的命令连接,而新Sentinel也会创建与这个Sentinel的命令连接,最后监视同一主服务器的多个Sentinel形成一个相互连接的网络,如下图:

各个Sentinel之间的网络连接

注:``Sentinel在连接主服务器或这从服务器时,会同时创建命令连接和订阅连接,但连接其他Sentinel时,只创建命令连接。因为Sentinel需要通过接受主服务器或从服务器发来的频道信息来发现未知的新Sentinel,所以需要建立订阅连接,而相互已知的Sentinel只要使用命令连接来进行通信就足够了。

检测主观/客观下线状态

  在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括所有主/从服务器,所有其他Sentinel)发送ping命令,并通过实例返回的有效或无效回复来判断实例是否在线。

什么是主观下线状态?

  前面配置文件中的down-after-milliseconds选项指定了Sentinel判断实例进入主观下线的时间长短,如果在该时间内实例连续向Sentinel发送无效回复,则Sentinel会修改这个实例的实例结构,来表示这个实例已经进入主观下线状态。
  举个例子,如果配置文件指定的down-after-milliseconds的选项值为50000毫秒,那么当主服务器连续50000毫秒都返回给Sentinel无效回复是,Sentinel就会将主服务器实例结构修改并标记为主观下线。
  而且配置文件指定的down-after-milliseconds选项也会被Sentinel用来判断主服务器下所有从服务器的主观下线限时时间标准。
  哦,对了,因为基本不可能只有一个Sentinel,所以其他Sentinel也会执行上面的判断,只不过判断的down-after-milliseconds时间可能不同,因为配置文件设置的不同。所以可能会有这个Sentinel判断主服务器50000毫秒就主观下线了,而另一个Sentinel却判断主服务器是70000秒才主观下线的情况发生。

什么是客观下线状态?

  当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入下线状态(可以是主观下线或客观下线)。当Sentinel从其他Sentinel那里接收到足够数量(总数为设置的仲裁值)的已下线判断之后,Sentinel就会将服务器判断为客观下线,并对主服务器执行故障转移操作。

选举领头Sentinel

  当一个主服务器被判断为客观下线时,监视这个现象主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移工作,理所当然选举会基于一定规则,此处不介绍。

故障转移

  在选举出领头Sentinel之后,其将对已下线的主服务器执行故障转移操作,包含3个步骤:
1):在已下线的主服务器下的所有从服务器基于一定挑选一个将其转换为主服务器(根据从服务器权值大小、复制偏移量大小等规则挑选);
2):让已下线主服务器下的所有从服务器改为复制新的主服务器;
3):将已下线主服务器设置为新的主服务器的从服务器,当这个旧主服务器上线时就会变成新的主服务器的从服务器
  具体过程如下图:

故障转移流程

在Redis服务器中使用哨兵

  哨兵使用前提:至少需要3个Redis服务器(一主二仆)。哨兵的使用有以下步骤:

cd /usr/local/etc/master-slave

touch redis-sentinel1.conf
sentinel monitor mymaster 127.0.0.1 6380 1
redis-sentinel /usr/local/etc/master-slave /redis-sentinel1.conf

启动界面如下:

31682:X 12 Apr 2019 15:19:27.937 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
31682:X 12 Apr 2019 15:19:27.937 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=31682, just started
31682:X 12 Apr 2019 15:19:27.937 # Configuration loaded
31682:X 12 Apr 2019 15:19:27.939 * Increased maximum number of open files to 10032 (it was originally set to 256).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 5.0.4 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 31682
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

31682:X 12 Apr 2019 15:19:27.940 # Sentinel ID is 6712a29afc6e6e776bb6aaa661d087ceb8170e89
31682:X 12 Apr 2019 15:19:27.940 # +monitor master mymaster 127.0.0.1 6380 quorum 1
31682:X 12 Apr 2019 15:19:27.940 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:19:27.941 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380

  很显然,哨兵监控了主服务器,并知道了其从服务器信息,当主机宕机后(模拟主机关闭):

127.0.0.1:6380> shutdown

  过了一段时间后,哨兵日志有如下内容:

31682:X 12 Apr 2019 15:21:14.993 # +sdown master mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:14.993 # +odown master mymaster 127.0.0.1 6380 #quorum 1/1
31682:X 12 Apr 2019 15:21:14.993 # +new-epoch 1
31682:X 12 Apr 2019 15:21:14.993 # +try-failover master mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:14.996 # +vote-for-leader 6712a29afc6e6e776bb6aaa661d087ceb8170e89 1
31682:X 12 Apr 2019 15:21:14.996 # +elected-leader master mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:14.996 # +failover-state-select-slave master mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:15.064 # +selected-slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:15.064 * +failover-state-send-slaveof-noone slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:15.130 * +failover-state-wait-promotion slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:15.566 # +promoted-slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:15.566 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:15.638 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:16.588 * +slave-reconf-inprog slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:16.588 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:16.692 # +failover-end master mymaster 127.0.0.1 6380
31682:X 12 Apr 2019 15:21:16.692 # +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6382
31682:X 12 Apr 2019 15:21:16.692 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6382
31682:X 12 Apr 2019 15:21:16.692 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382
31682:X 12 Apr 2019 15:21:46.776 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382

  可以看到,哨兵将本地6382端口的从机升级为了主机:

127.0.0.1:6382> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=25036,lag=1
master_replid:9a87c8ae6ea203b87991d4edb3584b3f83e73439
master_replid2:a5183310e466a5a9e7d2c465a1eea051575ab130
master_repl_offset:25036
second_repl_offset:24482
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:25036

  而6381变为了其从机:

127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6382
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:25316
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:9a87c8ae6ea203b87991d4edb3584b3f83e73439
master_replid2:a5183310e466a5a9e7d2c465a1eea051575ab130
master_repl_offset:25316
second_repl_offset:24482
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:25316

  当旧主机重新上线后,变为6382的从机:

127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6382
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:43259
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:9a87c8ae6ea203b87991d4edb3584b3f83e73439
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:43259
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:43104
repl_backlog_histlen:156

参考资料

《redis设计与实现》(第二版)

上一篇下一篇

猜你喜欢

热点阅读