如果当系统中只有一台Redis服务器运行时,假设服务器宕机了,那么所有的服务都将不可用,这对系统来说是不可忍受的。那么备份就是一个很自然的想法了。前面的博客中我们讲到了Redis的主从复制,应用Redis主从复制的功能,我们对主服务器配置几台从服务器从而对主服务器进行备份,主从服务器内的数据保持一致。但是仅仅做了主从是不够的——当主服务器挂了以后,从服务器只能提供读服务,而无法提供写服务,系统仍然无法完成高可用性的需求。我们的需求应该是——当主服务器挂了以后,从服务器应该能担当主服务器的角色,提供给用户正常的读写服务。仅仅依靠主从复制功能是无法满足以上需求的。
哨兵(Sentinel)是Redis高可用性的一个解决方案:由一个或多个哨兵实例组成的哨兵系统可以监视多个主服务器及该主服务器下的所有从服务器,当主服务器宕机下线时,哨兵系统可以迅速感知这一情况,从从服务器中选举出一个服务器充当主服务器的角色,进行故障转移。
接下来详细介绍下哨兵的概念以及是如何完成故障转移的操作。
一、哨兵Sentinel
哨兵Sentinel本质上是一个运行在特殊模式下的Redis服务器。sentinel.c包含了所有与Sentinel模式相关的API。Sentine模式下的Redis服务器与普通服务器有一定的区别:
1. 初始化过程不同
普通Redis服务器在初始化时需要载入RBD文件或AOF文件来还原数据库状态,而Sentinel不用。
2. 命令表不同
Sentinel不使用redis.c文件下的redisCommandTable作为服务器的命令表,而是使用了专用的命令表:
// 服务器在 sentinel 模式下可执行的命令
structredisCommand 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},
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};
当服务器进入Sentinel模式后,Redis会用一个sentinelState结构来保存服务器中与Sentinel相关的状态:
/*Main state. */
/*Sentinel 的状态结构 */
structsentinelState {
// 当前纪元
uint64_t current_epoch; /* Current epoch. */
// 保存了所有被这个 sentinel 监视的主服务器
// 字典的键是主服务器的名字
// 字典的值则是一个指向 sentinelRedisInstance 结构的指针
dict *masters; /* Dictionary of mastersentinelRedisInstances.
Key is the instancename, value is the
sentinelRedisInstance structure pointer. */
// 是否进入了 TILT 模式?
int tilt; /* Are we in TILT mode? */
// 目前正在执行的脚本的数量
int running_scripts; /* Number of scripts in execution rightnow. */
// 进入 TILT 模式的时间
mstime_t tilt_start_time; /* When TITL started. */
// 最后一次执行时间处理器的时间
mstime_t previous_time; /* Last time we ran the time handler. */
// 一个 FIFO 队列,包含了所有需要执行的用户脚本
list *scripts_queue; /* Queue of user scripts to execute. */
}sentinel;
sentinelState的masters属性是一个字典,字典的键是主服务器的名字,字典的值则是一个指向sentinelRedisInstance 结构的指针。masters字典记录了所有被Sentinel监视的主服务器的信息。
sentinelRedisInstance代表一个被Sentinel监视的Redis服务器的实例,被监视的实例可以是主服务器、从服务器、或者其他 Sentinel。sentinelRedisInstance结构中有很多属性:
typedef struct sentinelRedisInstance {
// 标识值,记录了实例的类型,以及该实例的当前状态
intflags; /* See SRI_... defines */
// 实例的名字
// 主服务器的名字由用户在配置文件中设置
// 从服务器以及 Sentinel 的名字由 Sentinel 自动设置
// 格式为 ip:port ,例如 "127.0.0.1:26379"
char*name; /* Master name from the pointof view of this sentinel. */
// 实例的运行 ID
char*runid; /* run ID of this instance. */
// 配置纪元,用于实现故障转移
uint64_t config_epoch; /*Configuration epoch. */
// 实例的地址
sentinelAddr *addr; /* Master host. */
// 用于发送命令的异步连接
redisAsyncContext *cc; /* Hiredis context for commands. */
// 用于执行 SUBSCRIBE 命令、接收频道信息的异步连接
// 仅在实例为主服务器时使用
redisAsyncContext *pc; /* Hiredis context forPub / Sub. */
// 已发送但尚未回复的命令数量
intpending_commands; /* Number of commandssent waiting for a reply. */
// cc 连接的创建时间
mstime_t cc_conn_time; /* cc connection time. */
// pc 连接的创建时间
mstime_t pc_conn_time; /* pc connection time. */
// 最后一次从这个实例接收信息的时间
mstime_t pc_last_activity; /* Last time we received any message. */
// 实例最后一次返回正确的 PING 命令回复的时间
mstime_t last_avail_time; /* Last time the instance replied to ping with
a reply weconsider valid. */
// 实例最后一次发送 PING 命令的时间
mstime_t last_ping_time; /* Lasttime a pending ping was sent in the
context of thecurrent command connection
with theinstance. 0 if still not sent or
if pongalready received. */
// 实例最后一次返回 PING 命令的时间,无论内容正确与否
mstime_t last_pong_time; /* Lasttime the instance replied to ping,
whatever thereply was. That's used to check
if the link isidle and must be reconnected. */
// 最后一次向频道发送问候信息的时间
// 只在当前实例为 sentinel 时使用
mstime_t last_pub_time; /* Lasttime we sent hello via Pub/Sub. */
// 最后一次接收到这个 sentinel 发来的问候信息的时间
// 只在当前实例为 sentinel 时使用
mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time
we received ahello from this Sentinel
via Pub/Sub.*/
// 最后一次回复 SENTINEL is-master-down-by-addr 命令的时间
// 只在当前实例为 sentinel 时使用
mstime_t last_master_down_reply_time; /* Time of last reply to
SENTINEL is-master-downcommand. */
// 实例被判断为 SDOWN 状态的时间
mstime_t s_down_since_time; /* Subjectively down since time. */
// 实例被判断为 ODOWN 状态的时间
mstime_t o_down_since_time; /* Objectively down since time. */
//SENTINEL down-after-milliseconds 选项所设定的值
// 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)
mstime_t down_after_period; /* Consider it down after that period. */
// 从实例获取 INFO 命令的回复的时间
mstime_t info_refresh; /* Time atwhich we received INFO output from it. */
/* Roleand the first time we observed it.
* Thisis useful in order to delay replacing what the instance reports
* withour own configuration. We need to always wait some time in order
* togive a chance to the leader to report the new configuration before
* wedo silly things. */
// 实例的角色
introle_reported;
// 角色的更新时间
mstime_t role_reported_time;
// 最后一次从服务器的主服务器地址变更的时间
mstime_t slave_conf_change_time; /* Last time slave master addr changed.*/
/*Master specific. */
/* 主服务器实例特有的属性-------------------------------------------------------------*/
// 其他同样监控这个主服务器的所有 sentinel
dict*sentinels; /* Other sentinelsmonitoring the same master. */
// 如果这个实例代表的是一个主服务器
// 那么这个字典保存着主服务器属下的从服务器
// 字典的键是从服务器的名字,字典的值是从服务器对应的 sentinelRedisInstance 结构
dict*slaves; /* Slaves for this masterinstance. */
//SENTINEL monitor <master-name> <IP> <port> <quorum> 选项中的 quorum 参数
// 判断这个实例为客观下线(objectively down)所需的支持投票数量
intquorum; /* Number of sentinelsthat need to agree on failure. */
//SENTINEL parallel-syncs <master-name> <number> 选项的值
// 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
intparallel_syncs; /* How many slaves to reconfigure at same time. */
// 连接主服务器和从服务器所需的密码
char*auth_pass; /* Password to use forAUTH against master & slaves. */
/*Slave specific. */
/* 从服务器实例特有的属性-------------------------------------------------------------*/
// 主从服务器连接断开的时间
mstime_tmaster_link_down_time; /* Slave replication link down time. */
// 从服务器优先级
intslave_priority; /* Slave priority according to its INFO output. */
// 执行故障转移操作时,从服务器发