您的位置:首页 > 数据库 > Redis

结合redis设计与实现的redis源码学习-21-哨兵(Sentinel.c)

2017-12-05 01:01 731 查看
Sentinel是redis的高可用性解决方案:由一个或多个Sentinel组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后向原来主服务器的slave发送新的复制指令,让他们成为新主服务器的从服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求,还会监视已下线的老的主服务器,在重新上线时让它成为新主服务器的从服务器。

一、启动并初始化Sentinel

当一个Sentinel启动时,他需要执行以下步骤:

-1、初始化服务器:

Sendtinel本质上是一个运行在特殊模式下的Redis服务器,所以启动Sentinel的第一步,就是初始化一个普通的Redis服务器。但是Sentinel并不使用数据库,所以初始化Sentinel时不会载入RDB或AOF文件。

-2、将普通Redis服务器使用的代码替换成Sentinel的:

Sentinel不会加载ComandTable,会使用infoCommand函数中载入的专用命令。

-3、初始化Sentinel状态:

初始化sentinelState结构,这个结构保存了服务器中所有和Sentinel功能有关的状态。

/* Main state. */
struct sentinelState {
char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. 哨兵ID*/
uint64_t current_epoch;         /* Current epoch. 当前纪元,用于实现故障转移*/
dict *masters;      /* Dictionary of master sentinelRedisInstances.Key is the instance name, value is the sentinelRedisInstance structure pointer. 主控字典的实例,key是实例名称,值是实例指针,保存了所有被这个哨兵监视的主服务器*/
int tilt;           /* Are we in TILT mode? 是否进入了TILT模式*/
int running_scripts;    /* Number of scripts in execution right now. 目前正在运行的脚本数量*/
mstime_t tilt_start_time;       /* When TITL started. 进入TILT模式的时间*/
mstime_t previous_time;         /* Last time we ran the time handler. 最后一次执行事件处理器的时间*/
list *scripts_queue;            /* Queue of user scripts to execute. 一个fifo队列,包含了所有需要执行的用户脚本*/
char *announce_ip;  /* IP addr that is gossiped to other sentinels if not NULL. ip地址,如果不是NULL,则会被闲谈到其他哨兵*/
int announce_port;  /* Port that is gossiped to other sentinels if non zero. 如果非零,则向其他哨兵闲谈的端口*/
unsigned long simfailure_flags; /* Failures simulation. 失败模拟*/
} sentinel;


-4、根据给定的配置文件,初始化Sentinel的监视主服务器列表:

这个步骤根据配置文件初始化第三步中结构体的master属性

typedef struct sentinelRedisInstance {
int flags;      /* See SRI_... defines 标识符,记录了实例的类型,以及该实例的当前状态*/
char *name;     /* Master name from the point of view of this sentinel. 实例的名字,主服务器的名字由用户在配置文件中设置,从服务器
e36c
及Sentinel的名字由Sentinel自动设置*/
char *runid;    /* Run ID of this instance, or unique ID if is a Sentinel. 实例运行的ID*/
uint64_t config_epoch;  /* Configuration epoch. 配置纪元,用于实现故障转移*/
sentinelAddr *addr; /* Master host. 实例的地址*/
instanceLink *link; /* Link to the instance, may be shared for Sentinels. */
mstime_t last_pub_time;   /* Last time we sent hello via Pub/Sub. */
mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time we received a hello from this Sentinel via Pub/Sub. */
mstime_t last_master_down_reply_time; /* Time of last reply to SENTINEL is-master-down command. */
mstime_t s_down_since_time; /* Subjectively down since time. */
mstime_t o_down_since_time; /* Objectively down since time. */
mstime_t down_after_period; /* Consider it down after that period. 实例无响应多少毫秒之后才会被判断为主管下线*/
mstime_t info_refresh;  /* Time at which we received INFO output from it. */

/* Role and the first time we observed it. This is useful in order to delay replacing what the instance reports with our own configuration. We need to always wait some time in order to give a chance to the leader to report the new configuration before we do silly things. */
int role_reported;
mstime_t role_reported_time;
mstime_t slave_conf_change_time; /* Last time slave master addr changed. */

/* Master specific. */
dict *sentinels;    /* Other sentinels monitoring the same master. */
dict *slaves;       /* Slaves for this master instance. */
unsigned int quorum;/* Number of sentinels that need to agree on failure. 判断这个实例为客观下线所需的支持投票数量*/
int parallel_syncs; /* How many slaves to reconfigure at same time. 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量*/
char *auth_pass;    /* Password to use for AUTH against master & slaves. */

/* Slave specific. */
mstime_t master_link_down_time; /* Slave replication link down time. */
int slave_priority; /* Slave priority according to its INFO output. */
mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */
struct sentinelRedisInstance *master; /* Master instance if it's slave. */
char *slave_master_host;    /* Master host as reported by INFO */
int slave_master_port;      /* Master port as reported by INFO */
int slave_master_link_status; /* Master link status as reported by INFO */
unsigned long long slave_repl_offset; /* Slave replication offset. */
/* Failover */
char *leader;       /* If this is a master instance, this is the runid of the Sentinel that should perform the failover. If this is a Sentinel, this is the runid of the Sentinel that this Sentinel voted as leader. */
uint64_t leader_epoch; /* Epoch of the 'leader' field. */
uint64_t failover_epoch; /* Epoch of the currently started failover. */
int failover_state; /* See SENTINEL_FAILOVER_STATE_* defines. */
mstime_t failover_state_change_time;
mstime_t failover_start_time;   /* Last failover attempt start time. */
mstime_t failover_timeout;      /* Max time to refresh failover state. */
mstime_t failover_delay_logged; /* For what failover_start_time value we logged the failover delay. */
struct sentinelRedisInstance *promoted_slave; /* Promoted slave instance. */
/* Scripts executed to notify admin or reconfigure clients: when they
* are set to NULL no script is executed. */
char *notification_script;
char *client_reconfig_script;
sds info; /* cached INFO output */
} sentinelRedisInstance;


