Redis Sentinel

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实例启动时,它需要执行以下步骤:

  1. 初始化服务器
  2. 将普通Redis服务器使用的代扣替换为Sentinel专用代码
  3. 初始化Sentinel 状态
  4. 根据给定的配置文件,初始化Sentinel的监视主服务器列表
  5. 创建连向主服务器的网络连接

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记录了从服务器的端口号,根据ipport的信息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时,会检测当前监控的主服务的主观下线状态,并做出如下格式的回复:

  1. <down_state>
  2. <leader_runid>
  3. <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设计与实现》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • [toc]Sentinel是Redis的高可用解决方案,由一个或多个Sentinel实例组成的Sentinel系统...
    涵仔睡觉阅读 243评论 0 0
  • Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progre...
    你是妖怪吧阅读 880评论 0 0
  • Sentinel是Redis的高可用性解决方案,本文主要介绍Sentinel的初始化过程及其与一般Redis服务器...
    wenmingxing阅读 3,036评论 1 5
  • 你是 秋水长天 你是山中的一泓清泉 奏着悦耳的琴声 融化了皑皑白雪 你是海...
    秋水长天_42b2阅读 222评论 0 1
  • 你问我怎么写诗写诗嘛就要像那蒲公英种子飞到哪便落到哪 20160529
    嚼冰阅读 168评论 0 0