关闭

redis哨兵(sentinel)系统

标签: redissetinel
228人阅读 评论(0) 收藏 举报
分类:

sentinel(哨兵)是redis的高可用性(high availability)解决方案:由一个或多个sentinel实例组成的Sentinel系统,监视任意多个主服务器及这些主服务器下的从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,代替已下线的主服务器继续处理命令请求。
下图是Sentinel系统监视服务器的例子:
这里写图片描述

1. 启动并初始化Sentinel

参考:http://redisbook.com/preview/sentinel/init_sentinel.html
启动一个Sentinel的命令:

redis> redis-sentinel /path/to/your/sentinel.conf

或者

redis> redis-server /path/to/your/sentinel.conf --sentinel

这两个效果完全相同。当启动一个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 常量的值作为服务器端口:

#define REDIS_SERVERPORT 6379

而 Sentinel 则使用 sentinel.c/REDIS_SENTINEL_PORT 常量的值作为服务器端口:

#define REDIS_SENTINEL_PORT 26379

(2)命令表
普通 Redis 服务器使用 redis.c/redisCommandTable 作为服务器的命令表:

struct redisCommand redisCommandTable[] = {
    {"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
    {"set",setCommand,-3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
    {"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
    // ...
    {"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0},
    {"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
    {"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
    {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0}
}

而 Sentinel 则使用 sentinel.c/sentinelcmds 作为服务器的命令表, 并且其中的 INFO 命令会使用 Sentinel 模式下的专用实现 sentinel.c/sentinelInfoCommand 函数, 而不是普通 Redis 服务器使用的实现 redis.c/infoCommand 函数:

struct redisCommand 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},
    {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}
};

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. 将已下线服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:63440次
    • 积分:2245
    • 等级:
    • 排名:第17446名
    • 原创:168篇
    • 转载:21篇
    • 译文:2篇
    • 评论:5条
    最新评论