16.1 启动并初始化Sentinel

初始化服务器

Sentinel本质上只是运行在特殊模式下的Redis服务器,启动第一步就是初始化一个普通的Redis服务器

使用Sentinel专用代码

使用redis.h/REDIS_SERVERPORT常量值作为服务器端口

使用redis.h/redisCommandTable作为服务器的命令表

// 服务器在 sentinel 模式下可执行的命令
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},
{"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}
}; struct redisCommand { // 命令名字
char *name; // 实现函数
redisCommandProc *proc; // 参数个数
int arity; // 字符串表示的 FLAG
char *sflags; /* Flags as string representation, one char per flag. */ // 实际 FLAG
int flags; /* The actual flags, obtained from the 'sflags' field. */ /* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect. */
// 从命令中判断命令的键参数。在 Redis 集群转向时使用。
redisGetKeysProc *getkeys_proc; /* What keys should be loaded in background when calling this command? */
// 指定哪些参数是 key
int firstkey; /* The first argument that's a key (0 = no keys) */
int lastkey; /* The last argument that's a key */
int keystep; /* The step between first and last key */ // 统计信息
// microseconds 记录了命令执行耗费的总毫微秒数
// calls 是命令被执行的总次数
long long microseconds, calls;
};

初始化Sentinel状态

/* Sentinel 的状态结构 */
struct sentinelState { // 当前纪元
uint64_t current_epoch; /* Current epoch. */ // 保存了所有被这个 sentinel 监视的主服务器
// 字典的键是主服务器的名字
// 字典的值则是一个指向 sentinelRedisInstance 结构的指针
dict *masters; /* Dictionary of master sentinelRedisInstances.
Key is the instance name, value is the
sentinelRedisInstance structure pointer. */ // 是否进入了 TILT 模式?
int tilt; /* Are we in TILT mode? */ // 目前正在执行的脚本的数量
int running_scripts; /* Number of scripts in execution right now. */ // 进入 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;

初始化Sentinel状态的masters属性

Sentinel状态中的master字典记录了所有被Sentinel监视的主服务器的相关信息其中:

