QCon 全球软件开发大会(北京站)门票 9 折倒计时 4 天,点击立减 ¥880 了解详情
写点什么

Redis Sentinel 源码分析(一):Sentinel 的初始化

2020 年 3 月 01 日

Redis Sentinel源码分析(一):Sentinel的初始化

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 函数会监测以下两种条件:


  1. 程序使用 redis-sentinel 可执行文件执行。

  2. 程序参数列表中有–sentinel 标志。

  3. 以上任何一种条件成立则 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(); ............. }
复制代码


  1. 使用 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}  };
复制代码


  1. 初始化 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 函数, 做以下几个工作:


  1. 检查配置文件是否被设置,并且检查程序对配置文件是否有写权限,因为如果 Sentinel 状态改变的话,会不断将自己当前状态记录在配置文件中。

  2. 如果在配置文件中指定运行 ID,Sentinel 会使用这个 ID 作为运行 ID,相反地,如果没有指定运行 ID,Sentinel 会生成一个 ID 用来作为 Sentinel 的运行 ID。

  3. 对所有的 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


2020 年 3 月 01 日 21:42244

评论

发布
暂无评论
发现更多内容

一文看懂 OAuth2

pingan8787

Java 前端 Web oauth2.0

图片处理不用愁,给你十个小帮手

阿宝哥

Java 前端 工具 开源项目 图片

漫画通信:一图看懂通信发展史

阿里云Edge Plus

Python中的@staticmethod和@classmethod的区别

BigYoung

Python classmethod staticmethod

带你解析MySQL binlog

Simon

MySQL Binlog

mac vmware centos7 设置静态IP

愤毛阿青

network vmware Centos 7

30 张图带你分分钟看懂进程和线程基础知识全家桶

爱嘤嘤嘤斯坦

Java 线程 进程 进程线程区别

数据结构与算法知识点总结

hiqian

利用 Python 爬取了 13966 条运维招聘信息,我得出了哪些结论?

JackTian

Python Linux 运维 数据分析 招聘

Node.js与二进制数据流

自然醒

Java node.js 前端 二进制

一些思考

张健

编程能力 —— 异步编程

wendraw

Java 前端进阶训练营 编程能力

关于计划的思考

zhongzhq

SpringBoot入门:00 - 初始化项目

封不羁

Spring Boot java 14

领域驱动设计(DDD)实践之路(一)

vivo互联网技术

架构 领域驱动设计 DDD

流水账

zack

编程能力 —— 寻路问题

wendraw

Java 前端进阶训练营 编程能力

积极支持EdgeX发展,英特尔为2020 EdgeX中国挑战赛获奖队伍创造广阔合作空间

最新动态

不是完成你学习的 KPI ,而是要形成指导你行动的 OKR

非著名程序员

学习 程序员 提升认知 知识管理 程序员成长

Rust是如何保障内存安全的

博文视点Broadview

读书笔记 rust

DDD实施过程中的点滴思考

Winfield

领域驱动设计 DDD

Python类中的__new__和__init__的区别

BigYoung

Python __init__ __new__

信创舆情一线--《关键信息基础设施安全保护条例》纳入2020年立法计划

统小信uos

信息安全

LR.Net平台研发轶事,每一个点都很难,但我们不将就

力软.net/java开发平台

C# .net 跨平台 框架开发

还在划水?这个SQL你能写出来吗?

书旅

php MySQL SQL语法 sql查询

Java集合总结,从源码到并发一路狂飙

给你买橘子

Java 编程 算法 集合

漫画 | 架构设计中的那些事

码农神说

架构设计 架构师 漫画编程

开发者必备——IDEA配置清单

Noneplus

配置 IDEA

性能碾压 POI !利用模板语法快速生成 Excel 报表

Geek_Willie

表格控件 GCExcel 服务器端开发

HTTP/2 总结

guoguo 👻

猿灯塔:spring Boot Starter开发及源码刨析(二)

猿灯塔

Java 猿灯塔 源码刨析

边缘计算隔离技术的挑战与实践

边缘计算隔离技术的挑战与实践

Redis Sentinel源码分析(一):Sentinel的初始化-InfoQ