上一篇 咱们模拟了单机器下哨兵模式的搭建,那么接下来咱们看下哨兵模式的实现与工做。html
为何又分红两篇呢?由于篇幅太长(偷懒),再一个这篇主要说的是Sentinel的初始化以及信息交换,下一篇着重说下状态检查、Sentinel头领选举与故障转移 。redis
启动并初始化Sentinel服务器
当一个Sentinel启动时,须要执行如下步骤:网络
(1)初始化服务器。异步
由于Sentinel本事上是一个运行在特殊模式下的Redis服务器,因此启动时的第一步也就是初始化一个普通的Redis服务器。不一样点是在初始化的时候不会载入RDB或AOF文件。函数
(2)将普通的Redis服务器使用的代码替换成Sentinel专用代码。学习
端口替换Redis服务器使用redis.h/REDIS_SERVERPORT(6379)常量做为服务器端口,而Sentinel则使用sentinel.c/REDIS_SENTINEL_PORT(26379)常量做为服务器端口。ui
另外Redis服务器使用redisCommandTable服务器做为命令表,而Sentinel则使用sentinelcmds做为服务器的命令表,而且其中的info命令会使用Sentinel模式下专用的sentinelInfoCommand函数,而不是普通redis服务器使用的redis.c/infoCommand函数。spa
(3)初始化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; //一个FIFO队列,包含了全部须要执行的用户脚本 list *scripts_queue; } sentinel;
(4)根据给定的配置文件,初始化Sentinel的监视主服务器列表。
sentinelRedisInstance结构(实例结构)
typedef struct sentinelRedisInstance{ //标识值,记录了实例的类型以及该实例的当前状态 int flag; //实例的名称(主服务器由用户配置,从服务器自动设置ip:port) char *name; //实例的运行id char *runid; //配置纪元,用户实现故障转移 uint64_t config_epoch; //实例的地址 //sentinelAddr *addr; //实例无相应多少毫秒以后才会被判断主观下线 mstime_t down_after_period‘; //判断这个实例为客观下线所须要支持的投票数量 int quorun; //在执行故障转移操做时,能够同时对新的主服务器进行同步的从服务器数量 int parallel_syncs; //刷新故障迁移状态的最大时间限制 mstime_t fallover_timeout; } sentinelRedisInstance;
*addr 指向sentinelAddr结构:
typedef struct sentinelAddr{ char *ip; int port; } sentinelAddr;
根据用户的配置初始化实例结构:
#名称 ip port
sentinel monitor mymaster 127.0.0.1 6379 2
#实例在相应多少毫秒以后才会被判断主观下线
sentinel down-after-milliseconds mymaster 3000
sentinel failover-timeout mymaster 10000
#在执行故障转移操做时,能够同时对新的主服务器进行同步的从服务器数量
sentinel parallel-syncs mymaster 1
(5)建立连向主服务器的网络链接。
Sentinel将称为主服务器的客户端,它能够向主服务器发送命令,并从命令回复中获取相关的信息:对于被Sentinel监视的主服务器来讲,Sentinel会建立两个连向主服务器的异步网络链接,一个是命令链接这个链接专门用于向主服务器发送命令,并接收命令回复;另外一个是订阅链接这个链接专门用于订阅主服务器的_sentinel_:hollo频道。
获取主服务器信息
sentinel默认会以每十秒一次的频率,经过命令链接向被监视的主服务器发送INFO命令,并经过分析INFO命令的回复来获取主服务器的当前信息。
经过分析主服务器返回的INFO命令回复,Sentinel能够获取如下两个方面的信息:
一方面是关于主服务器自己的信息,包括run_id域记录的服务器运行ID以及role域记录的服务器角色;另外一方面是关于主服务器属下的全部从服务器信息,每一个从服务器由一个“slave”字符串开头的行记录,每行的ip=域记录了从服务器的IP地址,而port=域则记录了从服务器的端口号。根据这些IP地址和端口号,Sentinel无须用户提供从服务器的地址信息,就能够自动发现从服务器。
主服务器 实力结构的flags属性的值为SRI_MASTER,而从服务器实例结构的flags属性的值为SRI_SLAVE。
当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器建立相应的实例结构外,Sentinel还会建立链接到从服务器的命令链接和订阅链接。
在默认状况下,Sentinel会以每两秒一次的频率,经过命令链接向全部被监视的主服务器和从服务器发送如下格式的命令,PUBLISH _sentinel_:hello "< s_ip > < s_port >< s_runid >< s_epoch > < m_name > < m_ip >< m_ip ><m_epoch>"。
接收来自主服务器和从服务器的频道信息
当Sentinel与一个主服务器或者从服务器创建起订阅链接以后,Sentinel就会经过订阅链接,向服务器发送如下命令:SUBSCRIBE __semtomel__:hello
当一个Sentinel从__sentinel__:hello频道收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中的Sentinel IP地址,Sentinel端口号,Sentinel运行ID等八个参数,并进行检查:若是信息中记录的Sentinel运行ID和基尔兽信息的Sentinel的运行ID相同,那么说明这条信息时Sentinel本身发送的,Sentinel将丢弃这条信息,不作处理,反之若是记录的Sentinel运行ID和接收消息的Sentinel的运行ID不相同,那么说明这条信息时监视同一个服务器的其余Sentinel发来的,接收信息的Sentinel将根据这些信息中的参数,对相应主服务器的实例结构进行更新。
Sentinel为主服务器建立的实例结构中的sentinels字典中除了保存自己之外,还会保存一样监视这个主服务器的其余Sentinel的资料,其中字典键为 ip:port 值为Sentinel实例结构。
若是目标Sentinel(接收消息的Sentinel)接收到源Sentinel(发送消息的Sentinel)的消息,提取其中的参数检查主服务器实例结构的sentinels字典中源Sentinel实例结构是否存在,若是存在则更新,不存在则建立实例结构并存储到主服务器的sentinels字典中。而且,目标Sentinel还会建立链接向源Sentinel的命令链接,最终监视同一主服务器的多个Sentinel将造成互相链接的网络。
可是Sentinel之间不对建立订阅链接,这是由于Sentinel须要经过接收主服务器或者从服务器发来的频道信息发现未知的新Sentinel,因此才须要创建订阅链接,而相互已知的Sentinel只要使用命令链接来进行通讯就足够了。
天天学一点,总会有收获。
下篇咱们看下Redis的Sentinel(哨兵)的状态检查、Sentinel头领选举与故障转移。
说明:尊重做者知识产权,文中内容参考《Redis设计与实现》,仅在此作学习与你们分享。