启动一个Sentinel可以使用命令:
redis-sentinel /path/to/your/sentinel.conf
或者:redis-server /path/to/your/sentinel.conf --sentinel
这两个命令的效果完全相同。
当一个sentinel启动时,它需要执行以下步骤:
1. 初始化服务器
2. 将普通redis服务器使用的代码替换成sentinel专用代码
3. 初始化sentinel状态
4. 根据给定的配置文件,初始化semtinel的监视主服务器列表
5. 创建连向主服务器的网络连接
因为sentinel执行的工作和普通redis服务器执行的工作不同,所以sentinel的初始化过程和普通redis服务器的初始化过程并不完全相同。
sentinel不会载入RDB文件
或者AOF文件
启动sentinel的第二个步骤就是将一部分普通redis 服务器使用的代码替换成sentinel专用代码。
端口:sentinel.c/REDIS_SENTINEL_PORT:26379
命令表:sentinel.c/sentinelcmds
在应用了sentinel的专用代码之后,接下来,服务器会初始化一个sentinel.c/sentinelState
结构,这个结构保存了服务器中所有和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选项设定的值
// 实例无响应多少毫秒之后才会被判断为主观下线
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的最后一步是创建连向被监视主服务器的网络连接,Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息
对于每个被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:
_sentinel_:hello
频道Sentinel默认会以每十秒一次
的频率。通过命令连接向被监视的主服务器发送INFO
命令,并通过分析INFO命令
的回复来获取主服务器的当前信息
通过分析主服务器返回的INFO
命令回复,Sentinel可以获取以下两方面的信息:
run_id
域记录的服务器运行ID,以及role
域记录的服务器角色根据run_id域
和role域
记录的信息,Sentinel将对主服务器的实例结构进行更新。
当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建新的实例结构之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接
根据INFO命令的回复,Sentinel会提取以下信息:
run_id
role
master_host
, 以及主服务器的端口号 master_port
master_link_status
slave_priority
slave_repl_offset
在默认情况下, Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:
publish _sentinel_:hello "
这条命令向服务器的_sentinel_:hello
频道发送了一条信息,信息的内容由多个参数组成:
s_
开头的参数记录的是Sentinel本身的信息,各个参数的意义如表:m_
开头的参数记录的则是主服务器的信息,各个参数的意义如表:如果Sentinel正在监视的是主服务器,那么这些参数记录的就是主服务器的信息;如果Sentinel正在监视的是从服务器,那么这些参数记录的是从服务器正在复制主服务器的信息当Sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令:subscribe _sentinel_:hello
Sentinel对_sentinel_:hello
频道的订阅会一直持续到Sentinel与服务器的连接断开为止。
当一个Sentinel从_sentinel_:hello
频道收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中的Sentinel IP地址
,Sentinel 端口号
,Sentinel运行ID
等八个参数,并进行以下检查:
Sentinel运行ID
和接收信息的Sentinel的运行ID
相同,那么说明这条信息是Sentinel自己发送的,Sentinel将丢弃这条信息,不做进一步处理Sentinel运行ID
和接收信息的Sentinel的运行ID
不相同,那么说明这条信息是监视同一个服务器的其他Sentinel发来的,接收信息的Sentinel将根据信息中的各个参数,对相应主服务器的实例结构进行更新Sentinel为主服务器创建的实例结构中的snetinels字典保存了除Sentinel本身之外,所有同样监视这个主服务器的其他Sentinel的资料:
ip:port
当一个sentinel接受到其他sentinel发来的信息时,目标sentinel会从信息中分析并提取出以下两方面参数:
根据信息中提取出的主服务器参数,目标sentinel会在自己的sentinel状态的masters字典中查找相应的主服务器实例结构,然后根据提取出的sentinel参数,检查主服务器实例结构的sentinels字典中,源sentinel的实例结构是否存在:
源sentinel
的实例结构已经存在,那么对源sentinel的实例结构进行更新源sentinel
的实例结构已经不存在,那么说明源sentinel是刚刚开始监视主服务器的新sentinel。目标sentinel会为源sentinel创建一个新的实例结构,并将这个结构添加到sentins字典里面当sentinel通过频道信息发现一个新的sentinel时,它不仅会为新sentinel在sentinels字典中创建相应的实例结构,还会创建一个连向新sentinel的命令连接,而新sentinel也同样会创建连向这个sentinel的命令连接,最终监视同一主服务器的多个sentinel将形成相互连接的网络
在默认情况下,sentinel会以每秒一次的频率向所有与它创建了命令连接的实例发送PING命令,并通过实例返回的PING命令回复来判断是否在线
sentinel配置文件中的down-after-nilliseconds
选项指定了sentinel判断实例进入主观下线所需的时间长度:如果一个实例在down-after-milliseconds
毫秒内,连续向sentinel返回无效回复,那么sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开sri_s_down
标识,以此来表示这个实例已经进入主观下线状态。
当sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态。
sentinel使用:
sentinel is-master-down-by-addr
命令询问其他sentinel是否同意主服务器已下线,命令中的各个参数的意义:
当一个sentinel接收到另一个sentinel发来的sentinel is-master-down-by
命令时,目标sentinel会分析并取出命令请求中包含的各个参数,并根据其中的主服务器IP和端口号,检查主服务器是否已下线。然后向源sentinel返回一个包含是哪个参数的multi bulk回复作为回复:
根据其他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 is-master-down-by-addr
命令,并且命令中的runid参数不是*
符号而是源sentinel的运行id时,这表示源sentinel要求目标sentinel将前者设置为后者的局部领头sentinelSENTINEL is-master-down-by-addr
命令之后,将向源Sentinel返回一条命令回复,回复中的leader_ runid
参数和leader_ epoch
参数分别记录了目标Sentinel的局部领头Sentinel 的运行ID和配置纪元。在选举产生领头sentinel之后,领头sentinel将对已下线的主服务器执行故障转移操作:
故障转移操作第一步要做的就是在已下线主服务器属下的所有从服务器中,挑选出一个状态良好,数据完整的从服务器,然后向这个从服务器发送slaveof no one
命令,将这个从服务器转换为主服务器
新的主服务器是怎么挑选出来的?
领头Sentinel会将已下线主服务器的所有从服务器保存到一个列表里面,然后按照以下规则,一项一项地对列表进行过滤:
- 删除列表中所有处于下线或者断线状态的从服务器,这可以保证列表中剩余的从服务器都是正常在线的。
- 删除列表中所有最近五秒内没有回复过领头Sentinel的
INFO
命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的。- 删除所有与已下线主服务器连接断开超过
down-after-milliseconds * 10
毫秒的从服务器:down-after-milliseconds
选项指定了判断主服务器下线所需的时间,而删除断开时长超过down-after-milliseconds * 10
毫秒的从服务器,则可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接,换句话说,列表中剩余的从服务器保存的数据都是比较新的。之后,领头Sentinel将根据从服务器的优先级,对列表中剩余的从服务器进行排序,并选出其中优先级最高的从服务器。如果有多个具有相同最高优先级的从服务器,那么领头Sentinel 将按照从服务器的复制偏移量,对具有相同最高优先级的所有从服务器进行排序,并选出其中偏移量最大的从服务器(复制偏移量最大的从服务器就是保存着最新数据的从服务器)。
最后,如果有多个优先级最高、复制偏移量最大的从服务器,那么领头Sentinel将按照运行ID
对这些从服务器进行排序,并选出其中运行ID
最小的从服务器。
通过向从服务器发送slaveof
命令来实现
当旧的主服务器上线之后,向他发送slaveof
命令