Sentinel(哨兵) 是redis的高可用性解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进行下线时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。然后由新的主服务器代替已下线的主服务器继续处理命令请求。
例如下图所示:
当Server1 掉线后:
升级Server2 为新的主服务器:
启动一个Sentinel可以使用命令:
redis-sentinel /path/to/your/sentinel.conf
或者:redis-server /path/to/your/sentinel.conf --sentinel
这两个命令执行的效果是一样的。
当一个Sentinel启动时它需要经历如下步骤:
因为sentinel执行的工作和普通redis服务器执行的工作不同,所以sentinel的初始化过程和普通redis服务器的初始化过程并不完全相同,Sentinel并不需要读取RDB/AOF文件来还原数据状态。
端口:sentinel.c/REDIS_SENTINEL_PORT:26379
命令表:sentinel.c/sentinelcmds
启动 Sentinel 的第二个步骤就是将一部分普通 Redis 服务器使用的代码替换成 Sentinel 专用代码。
比如说, 普通 Redis 服务器使用 redis.h/REDIS_SERVERPORT 常量的值作为服务器端口:
#define REDIS_SERVERPORT 6379
而 Sentinel 则使用 sentinel.c/REDIS_SENTINEL_PORT 常量的值作为服务器端口:
#define REDIS_SENTINEL_PORT 26379
普通 Redis 服务器使用 redis.c/redisCommandTable 作为服务器的命令表:
struct redisCommand redisCommandTable[] = {
{"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
{"set",setCommand,-3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
{"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
// ...
{"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0},
{"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0}
而 Sentinel 则使用 sentinel.c/sentinelcmds 作为服务器的命令表, 并且其中的 INFO 命令会使用 Sentinel 模式下的专用实现 sentinel.c/sentinelInfoCommand 函数, 而不是普通 Redis 服务器使用的实现 redis.c/infoCommand 函数:
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},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}
};
Sentinel用于较少的Redis命令,大部分命令在Sentinel客户端都不支持,并且Sentinel拥有一些特殊的功能,这些需要Sentinel在启动时将Redis服务器使用的代码替换为Sentinel的专用代码。在此期间Sentinel会载入与普通Redis服务器不同的命令表。
Sentinel不支持SET、DBSIZE等命令;保留支持PING、PSUBSCRIBE、SUBSCRIBE、UNSUBSCRIBE、INFO等指令;这些指令在Sentinel工作中提供了保障。
在应用了 Sentinel 的专用代码之后, 接下来, 服务器会初始化一个 sentinel.c/sentinelState 结构(后面简称“Sentinel 状态”), 这个结构保存了服务器中所有和 Sentinel 功能有关的状态。
struct sentinelState{
// 当前纪元,用于实现故障转移
uint64_t current_epoch;
// 保存了所有被这个sentinel监视的主服务器
// 字典的键是主服务器的名字
// 字典的值是一个指向sentinelRedisInstance和结构的指针
dict *masters;
// 是否进入了 TILT 模式?
int tilt;
// 目前正在执行时的脚本的数量
int running_scripts;
// 进入tilt模式的时间
mstime_t tilt_start_time;
// 最后一次执行时间处理器的时间
mstime_t previous_time;
// 一个fifo队列,包含了所有需要执行的用户脚本
list *scripts_queue;
}sentinel;
Sentinel状态中的masters字典记录了所有被sentinel监视的主服务器的相关信息,其中:
每个sentinelRedisInstance结构(后面简称“实例结构”)代表一个被Sentinel监视的服务器实例,这个实例可以是主服务器,从服务器,或者是另外一个sentinel。
实例结构属性包含:
typedef struct sentinelRedisInstance {
// 标识值,记录了实例的类型,以及该实例的当前状态
int flags;
// 实例的名字
// 主服务器的名字由用户在配置文件中设置
// 从服务器以及 Sentinel 的名字由 Sentinel 自动设置
// 格式为 ip:port ,例如 "127.0.0.1:26379"
char *name;
// 实例的运行 ID
char *runid;
// 配置纪元,用于实现故障转移
uint64_t config_epoch;
// 实例的地址
sentinelAddr *addr;
// SENTINEL down-after-milliseconds 选项设定的值
// 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)
mstime_t down_after_period;
// SENTINEL monitor 选项中的 quorum 参数
// 判断这个实例为客观下线(objectively down)所需的支持投票数量
int quorum;
// SENTINEL parallel-syncs 选项的值
// 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
int parallel_syncs;
// SENTINEL failover-timeout 选项的值
// 刷新故障迁移状态的最大时限
mstime_t failover_timeout;
// ...
} sentinelRedisInstance;
sentinelRedisInstance.addr属性是一个指向sentinelAddr结构的指针,这个结构保存着实例的IP地址和端口号
typedef struct sentinelAddr {
char *ip;
int port;
} sentinelAddr;
对Sentinel状态的初始化将引发对masters字典的初始化,而masters字典的初始化是根据被载入的Sentinel配置文件来进行的。
如果用户在启动 Sentinel 时, 指定了包含以下内容的配置文件:
#####################
# redis-master1 configure #
#####################
sentinel monitor redis-master1 127.0.0.1 6379 2
sentinel down-after-milliseconds redis-master1 30000
sentinel parallel-syncs redis-master1 1
sentinel failover-timeout redis-master1 900000
#####################
# redis-master2 configure #
#####################
sentinel monitor redis-master2 127.0.0.1 12345 5
sentinel down-after-milliseconds redis-master2 50000
sentinel parallel-syncs redis-master2 5
sentinel failover-timeout redis-master2 450000
那么 Sentinel 将为主服务器 redis-master1 创建如下图所示的实例结构:
并且为主服务器 redis-master2 创建如下图所示的实例结构:
这两个实例结构又会被保存到 Sentinel 状态的 masters 字典中, 如下图所示:
当实例结构初始化完成之后,Sentinel将会开始创建连接Master的网络连接,这一步Sentinel将成为Master的客户端。
Sentinel和Master之间会创建一个命令连接和一个订阅连接:
这里需要说一下为什么需要两个连接?
在 Redis 目前的发布与订阅功能中, 被发送的信息都不会保存在 Redis 服务器里面, 如果在信息发送时, 想要接收信息的客户端不在线或者断线, 那么这个客户端就会丢失这条信息。
因此, 为了不丢失 sentinel:hello 频道的任何信息, Sentinel 必须专门用一个订阅连接来接收该频道的信息。
而另一方面, 除了订阅频道之外, Sentinel 还又必须向主服务器发送命令, 以此来与主服务器进行通讯, 所以 Sentinel 还必须向主服务器创建命令连接。
并且因为 Sentinel 需要与多个实例创建多个网络连接, 所以 Sentinel 使用的是异步连接。
当Sentinel服务器初始化完成之后,会每10s向已经连接的被监视的服务器发送INFO命令,主服务器会返回自身的信息,以及它下面的从服务器的信息。
Sentinel也会去主动连接从服务器
sentinel默认会每10秒一次,给主服务器发送info命令,并且通过分析info命令,来判断当前主服务器的信息。
通过分析info命令,sentinel会得到两方面的信息:一是主服务器的运行ID以及其role域记录的服务器角色,二是关于主服务器属下所有从服务器的信息,由slave字符串开头,每行IP记录一个IP地址、端口号、offset(用于与主服务器aof)、lag(延迟时间)等。
因此,sentinel无序建立和从服务器的连接,也可以知道从服务器的情况。sentinel只需要将info获得的返回结果,分析并更新到sentinel的sentinelState结构体的相应属性即可。
另外,sentinel在更新结构体时,还会分析每一个从服务器是否存在,如果是现有的则更新结构体,如果不是现有的则新增一个结构体。
从上述可知,主从服务器sentinelRedisInstance的主要区别,一是flags属性主是SRI_MASTER而从是SRI_SLAVE;二是名称主服务器是sentinel起的,从服务器是IP:端口号;三是主服务器有个slaves属性,指向一个字典,该字典键是从服务器的IP:端口号,值是表示从服务器的sentinelRedisInstance。
当sentinel发现有新的从服务器,除了会为其创建实例结构,还会与其建立连接,连接也是包括订阅连接和命令连接。且默认下,每十秒也会给从服务器发送info命令。
根据info命令,可以提取出以下主要信息:运行ID、角色、主服务器ip与端口、主从连接状态、从服务器优先级、从服务器偏移量
在默认情况下, Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:
publish _sentinel_:hello ",,,,,,"
这条命令向服务器的_sentinel_:hello频道发送了一条信息,信息的内容由多个参数组成:
其中,以s_开头的参数记录的是Sentinel本身的信息,而m_开头的参数记录的则是主服务器的信息。
当Sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令:subscribe sentinel:hello,Sentinel对_sentinel_:hello频道的订阅会一直持续到Sentinel与服务器的连接断开为止。
当Sentinel从_sentinel_:hello频道接收到信息时,Sentinel会对这条信息进行分析,提取出信息中的 Sentinel IP地址,Sentinel 端口号,Sentinel 运行ID等八个参数,并进行以下检查:
Sentinel 为主服务器创建的实例结构中的 snetinels 字典保存了除 Sentinel 本身之外,所有同样监视这个主服务器的其他 Sentinel 信息,字典的键是sentinel的ip:port,值是对应的sentinel实例。
当一个 sentinel 接受到其他 sentinel 发来的信息时,目标 sentinel 会从信息中提取出以下两方面的参数:
根据信息中提取出的主服务器参数,目标sentinel会在自己的 sentinel 状态的 masters 字典中查找相应的主服务器实例结构,然后根据提取出的 sentinel 参数,检查主服务器实例结构的 sentinels 字典中,sentinel 的实例结构是否存在:
当sentinel通过频道信息发现一个新的sentinel时,它不仅会为新sentinel在sentinels字典中创建相应的实例结构,还会创建一个连向新sentinel的命令连接,而新sentinel也同样会创建连向这个sentinel的命令连接,最终监视同一主服务器的多个sentinel将形成相互连接的网络。
默认情况下,Sentinel 每隔1秒钟,向sentinelRedisInstance 实例中的所有 Master、Slave、Sentinel 发送 PING 命令,通过其他服务器的回复来判断其是否仍然在线。
sentinel down-after-milliseconds redis-master 50000
在Sentinel 的配置文件中,down-after-nilliseconds 选项指定了 sentinel 判断实例进入主观下线所需的时间长度,实例在 down-after-milliseconds 毫秒内,连续向 sentinel 返回无效回复,那么 sentinel 会修改这个实例所对应的实例结构,在结构的 flags 属性中打开 sri_s_down 标识,以此来表示这个实例已经进入主观下线状态。
down-after-nilliseconds选项需要注意的是:
对于监视同一个主服务器的多个Sentinel来说,这些 Sentinel 所设置的 down-after-nilliseconds 选项的值也可能不同,因此,当一个 Sentinel 将主服务器判断为主观下线时,其他 Sentinel 可能仍然认为主服务器处于在线的状态,比如:
Sentinel1 的配置:
sentinel monitor master 127.0.0.1 6379 2
sentinel down-after-milliseconds master 50000
而 Sentinel2 的配置:
sentinel monitor master 127.0.0.1 6379 2
sentinel down-after-milliseconds master 10000
那么当 master 的断线时长超过 10000 毫秒之后,Sentinel2 会将 master 判断为主观下线,而Sentinel1 认为 master 仍然在线。只有当 master 的断线时长超过 50000 毫秒之后,Sentinel1 和Sentinel2 才会都认为 master 进入了主观下线状态。
当前Sentinel认为其下线只能处于主观下线状态,要想判断当前Master是否客观下线,还需要询问其他Sentinel,并且所有认为Master主观下线或者客观下线的总和需要达到quorum配置的值,当前Sentinel才会将Master标志为客观下线。
当前Sentinel向sentinelRedisInstance实例中的其他Sentinel发送如下命令:
sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
命令询问其他sentinel是否同意主服务器已下线,如下图参数:
接收到命令的 Sentinel,会根据命令中的参数检查主服务器是否下线,检查完成后会返回如下图参数:
根据其他 sentinel 发回的命令回复,sentinel 将统计其他 sentinel 同意主服务器已下线的数量,当这一数量达到配置指定的判断客观下线所需的数量时,sentinel 会将主服务器实例结构 flags 属性的 SRI_O_DOWN 标识打开,表示主服务器已经进入客观下线状态。
客观下线状态的判断条件::
当认为主服务器已经进入下线状态的 Sentinel 的数量,超过 Sentinel 配置中设置的 quorum 参数的值,那么该Sentinel就会认为主服务器已经进入客观下线状态。比如说,如果 Sentinel 在启动时载入了以下配置:sentinel monitor master 127.0.0.1 6379 2,那么包括当前 Sentinel 在内,只要总共有两个 Sentinel 认为主服务器已经进入下线状态,那么当前 Sentinel 就将主服务器判断为客观下线。
又比如说,如果 Sentinel 在启动时载入了以下配置:sentinel monitor master 127.0.0.1 6379 5
那么包括当前 Sentinel 在内,总共要有五个 Sentinel 都认为主服务器已经下线,当前 Sentinel 才会将主服务器判断为客观下线。
当一个主服务器被判断为客户下线后,监视这个下线主服务器的各个sentinel 会进行协商,选举出一个领头sentinel,并由领头 sentinel 对下线主服务器进行故障转移操作:
以下是选举领头 sentinel 的规则和方法
选出的领头 Sentinel 开始执行故障转移,操作分为三个步骤:
故障转移操作第一步要做的就是在已下线主服务器属下的所有从服务器中,挑选出一个状态良好,数据完整的从服务器,然后向这个从服务器发送 slaveof no one 命令,将这个从服务器转换为主服务器。
新的主服务器是怎么挑选出来的?
领头Sentinel会将已下线主服务器的所有从服务器保存到一个列表里面,然后按照以下规则,一项一项地对列表进行过滤:
通过向从服务器发送 slaveof 命令来实现
当旧的主服务器上线之后,向他发送 slaveof 命令