一、简介

Redis的启动也就是main函数的执行,程序的入口在redis.c中,启动流程:

1. 初始化默认服务器配置,如果是sentinel模式还需进行额外的配置

2. 修改配置文件或配置选项,这其中包括处理诸如-h/--help,-v/--version,--test-memory的特殊选项,获取给定的配置文件,设定的配置选项,然后取得配置文件的绝对路径,重置保存条件,载入配置文件

3. 对服务器进行设置具体的包括:设置服务器为守护进程,创建并初始化服务器中的数据结构(如集群模式),为服务器进程设置名字,打印ASCII LOGO等等

4. 检查maxmemory配置,加载数据、运行事件处理器、监听事件

5.启动完成,AE会定时间去查询各个客户端是否有输入,如果有读取客户端输入并且对命令进行解析,命令信息保存在Hash表中

几个重要的函数

  • initServerConfig() 设定默认的参数值,并读取配置文件redis.conf,若用户配置了某个参数,则用该参数值替换默认值

  • initServer() 该函数主要对server进行初始化,内容包括: 调用anetTcpServer()函数创建socket server作为redis server,并将该server的句柄加到epoll/kqueue的监听队列中。 一旦有client接入,便会对该client触发操作acceptTcpHandler,该操作是调用aeCreateFileEvent注册的

  • anetTcpServer() 为建立网络套接字服务器的方法,对socket(), bind()和listen()等函数进行了封装

  • aeMain(aeEventLoop *eventLoop) 是启动事件轮询的入口,内部实现为一循环,不断处理来自客户的请求

二、初始化过程

1、执行下述语句

#ifdef INIT_SETPROCTITLE_REPLACEMENT
spt_init(argc, argv);
#endif

其中INIT_SETPROCTITLE_REPLACEMENT的定义config.h中

/* Check if we can use setproctitle().
* BSD systems have support for it, we provide an implementation for
* Linux and osx. */
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
#define USE_SETPROCTITLE
#endif #if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
#define USE_SETPROCTITLE
#define INIT_SETPROCTITLE_REPLACEMENT
void spt_init(int argc, char *argv[]);
void setproctitle(const char *fmt, ...);
#endif

实现了对Linux和OSX的该功能的扩展(BSD系统已经支持该功能,而Linux和APPLE不支持)

2、setlocale

setlocale(LC_COLLATE,"");//系统调用,用来配置本地化信息。

3、zmalloc 的一些配置

zmalloc_enable_thread_safeness(); //开启了内存分配管理的线程安全变量,当内存分配时,
//redis会统计一个总内存分配量,这是一个共享资源,所以需要原子性操作,
//在redis的内存分配代码里,当需要原子操作时,就需要打开线程安全变量。
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
//是一个内存分配错误处理,当无法得到需要的内存量时,会调用redisOutOfMemoryHandler函数。

4、随机函数种子

srand(time(NULL)^getpid());//设置随机种子
gettimeofday(&tv,NULL);//获取当前日期
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
server.sentinel_mode = checkForSentinelMode(argc,argv);
//设置哈希函数需要使用的随机种子 服务器的启动模式:单机模式、Cluster模式、sentinel模式

5、初始化服务器配置

initServerConfig();
//在该函数中除了进行属性的初始化外,主要初始化了命令表。
//调用了函数populateCommandTable,将命令集分布到一个hash table中。
//避免使用if分支来做命令处理的效率底下问题,而放到hash table中,在理想的情况下只需一次就能定位命令的处理函数。

initServerConfig()功能详细介绍:

  • 初始化服务器的状态

  • 初始化LRU时间

  • 设置保存条件

  • 初始化和复制相关的状态

  • 初始化PSYNC命令使用的backlog(回溯)

  • 设置客户端的输出缓冲区限制

  • 初始化浮点常量

  • 初始化命令表

  • 初始化慢查询日志

  • 初始化调试项

  • 初始化内存Swap相关设置

这个函数初始化了一个全局变量 struct redisServer server

