Redis Sentinel 是 Redis 提供的高可用模型解决方案。Sentinel 可以自动监测一个或多个 Redis 主备实例,并在主实例宕机的情况下自动实行主备倒换。本系列通过作者对 RedisSentinel 代码的理解,详细说明 Sentinel 的代码实现方式。
Sentinel 使用 Redis 内核相同的事件驱动代码框架,但 Sentinel 有自己独特的初始化步骤。在这篇文章里,作者会介绍 Sentinel 与 Redis 服务器不同的初始化部分。
我们可以通过 redis-sentinel 或者 redis-server--sentinel 这两种方式启动并运行 Sentinel 实例,这两种方式是等价的。在 Redis server.c 的 main 函数中,我们会看到 Redis 如何判断用户指定以 Sentinel 方式运行的逻辑:
int main(int argc, char **argv) {
..........
server.sentinel_mode = checkForSentinelMode(argc,argv);
..........
}
其中 checkForSentinelMode 函数会监测以下两种条件:
程序使用 redis-sentinel 可执行文件执行。
程序参数列表中有–sentinel 标志。
以上任何一种条件成立则 Redis 会使用 Sentinel 的方式运行。
/* Returns 1 if there is --sentinel among the arguments or if
* argv[0] contains "redis-sentinel". */
int checkForSentinelMode(int argc, char **argv) {
int j;
if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
for (j = 1; j < argc; j++)
if (!strcmp(argv[j],"--sentinel")) return 1;
return 0;
}
在 Redis 判断是否以 Sentinel 的方式运行以后,我们会看到如下代码段:
int main(int argc, char **argv) {
struct timeval tv;
int j;
............
/* We need to init sentinel right now as parsing the configuration file
* in sentinel mode will have the effect of populating the sentinel
* data structures with master nodes to monitor. */
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
............
在 initSentinelConfig 函数中,会使用 Sentinel 特定的端口(默认为 26379)来替代 Redis 的默认端口(6379)。另外,在 Sentinel 模式下,需要禁用服务器运行保护模式。
/* This function overwrites a few normal Redis config default with Sentinel
* specific defaults. */
void initSentinelConfig(void) {
server.port = REDIS_SENTINEL_PORT;
server.protected_mode = 0; /* Sentinel must be exposed. */
}
与此同时,initSentinel 函数会做如下操作:
/* Perform the Sentinel mode initialization. */
void initSentinel(void) {
unsigned int j;
/* Remove usual Redis commands from the command table, then just add
* the SENTINEL command. */
dictEmpty(server.commands,NULL);
for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) {
int retval;
struct redisCommand *cmd = sentinelcmds+j;
retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
serverAssert(retval == DICT_OK);
/* Translate the command string flags description into an actual
* set of flags. */
if (populateCommandTableParseFlags(cmd,cmd->sflags) == C_ERR)
serverPanic("Unsupported command flag");
}
/* Initialize various data structures. */
sentinel.current_epoch = 0;
sentinel.masters = dictCreate(&instancesDictType,NULL);
sentinel.tilt = 0;
sentinel.tilt_start_time = 0;
sentinel.previous_time = mstime();
.............
}
使用 Sentinel 自带的命令表去替代 Redis 服务器原生的命令. 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},
{"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"no-auth no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
{"hello",helloCommand,-2,"no-auth no-script fast",0,NULL,0,0,0,0,0}
};
初始化 Sentinel 主状态结构,Sentinel 主状态的定义及注释如下。
/* Main state. */
struct sentinelState {
char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */
uint64_t current_epoch; /* Current epoch. */
dict *masters; /* Dictionary of master sentinelRedisInstances.
Key is the instance name, value is the
sentinelRedisInstance structure pointer. */
int tilt; /* Are we in TILT mode? */
int running_scripts; /* Number of scripts in execution right now. */
mstime_t tilt_start_time; /* When TITL started. */
mstime_t previous_time; /* Last time we ran the time handler. */
list *scripts_queue; /* Queue of user scripts to execute. */
char *announce_ip; /* IP addr that is gossiped to other sentinels if
not NULL. */
int announce_port; /* Port that is gossiped to other sentinels if
non zero. */
unsigned long simfailure_flags; /* Failures simulation. */
int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
paths at runtime? */
} sentinel;
其中 masters 字典指针中的每个值都对应着一个 Sentinel 检测的主实例。
在读取配置信息后,Redis 服务器主函数会调用 sentinelIsRunning 函数, 做以下几个工作:
检查配置文件是否被设置,并且检查程序对配置文件是否有写权限,因为如果 Sentinel 状态改变的话,会不断将自己当前状态记录在配置文件中。
如果在配置文件中指定运行 ID,Sentinel 会使用这个 ID 作为运行 ID,相反地,如果没有指定运行 ID,Sentinel 会生成一个 ID 用来作为 Sentinel 的运行 ID。
对所有的 Sentinel 监测实例产生初始监测事件。
/* This function gets called when the server is in Sentinel mode, started,
* loaded the configuration, and is ready for normal operations. */
void sentinelIsRunning(void) {
int j;
if (server.configfile == NULL) {
serverLog(LL_WARNING,
"Sentinel started without a config file. Exiting...");
exit(1);
} else if (access(server.configfile,W_OK) == -1) {
serverLog(LL_WARNING,
"Sentinel config file %s is not writable: %s. Exiting...",
server.configfile,strerror(errno));
exit(1);
}
/* If this Sentinel has yet no ID set in the configuration file, we
* pick a random one and persist the config on disk. From now on this
* will be this Sentinel ID across restarts. */
for (j = 0; j < CONFIG_RUN_ID_SIZE; j++)
if (sentinel.myid[j] != 0) break;
if (j == CONFIG_RUN_ID_SIZE) {
/* Pick ID and persist the config. */
getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
sentinelFlushConfig();
}
/* Log its ID to make debugging of issues simpler. */
serverLog(LL_WARNING,"Sentinel ID is %s", sentinel.myid);
/* We want to generate a +monitor event for every configured master
* at startup. */
sentinelGenerateInitialMonitorEvents();
}
本文转载自中间件小哥公众号。
原文链接:https://mp.weixin.qq.com/s/GWe3eBGrFcOmM5M0QYXHkQ
更多内容推荐
Redis 源码之常用数据结构和函数
上一篇 扩展Redis:增加Redis命令 讲了如何动手编写一个命令,但没有具体讲代码的细节,今天讲下Redis代码中的常用数据结构和函数,看完这篇文章希望大家自己能写一个helloworld的命令。
2020 年 5 月 5 日
选型:etcd/ZooKeeper/Consul 等我们该如何选择?
今天我将带你了解主要分布式协调服务的基本原理和彼此之间的差异性。
2021 年 3 月 15 日
Redis 进阶应用:Redis+Lua 脚本实现复合操作
通过阅读本文将Redis+Lua有了一定的了解,并能使用脚本完成一些简单的复合操作。
腾讯云大神用这份“redis 深度笔记”把 Redis 入门到精通全部精髓全部展现出来了
作为这个时代码代码的秃头人员,对Redis肯定是不陌生的,如果连Redis都没用过,还真不好意思出去面试,指不定被面试官吊打多少次。
2021 年 2 月 18 日
Bitmap 为什么那么快?
一、Bitmap是个啥? Bitmap实际上就是String类型的,你可以在Redis里面Help一下可以看到String是有类似bitop、bitpos等位操作。
2020 年 12 月 8 日
SpringCloud 从入门到精通 16---Sentinel 流控
上节中,我们已经搭建了sentinel的web管理界面,并且cloud-sentinel-service-8010已经被sentinel监控了起来,接下来,我们通过几个简单的例子,来了解下sentinel常见的限流方式
2021 年 2 月 2 日
跨集群备份解决方案 MirrorMaker
Apache Kafka社区提供的MirrorMaker工具,可以帮我们实现消息或数据从一个集群到另一个集群的拷贝。
2019 年 8 月 22 日
Sentinel 的注解支持 - @SentinelResource 使用详解
Hello,大家好,我是麦洛,今天带大家来了解一下Sentinel中@SentinelResource的使用方法 ,这篇文章主要向大家介绍一下以下内容
2021 年 3 月 31 日
Redis 系列之扫盲篇(一)
作者:z小赵
2020 年 6 月 17 日
弹力设计篇之“服务的状态”
在容错设计中,服务状态是非常复杂的。尤其对于运维来说,要调度服务就要调度服务的状态,迁移服务的状态就需要迁移服务的数据。
2018 年 3 月 6 日
使用 Redis 作为时间序列数据库:原因及方法
在本文中,《Redis in Action》的作者Josiah Carlson博士将为我们详细阐述如何使用Redis及其有序集合与哈希进行时间序列分析。
42 丨如何使用 Redis 来实现多用户抢票问题
今天我们来更加深入地了解一下Redis的原理,比如Redis的事务处理机制是怎样的?
2019 年 9 月 9 日
火山引擎 Redis 云原生实践
本文整理自火山引擎开发者社区首次 Meetup 中的分享《Redis 云原生实践》,主要介绍了火山引擎 Redis 云原生相关的探索和实践。
2021 年 4 月 8 日
Redis 学习笔记(基础命令)
Redis学习笔记(基础命令)
2020 年 5 月 5 日
Disque:Redis 之父新开源的分布式内存作业队列
Disque是Redis之父Salvatore Sanfilippo新开源的一个分布式内存消息代理。它适应于“Redis作为作业队列”的场景,但采用了一种专用、独立、可扩展且具有容错功能的设计,兼具Redis的简洁和高性能,并且用C语言实现为一个非阻塞网络服务器。
架构设计流程:设计备选方案
今天我来讲讲架构设计流程第2步:设计备选方案,同样还会结合上期“前浪微博”的场景,谈谈消息队列设计备选方案的实战。
2018 年 5 月 22 日
Codis VS Redis Cluster:我该选择哪一个集群方案?
关于Codis和Redis Cluster的选型考虑,我从四个维度给你提供一些建议,希望能帮助到你。
2020 年 11 月 11 日
redis 数据结构介绍三 - 第三部分 整数集合
redis中对于 set 其实有两种处理,对于元素均为整型,并且元素数目较少时,使用 intset 作为底层数据结构,否则使用 dict 作为底层数据结构,先看一下代码先
2020 年 4 月 29 日
推荐阅读
Java 消费者是如何管理 TCP 连接的?
2019 年 7 月 20 日
如何用 Go 语言写出好用的 Http 中间件?
78|集群组件 Rpc 通信机制
2020 年 12 月 10 日
为什么 Redis 快照使用子进程 (一)
为什么要用 Redis 实现事务的 ACID
2021 年 4 月 2 日
10 分钟带你彻底搞懂服务限流和服务降级
2021 年 4 月 16 日
福利拉满!腾讯云大神亲码 Redis 深度手册【基础 + 应用 + 原理 + 集群 + 扩展 + 源码】六大核心知识,全部一次让你搞懂
2021 年 5 月 5 日
电子书
大厂实战PPT下载
换一换 吴亮(月影) | 奇虎360 奇舞团技术总监
肖枭 | 源伞科技 联合创始人兼 CEO
周爱民 | 南潮 架构师
评论