本文基于社区版Redis 4.0.8
 
 
 

1、 初始化参数配置

由函数initServerConfig()实现,具体操作就是给配置参数赋初始化值:
//设置时区
setlocale(LC_COLLATE,"");
//设置随机种子
char hashseed[16];
getRandomHexChars(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed);
//初始化module
void initServerConfig(void) {
//serverCron函数执行频率,默认10ms
server.hz = CONFIG_DEFAULT_HZ;
//监听端口,默认6379
server.port = CONFIG_DEFAULT_SERVER_PORT;
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
server.dbnum = CONFIG_DEFAULT_DBNUM;
......
//初始化命令表
server.commands = dictCreate(&commandTableDictType,NULL);
server.orig_commands = dictCreate(&commandTableDictType,NULL);
populateCommandTable();
server.delCommand = lookupCommandByCString("del");
server.multiCommand = lookupCommandByCString("multi");
server.lpushCommand = lookupCommandByCString("lpush");
server.lpopCommand = lookupCommandByCString("lpop");
server.rpopCommand = lookupCommandByCString("rpop");
server.sremCommand = lookupCommandByCString("srem");
server.execCommand = lookupCommandByCString("exec");
server.expireCommand = lookupCommandByCString("expire");
server.pexpireCommand = lookupCommandByCString("pexpire");
......
}
 

2、 加载并解析配置文件

在这一阶段,会对命令行传入的参数进行解析,并且调用 loadServerConfig 函数,对命令行参数和配置文件中的参数进行合并处理,然后为 Redis 各功能模块的关键参数设置合适的取值,以便 server 能高效地运行。
//filename表示配置文件全路径名称;
//options表示命令行输入的配置参数,如port=4000
void loadServerConfig(char *filename, char *options) {
sds config = sdsempty();
char buf[CONFIG_MAX_LINE+1]; /* 加载配置文件到内存 */
if (filename) {
FILE *fp; if (filename[0] == '-' && filename[1] == '\0') {
fp = stdin;
} else {
if ((fp = fopen(filename,"r")) == NULL) {
serverLog(LL_WARNING,
"Fatal error, can't open config file '%s'", filename);
exit(1);
}
}
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
config = sdscat(config,buf);
if (fp != stdin) fclose(fp);
}
/* Append the additional options */
if (options) {
config = sdscat(config,"\n");
config = sdscat(config,options);
}
//解析配置
loadServerConfigFromString(config);
sdsfree(config);
}

3、 初始化服务器内部变量

在完成对运行参数的解析和设置后,main 函数会调用 initServer 函数,对 server 运行时的各种资源进行初始化工作。包括了 server 资源管理所需的数据结构初始化、键值对数据库初始化、server 网络框架初始化等。
最后调用 loadDataFromDisk 函数,从磁盘上加载 AOF 或者是 RDB 文件,以便恢复之前的数据。
void initServer(void) {
/* 初始化需要的各种资源 */
server.clients = listCreate();//初始化客户端链表
server.pid = getpid();
server.current_client = NULL;
server.clients = listCreate();
server.clients_to_close = listCreate();
server.slaves = listCreate();
server.monitors = listCreate();
server.clients_pending_write = listCreate();
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
server.unblocked_clients = listCreate();
server.ready_keys = listCreate();
server.clients_waiting_acks = listCreate();
server.get_ack_from_slaves = 0;
server.clients_paused = 0;
server.system_memory_size = zmalloc_get_memory_size(); createSharedObjects();
//调用aeCreateEventLoop函数创建aeEventLoop结构体,并赋值给server结构的el变量
//maxclients 变量的值大小,可以在 Redis 的配置文件 redis.conf 中进行定义,默认值是 1000
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
if (server.el == NULL) {
serverLog(LL_WARNING,
"Failed creating the event loop. Error message: '%s'",
strerror(errno));
exit(1);
}
...... /* 创建数据库结构*/
for (j = 0; j < server.dbnum; j++) {
server.db[j].dict = dictCreate(&dbDictType,NULL);
server.db[j].expires = dictCreate(&keyptrDictType,NULL);
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
server.db[j].id = j;
server.db[j].avg_ttl = 0;
} ...... //创建事件循环框架
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);

//开始监听设置的网络端口
if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
exit(1);

//为server后台任务创建定时事件
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
serverPanic("Can't create event loop timers.");
exit(1);
}

//为每一个监听的IP设置连接事件的处理函数acceptTcpHandler
for (j = 0; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR)
{ … }
}
}

4、执行事件驱动框架

事件驱动框架是 Redis server 运行的核心。该框架一旦启动后,就会一直循环执行,每次循环会处理一批触发的网络读写事件。main 函数直接调用事件框架的主体函数 aeMain(在ae.c文件中)后,就进入事件处理循环了。当然,在进入事件驱动循环前,main 函数会分别调用 aeSetBeforeSleepProc 和 aeSetAfterSleepProc 两个函数,来设置每次进入事件循环前 server 需要执行的操作,以及每次事件循环结束后 server 需要执行的操作。下面代码显示了这部分的执行逻辑,你可以看下。
 
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
aeMain(server.el);
aeDeleteEventLoop(server.el);
 
 
 
 

