sentinel(哨兵)是redis的高可用性(high availability)解决方案:由一个或多个sentinel实例组成的Sentinel系统,监视任意多个主服务器及这些主服务器下的从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,代替已下线的主服务器继续处理命令请求。
下图是Sentinel系统监视服务器的例子:
![]()
redis哨兵(sentinel)系统1. 启动并初始化Sentinel2. 获取主服务器信息3. 获取从服务器信息4. 向主服务器和从服务器发送消息5. 接收来自主服务器和从服务器的频道信息6. 检测主观下线状态7. 检测客观下线状态8. 选举领头sentinel9. 故障转移
1. 启动并初始化Sentinel
参考:http://redisbook.com/preview/sentinel/init_sentinel.html
启动一个Sentinel的命令:
redis> redis-sentinel /path/to/your/sentinel.conf
或者
这两个效果完全相同。当启动一个sentinel时,需要执行以下步骤:
1. 初始化服务器
2. 将普通redis服务器使用的代码替换成sentinel专用代码
3. 初始化sentinel状态
4. 根据配置文件,初始化sentinel的监视主服务器列表
5. 创建连向主服务器的网络连接
1.1 初始化服务器
Sentinel 本质上是一个运行在特殊模式下的redis服务器,所以启动Sentinel的第一步(类似)初始化一个普通的redis服务器。
因为 Sentinel 执行的工作和普通 Redis 服务器执行的工作不同, 所以 Sentinel 的初始化过程和普通 Redis 服务器的初始化过程并不完全相同。
普通服务器在初始化时会通过载入 RDB 文件或者 AOF 文件来还原数据库状态, 但是因为 Sentinel 并不使用数据库, 所以初始化 Sentinel 时就不会载入 RDB 文件或者 AOF 文件。
Sentinel 模式下 Redis 服务器主要功能的使用情况
功能 | 使用情况 |
---|---|
数据库和键值对方面的命令, 比如 SET 、 DEL 、 FLUSHDB 。 | 不使用。 |
事务命令, 比如 MULTI 和 WATCH 。 | 不使用。 |
脚本命令,比如 EVAL 。 | 不使用。 |
RDB 持久化命令, 比如 SAVE 和 BGSAVE 。 | 不使用。 |
AOF 持久化命令, 比如 BGREWRITEAOF 。 | 不使用。 |
复制命令,比如 SLAVEOF 。 | Sentinel 内部可以使用,但客户端不可以使用。 |
发布与订阅命令, 比如 PUBLISH 和 SUBSCRIBE 。 | SUBSCRIBE 、 PSUBSCRIBE 、 UNSUBSCRIBE PUNSUBSCRIBE 四个命令在 Sentinel 内部和客户端都可以使用, 但 PUBLISH 命令只能在 Sentinel 内部使用。 |
文件事件处理器(负责发送命令请求、处理命令回复)。 | Sentinel 内部使用, 但关联的文件事件处理器和普通 Redis 服务器不同。 |
时间事件处理器(负责执行 serverCron 函数)。 | Sentinel 内部使用, 时间事件的处理器仍然是 serverCron 函数, serverCron 函数会调用 sentinel.c/sentinelTimer 函数, 后者包含了 Sentinel 要执行的所有操作。 |
1.2 使用 Sentinel 专用代码
将一部分普通 Redis 服务器使用的代码替换成 Sentinel 专用代码
(1)端口
普通 Redis 服务器使用 redis.h/REDIS_SERVERPORT 常量的值作为服务器端口:
而 Sentinel 则使用 sentinel.c/REDIS_SENTINEL_PORT 常量的值作为服务器端口:
(2)命令表
普通 Redis 服务器使用 redis.c/redisCommandTable 作为服务器的命令表:
struct redisCommand redisCommandTable[] = {
{"get",getCommand,,"r",,NULL,,,,,},
{"set",setCommand,-,"wm",,noPreloadGetKeys,,,,,},
{"setnx",setnxCommand,,"wm",,noPreloadGetKeys,,,,,},
// ...
{"script",scriptCommand,-,"ras",,NULL,,,,,},
{"time",timeCommand,,"rR",,NULL,,,,,},
{"bitop",bitopCommand,-,"wm",,NULL,,-,,,},
{"bitcount",bitcountCommand,-,"r",,NULL,,,,,}
}
而 Sentinel 则使用 sentinel.c/sentinelcmds 作为服务器的命令表, 并且其中的 INFO 命令会使用 Sentinel 模式下的专用实现 sentinel.c/sentinelInfoCommand 函数, 而不是普通 Redis 服务器使用的实现 redis.c/infoCommand 函数:
struct redisCommand sentinelcmds[] = {
{"ping",pingCommand,,"",,NULL,,,,,},
{"sentinel",sentinelCommand,-,"",,NULL,,,,,},
{"subscribe",subscribeCommand,-,"",,NULL,,,,,},
{"unsubscribe",unsubscribeCommand,-,"",,NULL,,,,,},
{"psubscribe",psubscribeCommand,-,"",,NULL,,,,,},
{"punsubscribe",punsubscribeCommand,-,"",,NULL,,,,,},
{"info",sentinelInfoCommand,-,"",,NULL,,,,,}
};
1.3 初始化 Sentinel 状态
服务器会初始化一个 sentinel.c/sentinelState 结构(“Sentinel 状态”), 保存了服务器中所有和 Sentinel 功能有关的状态 (服务器的一般状态仍然由 redis.h/redisServer 结构保存):
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;
1.4 初始化 Sentinel 状态的 masters 属性
Sentinel 状态中的 masters 字典记录了所有被 Sentinel 监视的主服务器的相关信息,是根据被载入的 Sentinel 配置文件对其进行初始化。
1. 字典的键是被监视主服务器的名字。
2. 字典的值则是被监视主服务器对应的 sentinel.c/sentinelRedisInstance 结构。
typedef struct sentinelRedisInstance {
// 标识值,记录了实例的类型,以及该实例的当前状态
int flags;
// 实例的名字
// 主服务器的名字由用户在配置文件中设置
// 从服务器以及 Sentinel 的名字由 Sentinel 自动设置
// 格式为 ip:port ,例如 "127.0.0.1:26379"
char *name;
// 实例的运行 ID
char *runid;
// 配置纪元,用于实现故障转移
uint64_t config_epoch;
// 实例的地址:ip地址和port
sentinelAddr *addr;
// SENTINEL down-after-milliseconds 选项设定的值
// 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)
mstime_t down_after_period;
// SENTINEL monitor <master-name> <IP> <port> <quorum> 选项中的 quorum 参数
// 判断这个实例为客观下线(objectively down)所需的支持投票数量
int quorum;
// SENTINEL parallel-syncs <master-name> <number> 选项的值
// 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
int parallel_syncs;
// SENTINEL failover-timeout <master-name> <ms> 选项的值
// 刷新故障迁移状态的最大时限
mstime_t failover_timeout;
// ...
} sentinelRedisInstance;
1.5 创建连向主服务器的网络连接
对于每个被 Sentinel 监视的主服务器来说, Sentinel 会创建两个连向主服务器的异步网络连接:
- 一个命令连接, Sentinel 将成为主服务器的客户端, 这个连接专门用于向主服务器发送命令, 并接收命令回复。
- 一个订阅连接, 这个连接专门用于订阅主服务器的 __sentinel__:hello 频道。
2. 获取主服务器信息
Sentinel默认会每十秒一次的频率,通过命令连接向被监视的主服务器发送 INFO命令
。
通过分析INFO命令的回复来获取主服务器的当前信息:
1. 主服务器本身的信息
2. 主服务器属下所有从服务器的信息
3. 获取从服务器信息
当sentinel发现主服务器有新的从服务器时,sentinel①为这个从服务器创建相应的实例结构;②创建连接到从服务器的命令连接和订阅连接。
4. 向主服务器和从服务器发送消息
sentinel会每2秒一次的频率,通过命令连接向所有被监视的(主从)服务器的_sentinel_:hello频道发送消息:
PUBLISH _sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_epoch>"
1. 以s_开头的参数记录sentinel本身的信息
2. 以m_开头的参数记录主服务器的信息
5. 接收来自主服务器和从服务器的频道信息
当sentinel与一个主服务器或从服务器建立订阅连接后,通过订阅连接发送订阅命令 SUBSCRIBE _sentinel_:hello
对于监视同一个服务器的多个sentinel来说,一个sentinel发送的信息会被其他sentinel接收,这些信息用于①更新其他sentinel对发送信息的sentinel的认知②更新其他sentinel对被监视服务器的认知。
(1)更新sentinel字典
sentinel为主服务器创建的实例结构的sentinel字典保存了除sentinel本身之外,所有同样监视这个主服务器的其他sentinel的资料。
(2)创建连向其他sentinel的命令连接
6. 检测主观下线状态
sentinel会以每秒一次的频率向所有与它从创建了命令连接的实例(包括主服务器、从服务器、其他sentinel在内)发送 PING
命令,并通过实例返回的PING命令回复来判断实例是否在线。
7. 检测客观下线状态
当sentinel在判定一个主服务器为主观下线后,为了确认这个主服务器是否真的下线,询问同样监视这个主服务器的其他sentinel,当收到足够数量的已下线判断后,sentinel就会将服务器判定为客观下线,并对服务器执行故障转移操作。
8. 选举领头sentinel
当一个主服务器被判断为客观下线后,监视这个下线服务器的各个sentinel会进行协商,选举出一个领头sentinel,并由领头sentinel对下线服务器执行故障转移操作。
9. 故障转移
领头sentinel对下线服务器执行故障转移操作,三个步骤:
1. 在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。
2. 让已下线服务器属下的所有从服务器改为复制新的主服务器
3. 将已下线服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。