-5、创建连向主服务器的网络连接:

Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息,对于每个被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:一个是命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复。另一个是订阅连接,这个连接专门用于订阅主服务器的sentinel:hello频道。

二、获取主服务器的信息

Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息:

-1、主服务器本身的信息,run_id和role记录的服务器角色;

-2、主服务器属下所有从服务器的信息,每个从服务器都由一个slave字符串开头的行记录,每行ip=域记录了从服务器的IP地址,port=记录了从服务器的端口号。所以可以自动发现从服务器。

根据记录的信息,Sentinel将对主服务器的实例结构进行更新。

主服务器返回的从服务器信息,会被用于更新主服务器实例结构的slaves字典,这个字典记录了主服务器属下从服务器的名单。

Sentinel在分析INFO命令中包含的从服务器的信息时,会检查从服务器对应的实例结构是否已经存在于slave字典中:如果已经存在,Sentinel对服务器的实例结构进行更新。如果不存在,那么说明这个从服务器是新发现的从服务器,Sentinel会在slaves字典中创建一个新的实例结构。

三、获取从服务器信息

当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构之外,还会创建连接到从服务器的命令连接和订阅连接。

在创建命令连接之后,Sentinel在默认情况下,会以每十秒一次的频率通过命令连接向从服务器发送INFO命令,并获得回复,提取以下信息:从服务器的运行ID,从服务器的角色role。主服务器的IP地址master_host,主服务器的端口号,master_port。主从服务器的连接状态,master_link_status,从服务器的优先级slave_priority,从服务器的复制偏移量slave_repl_offset。

根据这些信息,Sentinel对从服务器的实例结构进行更新。

四、向主服务器和从服务器发送信息

在默认情况下,Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送命令,PUBLISH sentinel:hello,这条发布命令的内容由多个参数组成:其中以s_开头的参数是Sentinel本身的信息;m_开头的参数记录的则是主服务器的信息,如果Sentinel正在监视的是主服务器,那么这些参数记录的就是主服务器的信息;如果Sentinel正在监视的是从服务器,那么这些参数记录的就是从服务器正在复制的主服务器信息。

五、接收来自主服务器和从服务器的频道信息

当Sentinel与一个主服务器或者从服务器建立起订阅连接后,Sentinel就会通过订阅连接,向服务器发送以下命令:SUBSCRIBE sentinel:hello,这个订阅会一直持续到Sentinel与服务器的连接断开为止。

对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel接收到,这些信息会被用于更新其他Sentinel对发送信息Sentinel的认知,也会被用于更新其他Sentinel对被监视服务器的认知。

当一个Sentinel从频道收到一个信息时,Sentinel会对这条信息进行分析,提取其中的IP,port,ID等参数,并进行以下检查:如果信息中的ID和接收信息的Sentinel的ID相同,丢弃。否则对相应主服务器的实例结构进行更新。

更新sentinels字典