  • 字典的键是被监视主服务器的名字
  • 而字典的值是被监视主服务器对应的sentinel.h/sentinelRedisInstance结构
/* A Sentinel Redis Instance object is monitoring. */
/* 每个被监视的 Redis 实例都会创建一个 sentinelRedisInstance 结构
* 而每个结构的 flags 值会是以下常量的一个或多个的并 */
// 实例是一个主服务器
#define SRI_MASTER (1<<0)
// 实例是一个从服务器
#define SRI_SLAVE (1<<1)
// 实例是一个 Sentinel
#define SRI_SENTINEL (1<<2)
// 实例已断线
#define SRI_DISCONNECTED (1<<3)
// 实例已处于 SDOWN 状态
#define SRI_S_DOWN (1<<4) /* Subjectively down (no quorum). */
// 实例已处于 ODOWN 状态
#define SRI_O_DOWN (1<<5) /* Objectively down (confirmed by others). */
// Sentinel 认为主服务器已下线
#define SRI_MASTER_DOWN (1<<6) /* A Sentinel with this flag set thinks that
its master is down. */
// 正在对主服务器进行故障迁移
#define SRI_FAILOVER_IN_PROGRESS (1<<7) /* Failover is in progress for
this master. */
// 实例是被选中的新主服务器(目前仍是从服务器)
#define SRI_PROMOTED (1<<8) /* Slave selected for promotion. */
// 向从服务器发送 SLAVEOF 命令,让它们转向复制新主服务器
#define SRI_RECONF_SENT (1<<9) /* SLAVEOF <newmaster> sent. */
// 从服务器正在与新主服务器进行同步
#define SRI_RECONF_INPROG (1<<10) /* Slave synchronization in progress. */
// 从服务器与新主服务器同步完毕,开始复制新主服务器
#define SRI_RECONF_DONE (1<<11) /* Slave synchronized with new master. */
// 对主服务器强制执行故障迁移操作
#define SRI_FORCE_FAILOVER (1<<12) /* Force failover with master up. */
// 已经对返回 -BUSY 的服务器发送 SCRIPT KILL 命令
#define SRI_SCRIPT_KILL_SENT (1<<13) /* SCRIPT KILL already sent on -BUSY */ // Sentinel 会为每个被监视的 Redis 实例创建相应的 sentinelRedisInstance 实例
// (被监视的实例可以是主服务器、从服务器、或者其他 Sentinel )
typedef struct sentinelRedisInstance { // 位图,标识值,记录了实例的类型,以及该实例的当前状态
int flags; /* See SRI_... defines */ // 实例的名字
// 主服务器的名字由用户在配置文件中设置
// 从服务器以及 Sentinel 的名字由 Sentinel 自动设置
// 格式为 ip:port ,例如 "127.0.0.1:26379"
char *name; /* Master name from the point of 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 for Pub / Sub. */ // 已发送但尚未回复的命令数量
int pending_commands; /* Number of commands sent 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 we consider valid. */
// 实例最后一次发送 PING 命令的时间
mstime_t last_ping_time; /* Last time a pending ping was sent in the
context of the current command connection
with the instance. 0 if still not sent or
if pong already received. */
// 实例最后一次返回 PING 命令的时间,无论内容正确与否
mstime_t last_pong_time; /* Last time the instance replied to ping,
whatever the reply was. That's used to check
if the link is idle and must be reconnected. */ // 最后一次向频道发送问候信息的时间
// 只在当前实例为 sentinel 时使用
mstime_t last_pub_time; /* Last time we sent hello via Pub/Sub. */ // 最后一次接收到这个 sentinel 发来的问候信息的时间
// 只在当前实例为 sentinel 时使用
mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time
we received a hello 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-down command. */ // 实例被判断为 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 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. */
/* 主服务器实例特有的属性 -------------------------------------------------------------*/ // 其他同样监控这个主服务器的所有 sentinel
dict *sentinels; /* Other sentinels monitoring the same master. */ // 如果这个实例代表的是一个主服务器
// 那么这个字典保存着主服务器属下的从服务器
// 字典的键是从服务器的名字,字典的值是从服务器对应的 sentinelRedisInstance 结构
dict *slaves; /* Slaves for this master instance. */ // SENTINEL monitor <master-name> <IP> <port> <quorum> 选项中的 quorum 参数
// 判断这个实例为客观下线(objectively down)所需的支持投票数量
int quorum; /* Number of sentinels that need to agree on failure. */ // SENTINEL parallel-syncs <master-name> <number> 选项的值
// 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
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. */ // 执行故障转移操作时,从服务器发送 SLAVEOF <new-master> 命令的时间
mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */ // 主服务器的实例(在本实例为从服务器时使用)
struct sentinelRedisInstance *master; /* Master instance if it's slave. */ // INFO 命令的回复中记录的主服务器 IP
char *slave_master_host; /* Master host as reported by INFO */ // INFO 命令的回复中记录的主服务器端口号
int slave_master_port; /* Master port as reported by INFO */ // INFO 命令的回复中记录的主从服务器连接状态
int slave_master_link_status; /* Master link status as reported by INFO */ // 从服务器的复制偏移量
unsigned long long slave_repl_offset; /* Slave replication offset. */ /* Failover */
/* 故障转移相关属性 -------------------------------------------------------------------*/ // 如果这是一个主服务器实例,那么 leader 将是负责进行故障转移的 Sentinel 的运行 ID 。
// 如果这是一个 Sentinel 实例,那么 leader 就是被选举出来的领头 Sentinel 。
// 这个域只在 Sentinel 实例的 flags 属性的 SRI_MASTER_DOWN 标志处于打开状态时才有效。
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. */ // SENTINEL failover-timeout <master-name> <ms> 选项的值
// 刷新故障迁移状态的最大时限
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. */
// 一个文件路径,保存着 WARNING 级别的事件发生时执行的,
// 用于通知管理员的脚本的地址
char *notification_script; // 一个文件路径,保存着故障转移执行之前、之后、或者被中止时,
// 需要执行的脚本的地址
char *client_reconfig_script; } sentinelRedisInstance;

16.2 获取服务器信息

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

通过分析主服务器回复的信息,填充主服务器实例结构中的slaves字典,而字典的键为从服务器的名字(addr:port), 值为从服务器的实例结构,如果从服务器的值已经存在,那么更新从服务器实例结构

16.3 获取从服务器信息

在创建命令连接之后,Sentinel默认情况下会以每十秒一次的频率通过命令连接向从服务器发送INFO命令,并获得类似以下内容的回复:

16.4 向主服务器和从服务器发送信息

在默认情况下,Sentinel会以每2秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:

PUBLISH __sentinel__:hello "<s_ip>, <s_port>, <s_runid>, <s_epoch>, <m_name>, <m_ip>, <m_prot>, <m_epoch>"

int sentinelSendHello(sentinelRedisInstance *ri) {
char ip[REDIS_IP_STR_LEN];
char payload[REDIS_IP_STR_LEN+1024];
int retval; // 如果实例是主服务器,那么使用此实例的信息
// 如果实例是从服务器,那么使用这个从服务器的主服务器的信息
sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ? ri : ri->master; // 获取地址信息
sentinelAddr *master_addr = sentinelGetCurrentMasterAddress(master); /* Try to obtain our own IP address. */
// 获取实例自身的地址
if (anetSockName(ri->cc->c.fd,ip,sizeof(ip),NULL) == -1) return REDIS_ERR;
if (ri->flags & SRI_DISCONNECTED) return REDIS_ERR; /* Format and send the Hello message. */
// 格式化信息
snprintf(payload,sizeof(payload),
"%s,%d,%s,%llu," /* Info about this sentinel. */
"%s,%s,%d,%llu", /* Info about current master. */
ip, server.port, server.runid,
(unsigned long long) sentinel.current_epoch,
/* --- */
master->name,master_addr->ip,master_addr->port,
(unsigned long long) master->config_epoch); // 发送信息
retval = redisAsyncCommand(ri->cc,
sentinelPublishReplyCallback, NULL, "PUBLISH %s %s",
SENTINEL_HELLO_CHANNEL,payload); if (retval != REDIS_OK) return REDIS_ERR; ri->pending_commands++; return REDIS_OK;
}

16.5 接收来自主服务器和从服务器的频道消息

当一个Sentinel从__sentinel__:hello频道收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中的八个参数,根据这些参数对主服务器的实例结构进行更新

更新sentinals字典

【笔记】《Redis设计与实现》chapter16 Sentinel的更多相关文章