struct redisServer {
/* General */
redisDb *db;
dict *commands; /* Command table hahs table */
aeEventLoop *el;
unsigned lruclock:22; /* Clock incrementing every minute, for LRU */
unsigned lruclock_padding:10;
int shutdown_asap; /* SHUTDOWN needed ASAP */
int activerehashing; /* Incremental rehash in serverCron() */
char *requirepass; /* Pass for AUTH command, or NULL */
char *pidfile; /* PID file path */
int arch_bits; /* 32 or 64 depending on sizeof(long) */
int cronloops; /* Number of times the cron function run */
char runid[REDIS_RUN_ID_SIZE+1]; /* ID always different at every exec. */
int sentinel_mode; /* True if this instance is a Sentinel. */
/* Networking */
int port; /* TCP listening port */
char *bindaddr; /* Bind address or NULL */
char *unixsocket; /* UNIX socket path */
mode_t unixsocketperm; /* UNIX socket permission */
int ipfd; /* TCP socket file descriptor */
int sofd; /* Unix socket file descriptor */
int cfd; /* Cluster bus lisetning socket */
list *clients; /* List of active clients */
list *clients_to_close; /* Clients to close asynchronously */
list *slaves, *monitors; /* List of slaves and MONITORs */
redisClient *current_client; /* Current client, only used on crash report */
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
……
int bug_report_start; /* True if bug report header was already logged. */
int watchdog_period; /* Software watchdog period in ms. 0 = off */
};

在这个函数中,对server变量进行了部分成员的初始化,其中:

  • runid:运行该redis服务器端程序的唯一标识,即每次启动都会一个唯一ID,用来区分不同的redis服务器端程序

  • maxidletime:最大空闲时间,就是client连接到server时,如果超出这个值,就会被自动断开,当然,master和slave节点不包括;如果client有阻塞命令在运行,也不会断开

saveparams:这个存储的是redis服务器端程序从配置文件中读取的持久化参数,如配置文件所述

save 900 1
save 300 10
save 60 10000

lruclock:是redis实现LRU算法所需的,每个redis object都带有一个lruclock,用来从内存中移除空闲的对象

6、sentinel_mode

是否开启redis的哨兵模式,也就是是否监测,通知,自动错误恢复,是用来管理多个redis实例的方式。

if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}

7、读取参数选项

