1.概述
Sentinel(哨岗、哨兵)是Redis高可用性的解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这个主服务器下的从服务器,并在被监视主服务器下线时,自动将下线主服务器下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
Sentinel系统
当主服务器下线时,Sentinel会采取如下操作:
主服务器下线
Sentinel会选出一个从服务器作为新的主服务器,假设此时选出的从服务器为:从服务器2,此时系统会如下图所示:
新主服务器上线
当已下线的主服务器重新上线后,变为如下所示:
下线的主服务器重新上线
2.启动并初始化Sentinel
启动Sentinel可以使用命令:
redis-sentinel /path/to/your/sentinel.conf
#或者命令
redis-server /path/to/your/sentinel.conf --sentinel
这两个命令的效果完全一样。
一个Sentinel实例启动时,它需要执行以下步骤:
- 初始化服务器
- 将普通Redis服务器使用的代扣替换为Sentinel专用代码
- 初始化Sentinel 状态
- 根据给定的配置文件,初始化Sentinel的监视主服务器列表
- 创建连向主服务器的网络连接
2.1 初始化服务器
Sentinel本质上是一个运行在特殊模式下的Redis服务器,Sentinel的启动第一步就是启动一个普通的Redis服务器。但是Sentinel和普通Redis服务器执行的工作不一样,所以Sentinel的初始化过程和普通Redis服务器并不完全相同。
Sentinel在初始化时,不需要载入RDB文件或者AOF文件。
并且Sentinel向外提供的命令和普通Redis服务器也不是完全一样的,像SET这一类命令Sentinel是没有的。
2.2 使用Sentinel专用代码
- 使用与普通Redis服务器不同的默认端口号
- 载入Sentinel需要使用的命令列表
Sentinle支持:PING、SENTINEL、INFO、SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE和PUNSUBSCRIBE这七个命令
2.3 初始化Sentinel状态
在应用了Sentinel的专用代码之后,接下来服务器会初始化一个snetinel.c/sentinelState的结构,这个结构中保存了服务器中所有和Sentinel功能相关的状态。
比较重要的一个属性:
#保存了所有这个Sentinle监视的主服务器
#字典的键是主服务器的名字
#字典的值则是一个指向sentinelRedisInstance结构的指针
dict *masters;
2.4 初始化Sentinel状态的masters属性
对Sentinel状态的初始化会引起对于masters字典的初始化,即初始化Sentinel监控的所有主服务器,它是根据Sentinel的配置文件来进行初始化的。
配置文件示例如下:
# master1 configure
sentinel monitor master1 127.0.0.1 6379 2
sentinel down-after-milliseconds master1 30000
sentinel parallel-syncs master1 1
sentinel failover-timeout master1 900000
# master2 configure
sentinel monitor master2 127.0.0.1 6379 2
sentinel down-after-milliseconds master2 30000
sentinel parallel-syncs master2 1
sentinel failover-timeout master2 900000
此时Sentinel将主服务器master1会创建为如下结构:
master1结构
此时Sentinel状态的结构如下所示:
Sentinel状态的结构
2.5 创建连向主服务器的连接
初始化Sentinel的最后一步是创建连向主服务器的网络连接。Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关信息。
Sentinel对每个被监视的主服务器会创建两个异步网络连接:
- 命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复
- 订阅连接,这个连接专门用于订阅主服务器的sentinel:hello 频道
为什么使用两个连接?
Redis在使用订阅功能时,如果在发送消息时,想要接收信息的客户端不在线或者断线,那么这个客户端就会丢失这条消息,因此为了不丢失sentinel:hello 频道的任何信息,sentinel必须开通一条专门的连接来接收该频道的消息。但是由于Snetinel还必须向主服务器发送命令来获取主服务器的相关信息,因此必须再开通一条命令连接。
因为Sentinel需要与多个实例创建多个网络连接,所有Sentinel使用的异步连接
如下图展示了一个Snetinel与两个master建立连接的情况:
Snetinel与两个master建立连接
3.获取主服务器信息
Sentinel会以每10秒一次的频率,通过命令连接向被监视的主服务器发送INFO
命令,并通过分析INFO
的回复来获取主服务器当前的状态。
通过分析INFO
的回复,Sentinel可以获取以下两个方面的信息:
- 主服务器本身的信息,包括run_id域记录的服务器运行ID,以及role域记录的服务器角色
- 主服务器下从服务器的信息,每个从服务器都由一个
slave
字符串开头的行记录,每行的ip
记录了从服务器的IP地址,port
记录了从服务器的端口号,根据ip
和port
的信息Sentinel无需用户来配置从服务器信息,即可自动发现从服务器。
根据run_id和role记录的信息,Sentinel对主服务器的实例进行更新。
从服务器的信息会更新至主服务器实例结构中的slaves
字典中,这个字典记录了主服务器下从服务器的名单:
- 字典的键是由Sentinel自动设置的从服务器的名字,格式为:ip:port
- 字典的值则是对应从服务器的实例结构。
具体结构如下图所示:
Sentinel主服务器实例中带有从服务器信息
注意:从服务器的flags
值为SRI_SLAVE
4.获取从服务器信息
当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实体结构之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。
在创建命令连接后,会以每10秒一次的频率发送INFO
命令,并解析返回的信息。提取出如下信息对从服务器实例进行更新:
- 从服务器运行run_id
- 从服务器角色role
- 主服务器的ip地址和端口号
- 主服务器的连接状态:master_link_status
- 从服务器器优先级:slave_priority
- 从服务器的复制偏移量:slave_repl_offset
更新后从服务器结构如下所示:
从服务器结构
5.向主服务器和从服务器发送消息
默认情况下Sentinel会以两秒每次的频率,通过命令连接向所有被监视的主服务器和从服务器发送如下格式的命令:
#s开头的参数是Sentinel的信息
#m开头的信息是主服务器的信息:如果Sentinel正在监视的为主服务器那么就是主服务器自身的信息;
#如果Sentinel监视的是从服务器那么就是从服务器复制的主服务器的信息
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的port |
s_runid | Sentinel的runid |
s_epoch | Sentinel当前的配置纪元(在选举领头Sentinel时使用) |
m_name | 主服务器的名字 |
m_ip | 主服务器的ip |
m_port | 主服务器的port |
m_epoch | 主服务器的配置纪元 |
6.接收来自主服务器和从服务器的频道信息
Sentinel当与一个主服务器或从服务器建立器订阅连接之后,Sentinel就会通过订阅连接向服务器发送以下命令来进行订阅消息:
SUBSCRIBE __sentinel__:hello
Senetinel会一直对sentinel:hello继续订阅直到Sentinel与服务器断开连接为止。也就是说Sentinel既会通过命令连接向服务器发送sentinel:hello消息又会通过订阅连接从服务器接收消息。
对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的消息会被其他Sentinel接收到,这些信息会被用于更新其他Sentinel对于发送消息的Sentnel的认知,也会被用于更新其他Sentinel对于被监视服务的认知。
当一个Sentinel从sentinel:hello收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中的<s_ip>,<s_port>,<s_runid>等上面提到的8个参数:
- 如果记录的<s_ip>与当前Sentinel一致,那么说明是自身发送的消息, 那么会丢弃这条消息
- 如果不一致,那么说明还有另外一个Sentinel在监视同一个服务器,接收消息的Sentinel会对器监视的主服务器实例结构中的sentinels字典进行更新
6.1 更新sentinels字典
- sentinels字典中键为Sentinel的名字,格式:ip:port
- sentinels字典中值为对应的Sentinel的实例结构
具体如下图所示:
sentinels结构
6.2 创建连接其他Sentinel的命令连接
Sentinel与Sentinel之间只会创建命令连接,不会创建订阅连接,因为Sentinel需要通过接收主服务器和从服务器的订阅信息来发现未知的Sentinel,对于相互已知的Sentinel不需要再建立订阅连接来进行通信。
7.检测主观下线状态
默认情况下Sentinel会向其监控的服务器(主服务器、从服务器、Sentinel)以每秒一次的频率发送PING命令,并通过PING命令的回复来判断具体实例的在线状态。实例回复可以分为以下两种情况:
- 有效回复:+PONG、-LOADING、-MASTERDOWN三种回复中的其中一种
- 无效回复:+PONG、-LOADING、-MASTERDOWN三种回复之外的回复,或者在指定时间内没有回复
Sentinel的配置文件中的down-after-milliseconds
指定了Sentinel判断实例进入主观下线的时间长度:如果一个实例在down-after-milliseconds
毫秒内连续向Sentinel返回无效回复,那么Sentinel会修改这个实例对应的实例结构,在flags
属性中打开SRI_S_DOWN
标识,以此来表示实例进入主观下线状态。
具体如下:
Sentinel的配置文件中的down-after-milliseconds
不但用来判断主服务器的主观下线时间,还用于此主服务器下所有的从服务器的主观下线状态的判断。
每个Senttinel配置文件中down-after-milliseconds
的配置不一定会一样,因此对于同一个服务器而言,一个Sentinel认为其下线但是另外一个Sentinel可能没有认为其主观下线。
8.检测客观下线状态
当Sentinel将一个主服务器检测为主观下线后,为确认这个主服务器是否真的下线,它会向同时在监控这台主服务器的其他Sentinel进行询问,看他们是否也认为服务器进入下线状态(可以是主观下线或客观下线),如果Sentinel从其他Sentinel哪里接收到足够的数量的已下线判断后,Sentinel就会将主服务器判定为客观下线,对其执行故障转移
检测客观下线主要分以下三步:
8.1 发送SENTINEL is-master-down-by-addr命令
命令格式:
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 |
82. 接收SENTINEL is-master-down-by-addr命令
当一个Sentinel接收到另一个Sentinel发送的SENTINEL is-master-down-by-addr
时,会检测当前监控的主服务的主观下线状态,并做出如下格式的回复:
- <down_state>
- <leader_runid>
- <leader_epoch>
具体参数含义:
参数 | 含义 |
---|---|
down_state | 目标Sentinel主服务器主观下线状态:1代表主服务已下线,0代表主服务器未下线 |
leader_runid | 可以为或目标Sentinel的局部领头Sentinel的运行ID;当为时,代表命令仅用于检测主服务器客观下线状态;当为目标Sentinel的局部领头Sentinel的运行ID时,则用于选举领头Sentinel |
leader_epoch | 目标Sentinel局部领头Sentinel的配置纪元,仅在leader_runid部位时有效,如果leader_runid为,则leader_epoch总是为0 |
8.3 接收SENTINEL is-master-down-by-addr命令回复
根据其他Sentinel返回的回复,Sentinel将统计其他Sentinel同意主服务器已下线的数量,这一数量达到配置指定的判断客观下线所需的数量时,Sentinel会将主服务器实例结构中的flags
属性的SRI_O_DOWN
打开,表示主服务器已进入客观下线状态。具体如下图所示:
配置文件中对于客观下线的配置:
#此表示表示总共需要两个Sentinel认为主服务器已进入主观下线状态,那么就可以判断主服务为客观下线
sentinel monitor master 127.0.0.1 6379 2
另:不同的Sentinel配置文件不同,因此对于同一主服务器认为其客观下线的判断也不一样。
9.选举领头Sentinel
当一个主服务器被判定为主观下线时,监控这个主服务器的各个Sentinel会进行协商,选举一个领头Sentinel,并由选举出的这个领头Sentinel对主服务器进行故障转移。
具体选举步骤如下:
1)所有在线Sentinel都有被选为领头Sentinel的资格
2) 每次进行领头Sentinel选举之后,无论选举是否成功,所有Sentinel的配置纪元值都会自增一次,配置纪元实际上就是一个计数器,并无其他特别之处
3) 在一个配置纪元里面,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头Sentinel一旦设置,在这个配置纪元里面就不能在更改
4)每个发现主服务器客观下线
的Sentinel都会要求其他Sentinel将其设置为局部领头Sentinel
5)当一个Sentinel向另一个Sentinel(目标)发送SENTINEL is-master-down-by-addr时,并且命令中runid不为*而是源Sentinel自身的runid时,即表示源Sentinel要求目标Sentinel将其设置为自己的局部领头Sentinel。设置局部领头Sentinel的规则是先到先到,最先向目标Sentinel发送命令的源Sentinel被成功设置为目标Sentinel的局部领头Sentinel。
6) 目标Sentinel在接收SENTINEL is-master-down-by-addr命令后,会向源Sentinel返回一条命令回复,回复中的<leader_runid>和<leader_epoch>分别记录了被成功设置为自身局部领头Sentinel的runid和epoch,源Sentinel在收到目标Sentinel返回的命令回复后,会对目标Sentinel回复的<leader_runid>和<leader_epoch>进行检查,如果都与自身信息一致则表示目标Sentinel将自身设置为局部领头Sentinel
7) 如果有半数以上的Sentinel将源Sentinel设置为局部领头Sentinel,那么这个Sentinel就成为领头Sentinel。因为需要半数以上,因此每次最多会有一个领个Sentinel被选举出。
8) 如果在给定的时间内,没有选出领头Sentinel,那么各个Sentinel将在一段时间后再次进行选举,直到选出领头Sentinel
10.故障转移
在选出领头Sentinel之后,该Sentinel会对被判定为客观下线的主服务器执行故障转移:
1) 在已下线的主服务器属下的从服务器里面挑选一个从服务器,并将其转换为从服务器。
2) 让已下线的主服务器下的剩余的从服务器改为复制新的主服务器
3) 将已下线的主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它会成为新的主服务器的从服务器
10.1 选出新的主服务器
领头Sentinel挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送SLAVEOF no one
命令,将这个从服务器转换为主服务器。
具体挑选从服务器的规则如下:
- 删除列表中所有处于下线或断线状态的从服务器
- 删除最近5秒内没有回复过领头Sentinel的INFO命令的从服务器
- 删除所有与已下线主服务器连接断开查过 down-after-milliseconds*10时间的从服务器
- 根据从服务器的优先级进行排序
- 如果有多个同优先级的从服务器,那么按照复制偏移量进行排序
- 如果复制偏移量相同的从服务器出现多台,那么将从服务器的运行id进行排序,并选出运行id最小的从服务器
11.参考资料
《Redis设计与实现》
网友评论