  1. Redis设计与实现3.2:Sentinel

    Sentinel哨兵 这是<Redis设计与实现>系列的文章,系列导航:Redis设计与实现笔记 哨兵:监视.通知.自动故障恢复 启动与初始化 Sentinel 的本质只是一个运行在特殊模 ...

  2. redis 学习笔记(4)-HA高可用方案Sentinel配置

    上一节中介绍了master-slave模式,在最小配置:master.slave各一个节点的情况下,不管是master还是slave down掉一个,“完整的”读/写功能都将受影响,这在生产环境中显然 ...

  3. 《Redis设计与实现》读书笔记

    <Redis设计与实现>读书笔记 很喜欢这本书的创作过程,以开源的方式,托管到Git上进行创作: 作者通读了Redis源码,并分享了详细的带注释的源码,让学习Redis的朋友轻松不少: 阅 ...

  4. 《Redis设计与实现》阅读笔记(一)--Redis学习

    Redis学习资料与过程记录 在实习中经常会用到很多Redis,对Redis有了一些模糊的了解,总觉得隔靴搔痒的不痛快,所以决定开始深入的了解Redis,也作为我实习期间的目标. 这篇只是为了占个位置 ...

  5. Redis 学习笔记(篇十):Sentinel

    Sentinel(哨兵)是 Redis 的高可用解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主 ...

  6. Redis | 第12章 Sentinel 哨兵模式《Redis设计与实现》

    目录 前言 1. 启动并初始化 Sentinel 2. Sentinel 与服务器间的默认通信 2.1 获取主服务器信息 2.2 获取从服务器信息 2.3 向主服务器和从服务器发送信息 3. 接受来自 ...