Sentinel为主服务器创建的实例结构中的sentinels字典,保存除了Sentinel本身外,所有同样监视这个主服务器的其他Sentinel资料。

当一个Sentinel接收到其他Sentinel发来的信息时,目标Sentinel会从信息中分析并提取出以下两个方面参数:源Sentinel的IP地址,port,id和配置纪元;源Sentinel正在监视的主服务器的名字,ip,port和配置纪元。

根据信息中提取出主服务器的参数,目标Sentinel会在自己的Sentinel状态masters字典中查找相应的主服务器实例结构,然后根据提取出的Sentinel参数,检查主服务器是咧结构的sentinels字典中,源Sentinel实例是否存在:如果源Sentinel的实例结构已经存在,那么对源Sentinel的实例结构进行更新。如果源Sentinel的实例结构不存在,那么说明源Sentinel是刚刚开始监视主服务器的新Sentinel,目标Sentinel会为源Sentinel创建一个新的实例结构,并添加到sentinels字典中。

创建连向其他Sentinel的命令连接

当Sentinel通过频道信息发现一个新的Sentinel时,他不仅会为新Sentinel在sentinels字典中创建相应的实例结构,还会创建一个连向新Sentinel的命令连接,最终监视同意主服务器的多个Sentinel将形成相互连接的网络。

使用命令连接相连的各个Sentinel可以通过向其他Sentinel发送命令请求来进行信息交换。Sentinel之间不会创建订阅连接。

六、检测主观下线状态

在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。

有效回复:+PONG,-LOADING,-MASTERDOWN。

如果一个实例在down-after-milliseconds毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来表示这个实例已经进入了主管下线状态。

七、检查客观下线状态

当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态。当Sentinel从其他Sentinel那里接收到足够数量的已下线判断之后,Sentinel会将服务器判定为客观下线,并对主服务器执行故障转移操作。

-1、发送SENTINEL is-master-down-by-addr命令;

-2、目标Sentinel接收到另一个Sentinel发来的SENTINEL is-master-down-by命令时,目标Sentinel会分析并取出命令请求中包含的各个参数,并根据其中的服务器ip和port,检查主服务器是否已下线,然后向源服务器回复。

-3、接收SENTINEL is-master-down-by-addr回复,统计其他Sentinel同意主服务器已下线的数量,当这一数量达到配置指定的判断客观下线所需的数量时,Sentinel会将主服务器的实例结构flags的SRI_O_DOWN标识打开,表示主服务器已经进入客观下线状态。

八、选举领头Sentinel

当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Snetinel对下线主服务器进行故障转移操作。规则:

-1、所有Sentinel都有被选为领头Sentinel的资格;

-2、每次进行选举之后,不论是否成功,所有Sentinel的配置纪元都会自增一次;

-3、在一个配置纪元里,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元里就不会再更改;

-4、每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel;

-5、当一个Sentinel向另一个Sentinel发送请求命令,并且命令中的runid不是*而是运行id时,这表示源Sentinel要求目标Sentinel将前者设置为后者的局部领头Sentinel。

-6、设置局部领头Sentinel的原则是先到先得,之后所有的设置要求都会被拒绝;

-7、目标Sentinel在收到命令后,会返回一条回复,回复中的leader_runid参数和leader_epoch参数分别记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元;

-8、源Sentinel在收到回复后,会检查配置纪元与自己是否相等,如果相同,且leader_runid与自己相同,那么表示自己成为了目标的局部领头;

-9、如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么它成为领头Sentinel;

-10、因为领头需要产生半数的支持,并且每个配置纪元只能设置一次局部领头Sentinel,所以在一个配置纪元里面,只会出现一个领头Sentinel;

-11、如果在给定时限内没有选出领头Sentinel,那么各个Sentinel将在一段时间之后再次进行选举,直到选出来。

九、故障转移

在选举产生局部领头Sentinel后,它将会对已经下线的主服务器执行故障转移操作:

-1、在已下线的主服务器属下的所有从服务器中,挑选一个从服务器,将其转换为主服务器;

挑选一个状态良好,数据完整的从服务器,发送SLAVEOF no one,转换为主服务器。

当新主服务器回复Sentinel的INFO命令的role变为master时,升级成功。

-2、让已下线主服务器的所有从服务器改为复制新的主服务器;

发送给从服务器SLAVEOF 命令使之成为新主服务器的从服务器;

-3、将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线,它就会成为新主服务器的从服务器;

当就的主服务器重新上线,发送SLAVEOF命令使之成为新主服务器的从服务器。

sentinel.c

待续
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