【源码】Redis Server启动过程的更多相关文章

  1. 曹工说Redis源码(3)-- redis server 启动过程完整解析(中)

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  2. 曹工说Redis源码(5)-- redis server 启动过程解析,以及EventLoop每次处理事件前的前置工作解析(下)

    曹工说Redis源码(5)-- redis server 启动过程解析,eventLoop处理事件前的准备工作(下) 文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis ...

  3. 曹工说Redis源码(2)-- redis server 启动过程解析及简单c语言基础知识补充

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  4. workerman源码分析之启动过程

    PHP一直以来以草根示人,它简单,易学,被大量应用于web开发,非常可惜的是大部分开发都在简单的增删改查,或者加上pdo,redis等客户端甚至分布式,以及规避语言本身的缺陷.然而这实在太委屈PHP了 ...

  5. 跟着大彬读源码 - Redis 1 - 启动服务,程序都干了什么?

    一直很羡慕那些能读 Redis 源码的童鞋,也一直想自己解读一遍,但迫于 C 大魔王的压力,解读日期遥遥无期. 相信很多小伙伴应该也都对或曾对源码感兴趣,但一来觉得自己不会 C 语言,二来也不知从何入 ...

  6. Nimbus<三>Storm源码分析--Nimbus启动过程

    Nimbus server, 首先从启动命令开始, 同样是使用storm命令"storm nimbus”来启动看下源码, 此处和上面client不同, jvmtype="-serv ...

  7. 【Kafka源码】KafkaController启动过程

    [TOC] 之前聊过了很多Kafka启动过程中的一些加载内容,也知道了broker可以分为很多的partition,每个partition内部也可以分为leader和follower,主从之间有数据的 ...

  8. 【Kafka源码】Kafka启动过程

    一般来说,我们是通过命令来启动kafka,但是命令的本质还是调用代码中的main方法,所以,我们重点看下启动类Kafka.源码下下来之后,我们也可以通过直接运行Kafka.scala中的main方法( ...

  9. spark源码阅读--SparkContext启动过程

    ##SparkContext启动过程 基于spark 2.1.0  scala 2.11.8 spark源码的体系结构实在是很庞大,从使用spark-submit脚本提交任务,到向yarn申请容器,启 ...

随机推荐

  1. Shell实现文件内容批量替换的方法

    在Linux系统中,文件内容的批量替换同Windows平台相比要麻烦一点.不过这里可以通过Shell命令或脚本的方式实现批量替换的功能. 笔者使用过两个命令:perl和sed ,接下来会对其做出说明. ...

  2. 【C++】关键字回忆leetcode题解

    20200515 前缀和 no.560 20200518 动态规划 no.152 20200520 状态压缩 no.1371 20200521 中心扩散 no.5 20200523 滑动窗口 no.7 ...

  3. SNGAN

    目录 概 主要内容 Miyato T., Kataoka T., Koyama M & Yoshida Y. SPECTRAL NORMALIZATION FOR GENERATIVE ADV ...

  4. Mysql数据库语言学习的路线

    对于我们数据库的学习,不管是测试人员还是开发人员以及我们的DBA来说重点都是SQL:但是我们的SQL可以分多少类型,学习重点又是在哪里呢,本文仅仅针对测试人员来展开说明: SQL:structure ...

  5. Python基础入门(9)- Python文件操作

    1.文件的读写 1.1.文件的创建与写入 利用内置函数open获取文件对象 文件操作的模式之写入 文件对象的操作方法之写入保存 1 # coding:utf-8 2 3 import os 4 5 c ...

  6. pytest之 fixture 实现机制

    一.相同测试数据存放优化 在讲 fixture 实现机制之前,插入一段内容 上次有个小伙伴问我说,类似下面的用例代码情况,每条测试用例的数据都一样的,我们可以怎么进行优化吗? 当然是可以的 其实我们可 ...

  7. Java实习生常规技术面试题每日十题Java基础(八)

    目录 1.解释内存中的栈(stack).堆(heap)和静态区(static area)的用法. 2.怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串? 3.运行时异常与受检异常有 ...

  8. C# 高性能对象复制

    需求背景:对象复制性能优化:同时,在对象复制时,应跳过引用类型的null值复制,值类型支持值类型向可空类型的复制 -------------- 1 using Common; 2 using Syst ...

  9. awk 内置变量

    awk 内置变量 FS 设置输入域分隔符,等价于命令行 -F选项 OFS 输出域分隔符 NF 浏览记录的域的个数NR 已读的记录数RS 控制记录分隔符 ORS 输出记录分隔符 substr 截取指定的 ...

  10. python 数据库查询,将查询的列名也保存下来,返回字典类型的数据

    前言: python + pymysql:通过pymysql,查询到符合要求的数据,不做类型转化的,返回的时元组类型的数据,可以根据下标,取想要的值. self.cursor.execute(sql) ...