  7. Redis学习笔记之Redis单机,伪集群,Sentinel主从复制的安装和配置

    0x00 Redis简介 Redis是一款开源的.高性能的键-值存储(key-value store).它常被称作是一款数据结构服务器(data structure server). Redis的键值 ...

  8. Redis设计与实现3.3:集群

    集群 这是<Redis设计与实现>系列的文章,系列导航:Redis设计与实现笔记 集群中的节点 创建集群 通过 CLUSTER NODE 命令可以查看当前集群中的节点.刚启动时,默认每一台 ...

  9. 重读redis设计与实现

    重读了一遍redis设计与实现,这次收获也不错,把之前还有些疑惑的点:redis跳跃表的原理.redis持久化的方法.redis复制.redis sentinel.redis集群等,都重新熟悉了一遍, ...

  10. 如何使用redis设计关系数据库

    目录 redis设计关系数据库 前言 设计用户信息表结构 hash存储记录 set存储id 图示 索引/查询: 1.select 查询所有记录 : 类似sql的select from table_na ...

随机推荐

  1. Java线程池状态和状态切换

    摘要 介绍线程池的五种状态RUNNING.SHUTDOWN.STOP.TIDYING和TERMINATED,并简述五种状态之间的切换.   在类ThreadPoolExecutor中定义了一个成员变量 ...

  2. Fast-RTPS简介

    RTPS即DDS中的主要核心通信部分.它提供实时高效的去中心化publish/subscribe通信机制.是ROS-2的核心底层通信组件,也是未来机器人/无人驾驶领域一个必然的方向. 资料参考: ht ...

  3. C++算法代码——扫雷游戏

    题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1685 题目描述 扫雷游戏是一款十分经典的单机小游戏. 在 n 行 m 列的雷区中有一 ...

  4. ForkJoinPool大型图文现场(一阅到底 vs 直接收藏)

    知识回顾 并发工具类我们已经讲了很多,这些工具类的「目标」是让我们只关注任务本身,并且忽视线程间合作细节,简化了并发编程难度的同时,也增加了很多安全性.工具类的对使用者的「目标」虽然一致,但每一个工具 ...

  5. Vim的基本命令

    Vi vi的两种模式 ①commad命令模式:无法输入任何东西,需要按下i进入编辑模式 ②edit编辑模式:按下esc退出到命令模式,在命令模式下按下wq [文件名] 可以退出并且成功的保存 //一些 ...

  6. IO、NIO、BIO的区别

    我们首先得明白什么是同步,异步,阻塞,非阻塞,只有这几个单个概念理解清楚了,然后在组合理解起来,就相对比较容易了. IO模型主要分类: 同步(synchronous) IO和异步(asynchrono ...

  7. FreeBSD 发布 2020 年 Q3 季度报告

    FreeBSD 几日前发布 Q3 季度报告,介绍了在过去第三季度里 FreeBSD 完成的工作和相关项目,涉及到架构支持.内核改进.持续集成和驱动程序优化等. 列举部分如下: FreeBSD 基金会目 ...

  8. SPOJ QTree【树链剖分】

    一 题目 QTREE 二 分析 第一道树链剖分的题,写的好艰难啊. 题意还是比较好理解的,就是在树上操作. 对于修改,题中要求的是单点修改,就算是直接树上操作也是非常简单的. 对于查询,查询的时候,是 ...

  9. C/C++ 性能优化背后的方法论:TMAM

    开发过程中我们多少都会关注服务的性能,然而性能优化是相对比较困难,往往需要多轮优化.测试,属于费时费力,有时候还未必有好的效果.但是如果有较好的性能优化方法指导.工具辅助分析可以帮助我们快速发现性能瓶 ...

  10. Linux入门视频笔记二(Shell)

    一.Shell脚本编程基础 1.简单地理解是脚本就是一堆的Linux命令或其他命令,把他们写到一起,打包成一个文件就是脚本,Shell脚本一般以.sh后缀结尾 2.sh text.sh:运行text. ...