if (argc >= 2) {
int j = 1; /* First option to parse in argv[] */
sds options = sdsempty();
char *configfile = NULL; /* Handle special options --help and --version */
if (strcmp(argv[1], "-v") == 0 ||
strcmp(argv[1], "--version") == 0) version();
if (strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "-h") == 0) usage();
if (strcmp(argv[1], "--test-memory") == 0) {
if (argc == 3) {
memtest(atoi(argv[2]),50);
exit(0);
} else {
fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");
fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");
exit(1); configfile = argv[j++];
/* All the other options are parsed and conceptually appended to the
* configuration file. For instance --port 6380 will generate the
* string "port 6380\n" to be parsed after the actual file name
* is parsed, if any. */
while(j != argc) {
if (argv[j][0] == '-' && argv[j][1] == '-') {
/* Option name */
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
} else {
/* Option argument */
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
}
j++;
}
if (server.sentinel_mode && configfile && *configfile == '-') {
redisLog(REDIS_WARNING,
"Sentinel config from STDIN not allowed.");
redisLog(REDIS_WARNING,
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
}
if (configfile) server.configfile = getAbsolutePath(configfile);
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
} else {
redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "se
ntinel" : "redis");
}

8、服务器初始化

if (server.daemonize) daemonize();//daemonize()用于在shell启动时后台运行
initServer();
if (server.daemonize) createPidFile();
redisAsciiArt();

initServer主要做以下工作:

  • 设置信号处理程序,如sighup, sigpipe等

  • 打开系统日志文件

  • 继续初始化server结构,如server.clients, server.slaves, server.monitors等

  • 创建共享对象,这里的共享对象是一个struct sharedObjectsStruct shared;它用于全局文本信息的保存,避免每次发送固定格式的信息给clients都需要创建一个新的字符串。如:

shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n"));
shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n"));

还包括了从1到10000的redis 整数对象的创建,在这部分范围的整数经常被用到,所以先创建了,可以反复重用。

  • 调整系统对文件操作参数的约束大小,如最大打开的文件数。

  • 创建ae事件循环

  • 初始化server.db

  • 开启监听redis端口或者unix socket

  • 创建Pub/Sub通道
  • 初始化server结构的统计变量,如执行的命令数,连接数,过期键等等,还有跟踪每秒操作的时间和命令数

  • 创建ae时间事件,也是redis的核心循环,该过程是serverCron,每秒调用次数由一个叫REDIS_HZ的宏决定,默认是每10微秒超时,即每10微秒该ae时间事件处理过程serverCron会被过期调用

  • 创建ae文件事件,对redis的TCP或者unix socket端口进行监听,使用相应的处理函数注册。每次得到clients连接后,都会创建ae文件事件,异步接收命令

  • 针对配置文件,设置是否开启aof和最大使用内存

  • 如果有集群设置,初始化集群。初始化lua脚本处理,初始化slowlog和bio(background io)。bio是异步io操作,用于redis读取或存取时的io操作

  • 如果开启了VM,则初始化虚拟内存相关的IO/Thread

9、serverCron核心循环

#define run_with_period(_ms_) if (!(server.cronloops%((_ms_)/(1000/REDIS_HZ))))

这个宏类似于条件判断,每ms时间执行一次后续的操作。如:

run_with_period(100) trackOperationsPerSecond();

每百微秒,执行一次跟踪操作函数,记录这段时间的命令执行情况。这个循环有以下任务需要执行:

  • 如果设置了watchdog_period,那么每过watchdog_period,都会发送sigalrm信号,该信号又会得到处理,来记录此时执行的命令。这个过程主要是为了了解一些过长命令的执行影响服务器的整体运行,是一个debug过程。

  • 每百微秒记录过去每秒的命令执行情况。

  • 更新统计变量,如内存使用总数,更新server.lruclock

  • 是否得到关闭程序的信号,如果是,就进入关闭程序的节奏,如aof,rdb文件的处理,文件描述符的关闭等

  • 每5秒输出一次redis数据库的使用情况,连接数,总键值数

  • 每次都尝试resize每个db,resize是让每个db的dict结构进入rehash状态,rehash是为了扩容dict或者缩小dict。然后每次都尝试执行每个db的rehash过程一微秒。

  • 每次调用clientCron例程,这是一个对server.clients列表进行处理的过程。再每次执行clientCron时,会对server.clients进行迭代,并且保证 1/(REDIS_HZ*10) of clients per call。也就是每次执行clientCron,如果clients过多,clientCron不会遍历所有clients,而是遍历一部分clients,但是保证每个clients都会在一定时间内得到处理。处理过程主要是检测client连接是否idle超时,或者block超时,然后会调解每个client的缓冲区大小。

  • 对aof,rdb等过程进行开启或终结。

  • 如果是master节点的话,就开始对过期的键值进行处理,与处理clients类似,不是多所有有时间限制的键值进行迭代,而是在一个限定的数量内迭代一部分,保证一定时间内能检测所有键值。

  • 对异步io过程中可能需要关闭的clients进行处理。

  • 每秒调用复制例程和集群例程,每0.1秒调用哨兵例程。

10、aeMain

aeSetBeforeSleepProc(server.el,beforeSleep);
aeMain(server.el);
aeDeleteEventLoop(server.el);

在每次ae循环进入阻塞时,都会先执行beforeSleep(),在该函数中,会对unblock的clients(指使用blpop等阻塞命令的clients)进行处理,并且执行fsync函数,同步内存到磁盘上。

11、总结

redis启动->初始化server结构部分变量->从命令行和配置文件中读取配置选项进行初始化->创建ae事件循环->创建ae时间事件调用redis运行的必需任务(serverCron)和创建ae文件事件监听端口->收到client连接时,创建对应的文件事件来纳入ae事件循环进行异步接受->收到关闭请求,在serverCron中执行关闭步骤->redis关闭

作者在代码中处处对运行例程进行约束,保证不过长的陷入某一个不友好的命令中,如检查过期键值和处理过多的clients。

  

参考文章

http://olylakers.iteye.com/blog/1228198

http://blog.csdn.net/zwan0518/article/details/50281175

http://www.wzxue.com/%E8%A7%A3%E8%AF%BBredis%E8%BF%90%E8%A1%8C%E6%A0%B8%E5%BF%83%E5%BE%AA%E7%8E%AF%E8%BF%87%E7%A8%8B/

http://studentdeng.github.io/blog/2013/08/19/redis-start-up/

http://olylakers.iteye.com/blog/1277256

关于Redis的启动过程的更多相关文章

  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. Redis的启动过程

    本文主要介绍Redis启动加载过程,总体上可以分为如下几步: 1. 初始化全局服务器配置 2. 加载配置文件(如果指定了配置文件,否则使用默认配置) 3. 初始化服务器 4. 加载数据库 5. 网络监 ...

  5. 【源码】Redis Server启动过程

    本文基于社区版Redis 4.0.8       1. 初始化参数配置 由函数initServerConfig()实现,具体操作就是给配置参数赋初始化值: //设置时区 setlocale(LC_CO ...

  6. redis(一)内部机制的介绍和启动过程

    redis(一)内部机制的介绍和启动过程 redis的基本介绍 redis服务端 redis客户端 redis的持久化 redis中的文件事件和时间时间 redis的启动过程 redis的基本介绍 r ...

  7. redis源码笔记(一) —— 从redis的启动到command的分发

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/redis1 本博客同步在http://www.cnblog ...

  8. laravel启动过程简单解析

    :first-child{margin-top:0!important}img.plugin{box-shadow:0 1px 3px rgba(0,0,0,.1);border-radius:3px ...

  9. laravel的启动过程---摘自网络博客个人学习之用

    如果没有使用过类似Yii之类的框架,直接去看laravel,会有点一脸迷糊的感觉,起码我是这样的.laravel的启动过程,也是laravel的核心,对这个过程有一个了解,有助于得心应手的使用框架,希 ...

随机推荐

  1. Linux内核知识

    版本 linus树 Linux内核最初创始人--Linus Torvalds管理一个Linus树,linus树也称为主线(mainline).一般指的upstream,"上游",也 ...

  2. Node.js爬虫数据抓取 -- 问题总结

    一  返回的信息提示  Something went wrong  request模块请求出现未知错误 其中,所用代码如下(无User-Agent部分) 问题多次派查无果,包括: 1:postman请 ...

  3. 原生javascript模仿win8等待进度条。

    一.序言 一直很中意win8等待提示圆圈进度条.win8刚出来那会,感觉好神奇!苦于当时没思路,没去研究.通过最近网上找找资料,终于给搞出来了!先上Demo,献丑了!预览请看:win8进度条. 二.简 ...

  4. c# tcp备忘及networkstream.length此流不支持查找解决

    服务端 bool isRunning = true;  MouseKeyBoard mk = new MouseKeyBoard(); void InitTcpServer(int port) { T ...

  5. 看了一本Unity3D的教程

    国内写的<Unity 3D游戏开发>. 实例挺多,对于有基础的人来说,上手会挺快的: 但进阶的东西没有涉及,可能与书的定位有关吧

  6. java之AbstractStringBuilder类详解

    目录 AbstractStringBuilder类 字段 构造器 方法   public abstract String toString() 扩充容量 void  expandCapacity(in ...

  7. 泛函编程(9)-异常处理-Option

    Option是一种新的数据类型.形象的来描述:Option就是一种特殊的List,都是把数据放在一个管子里:然后在管子内部对数据进行各种操作.所以Option的数据操作与List很相似.不同的是Opt ...

  8. 干净的停止tomcat/java应用程序

    通常在使用了jdbc或者netty的应用程序中,当shutdown tomcat或java应用程序时,会出现无法停止的情况,报类似如下错误: 严重: The web application [] re ...

  9. 每日微软面试题——day 6(打印所有对称子串)

    每日微软面试题——day 6(打印所有对称子串) 分类: 2.数据结构与算法2011-08-14 14:27 9595人阅读 评论(15) 收藏 举报 面试微软string测试systemdistan ...

  10. rabbitmq+ keepalived+haproxy高可用集群详细命令

    公司要用rabbitmq研究了两周,特把 rabbitmq 高可用的研究成果备下 后续会更新封装的类库 安装erlang wget http://www.gelou.me/yum/erlang-18. ...