Redis启动过程详解.docx_第1页
Redis启动过程详解.docx_第2页
Redis启动过程详解.docx_第3页
Redis启动过程详解.docx_第4页
Redis启动过程详解.docx_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Redis启动过程Redis的启动也就是main函数的执行。Redis的主main(程序的入口)在redis.c中。Redis启动流程:1. 初始化库和默认服务器配置,如果是sentinel模式还需进行额外的配置。2. 修改配置文件或配置选项这其中包括处理诸如-h/-help,-v/-version,-test-memory的特殊选项,获取给定的配置文件,设定的配置选项,然后取得配置文件的绝对路径,重置保存条件,载入配置文件。3. 对服务器进行设置具体的包括:设置服务器为守护进程,创建并初始化服务器中的数据结构(initserver函数)(集群模式),为服务器进程设置名字,打印ASCII LOGO等4. 检查maxmemory配置,运行事件处理器,监听事件。一、初始化过程具体的:首先执行下述语句:#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 _APPLE_)#define USE_SETPROCTITLE#define INIT_SETPROCTITLE_REPLACEMENTvoid spt_init(int argc, char *argv);void setproctitle(const char *fmt, .);#endif由于BSD系统已经支持该功能,而Linux和APPLE不支持。所以上述代码实现了对Linux和OSX的该功能的扩展。接下来的语句是:setlocale(LC_COLLATE,);系统调用,用来配置本地化信息。/zmalloc 需要的一些配置zmalloc_enable_thread_safeness();zmalloc_set_oom_handler(redisOutOfMemoryHandler);srand(time(NULL)getpid();/设置随机种子gettimeofday(&tv,NULL);/获取当前日期dictSetHashFunctionSeed(tv.tv_sectv.tv_usecgetpid();/设置哈希函数需要使用的随机种子服务器的启动模式:单机模式、Cluster模式、sentinel模式,详细介绍见Redis设计与实现第二版的第十四章、第十六章和第十七章。/ 初始化服务器配置initServerConfig();在该函数中除了进行属性的初始化外,主要初始化了命令表。调用了函数populateCommandTable,将命令集分布到一个hash table中。避免使用if分支来做命令处理的效率底下问题,而放到hash table中,在理想的情况下只需一次就能定位命令的处理函数。initServerConfig()功能详细介绍:初始化服务器的状态,初始化LRU时间,设置保存条件,初始化和复制相关的状态,初始化PSYNC命令使用的backlog(回溯),设置客户端的输出缓冲区限制,初始化浮点常量,初始化命令表,初始化慢查询日志,初始化调试项。命令的执行:initServerConfig()函数中涉及命令表的相关源码如下:void initServerConfig() mands = 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);/1 mands = dictCreate(&commandTableDictType,NULL); server.orig_commands = dictCreate(&commandTableDictType,NULL);为了加快命令的查找,使用字典来存储命令。其中命令的名称作为键,命令recommand作为值。考虑到用户由于某种原因可能会定制命令,所以创建两个字典分别用来存储“受到rename配置影响的命令表(server. ocommands)”和“未受rename配置影响的源命令表(server. orig_commands)”。其中redisCommand的结构体如下:命令表解释:name:命令的名字proc:一个指向命令的实现函数的指针arity:参数的数量。可以用 -N 表示 = N sflags:字符串形式的 FLAG,用来计算以下的真实 FLAG flags:位掩码形式的 FLAG,根据 sflags 的字符串计算得出get_keys_proc:一个可选的函数,用于从命令中取出 key 参数,仅在以下三个参数都不足以表示 key 参数时使用first_key_index:第一个 key 参数的位置last_key_index:最后一个 key 参数的位置key_step:从 first 参数和 last 参数之间,所有 key 的步数(step)。比如说, MSET 命令的格式为 MSET key value key value . 它的 step 就为 2microseconds:执行这个命令耗费的总微秒数calls:命令被执行的总次数其中flags可以识别的命令标识影响flags的位置如下:/ 命令标志#define REDIS_CMD_WRITE 1 /* 20 w flag */#define REDIS_CMD_READONLY 2 /*21 r flag */#define REDIS_CMD_DENYOOM 4 /* m flag */#define REDIS_CMD_NOT_USED_1 8 /* no longer used flag */#define REDIS_CMD_ADMIN 16 /* a flag */#define REDIS_CMD_PUBSUB 32 /* p flag */#define REDIS_CMD_NOSCRIPT 64 /* s flag */#define REDIS_CMD_RANDOM 128 /* R flag */#define REDIS_CMD_SORT_FOR_SCRIPT 256 /* S flag */#define REDIS_CMD_LOADING 512 /* l flag */#define REDIS_CMD_STALE 1024 /* t flag */#define REDIS_CMD_SKIP_MONITOR 2048 /* M flag */#define REDIS_CMD_ASKING 4096 /* k flag */2/根据 redis.c 中提供的命令列表redisCommandTable,创建命令表populateCommandTable();populateCommandTable()函数的具体执行如下:轮询静态命令表redisCommandTable中的每条命令redisCommand,对每条命令(假定为Command A):(1)将命令A中的字符串FLAG-sflags转换成位掩码形式FLAG-flags(2)将命令A分别添加到命令表(server. ocommands)和源命令表(server. orig_commands)中。其中命令的名称作为键,命令recommand作为值。/3/使用变量存储经常查找的命令,便于快速访问(空间换时间)server.delCommand = lookupCommandByCString(del); server.multiCommand = lookupCommandByCString(multi); server.lpushCommand = lookupCommandByCString(lpush); server.lpopCommand = lookupCommandByCString(lpop); server.rpopCommand = lookupCommandByCString(rpop);这样,就在initServerConfig()函数中,完成将由静态数组存储的命令表转换成由字典存储的命令表,并使用变量存储了部分常用的命令,加快了命令的查找速度。接着在initServer()中打开UNIX本地端口并将使用的套接字关联文件事件,添加对命令请求的监听事件。initServer();void initServer() /* Open the listening Unix domain socket. */ / 打开 UNIX 本地端口 if (server.unixsocket != NULL) unlink(server.unixsocket); /* dont care if this fails */ server.sofd = anetUnixServer(err,server.unixsocket, server.unixsocketperm, server.tcp_backlog); if (server.sofd = ANET_ERR) redisLog(REDIS_WARNING, Opening socket: %s, err); exit(1); anetNonBlock(NULL,server.sofd); / 为本地套接字关联应答处理器 if (server.sofd 0 & aeCreateFileEvent(server.el,server.sofd,AE_READABLE, acceptUnixHandler,NULL) = AE_ERR) redisPanic(Unrecoverable error creating server.sofd file event.);执行完initServer()后,完成了事件的注册,接下来开启事件处理器,本地端口的命令请求发出后,就会被事件处理器监听到,然后进行一系列的响应操作,完成请求命令的执行。int main() / 运行事件处理器,一直到服务器关闭为止 aeSetBeforeSleepProc(server.el,beforeSleep);aeMain(server.el);待补充:命令的查找大小写无关的查找算法(未完待续!)服务器启动模式:在分析Redis启动过程中,涉及的关键技术之一就是服务器的启动。经过源码分析,可以知道服务器的启动模式大致可以分为:单机模式,Cluster(集群)模式和Sentinel(哨兵)模式。二、配置设置服务器在用initServerConfig函数初始化完server变量后,就会载入用户给定的配置文件,设置提供的配置参数。关于启动配置设置的几种情况:main()函数中参数解析如下:int main(int argc, char *argv) / 检查用户是否指定了配置文件,或者配置选项 if (argc = 2) int j = 1; /* First option to parse in argv */ sds options = sdsempty(); char *configfile = NULL; /* First argument is the config file name? */ / 如果第一个参数(argv1)不是以 - 开头 / 那么它应该是一个配置文件 if (argvj0 != - | argvj1 != -) configfile = argvj+; / 对用户给定的其余选项进行分析,并将分析所得的字符串追加稍后载入的配置文件的内容之后 / 比如 -port 6380 会被分析为 port 6380n while(j != argc) if (argvj0 = - & argvj1 = -) /* Option name */ if (sdslen(options) options = sdscat(options,n); options = sdscat(options,argvj+2); options = sdscat(options, ); else /* Option argument */ options = sdscatrepr(options,argvj,strlen(argvj); options = sdscat(options, ); j+; if (configfile) server.configfile = getAbsolutePath(configfile); / 重置保存条件 resetServerSaveParams(); / 载入配置文件, options 是前面分析出的给定选项 loadServerConfig(configfile,options); sdsfree(options); / 获取配置文件的绝对路径 if (configfile) server.configfile = getAbsolutePath(configfile); 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, argv0, server.sentinel_mode ? sentinel : redis);集群模式下,如果未指定配置文件,则自动生成名为nodes.conf的配置文件:void clusterInit(void) /* Load or create a new nodes configuration. */ if (clusterLoadConfig(server.cluster_configfile) = REDIS_ERR) saveconf = 1; / 保存 nodes.conf 文件if (saveconf) clusterSaveConfigOrDie(1);三、服务器配置服务器在载入用户指定的配置选项并对sever状态进行更新后,server紧接着就开始初始化服务器使用到的数据结构。使用到的函数为:initServer();首先设置信号处理函数。 signal(SIGHUP, SIG_IGN);/ SIGHUP表示终端挂起或者控制进程终止 signal(SIGPIPE, SIG_IGN);/ SIGPIPE管道破裂: 写一个没有读端口的管道写管道时,发现读进程终止所产生的信号setupSignalHandlers();setupSignalHandlers函数处理的信号分两类:1)SIGTERMSIGTERM是kill命令发送的系统默认终止信号。也就是我们在试图结束server时会触发的信号。对这类信号,redis并没有立即终止进程,其处理行为是,设置一个server.shutdown_asap,然后在下一次执行serverCron时,调用prepareForShutdown做清理工作,然后再退出程序。这样可以有效的避免盲目的kill程序导致数据丢失,使得server可以优雅的退出。2)SIGSEGV、SIGBUS、SIGFPE、SIGILL上述信号分别为无效内存引用(即我们常说的段错误),实现定义的硬件故障,算术运算错误(如除0)以及执行非法硬件指令。这类是非常严重的错误,redis的处理是通过sigsegvHandler,记录出错时的现场、执行必要的清理工作,然后kill自身。除上面提到的7个信号意外,redis不再处理任何其他信号,均保留默认操作。 / 设置 syslog,用于守护进程的日志记录 if (server.syslog_enabled) openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT, server.syslog_facility); / 初始化并创建数据结构 server.current_client = NULL; server.clients = listCreate(); server.clients_to_close = listCreate(); server.slaves = listCreate(); server.monitors = 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; / 创建共享对象 createSharedObjects();Redis在初始化时会把后续server执行过程中普遍需要的对象构造出来,如对执行成功的反馈值“+OK”,特定类型的错误值“+-ERR no such keyrn”等等,这些对象多用在与客户端的响应的纯文本协议之中,现在版本共有40+,避免了临时申请对象的开销,简化了资源的管理。server.el = aeCreateEventLoop();server.db = zmalloc(sizeof(redisDb)*server.dbnum);aeCreateEventLoop()函数/初始化事件处理器状态server.db = zmalloc(sizeof(redisDb)*server.dbnum)/初始化维护db所需要的数据结构/网络-端口打开 /* Open the TCP listening socket for the user commands. */ / 打开 TCP 监听端口,用于等待客户端的命令请求 if (server.port != 0 & listenToPort(server.port,server.ipfd,&server.ipfd_count) = REDIS_ERR) exit(1); /* Open the listening Unix domain socket. */ / 打开 UNIX 本地端口 if (server.unixsocket != NULL) unlink(server.unixsocket); /* dont care if this fails */ server.sofd = anetUnixServer(err,server.unixsocket, server.unixsocketperm, server.tcp_backlog); if (server.sofd = ANET_ERR) redisLog(REDIS_WARNING, Opening socket: %s, err); exit(1); anetNonBlock(NULL,server.sofd); /* Abort if there are no listening sockets at all. */ if (server.ipfd_count = 0 & server.sofd 0) redisLog(REDIS_WARNING, Configured to not listen anywhere, exiting.); exit(1); /* Create the Redis databases, and initialize other internal state. */ / 创建并初始化数据库结构 for (j = 0; j server.dbnum; j+) server.dbj.dict = dictCreate(&dbDictType,NULL); server.dbj.expires = dictCreate(&keyptrDictType,NULL); server.dbj.blocking_keys = dictCreate(&keylistDictType,NULL); server.dbj.ready_keys = dictCreate(&setDictType,NULL); server.dbj.watched_keys = dictCreate(&keylistDictType,NULL); server.dbj.eviction_pool = evictionPoolAlloc(); server.dbj.id = j; server.dbj.avg_ttl = 0;/创建或重构AOF缓存aofRewriteBufferReset(); / 为 serverCron() 创建时间事件 if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) = AE_ERR) redisPanic(Cant create the serverCron time event.); exit(1); 这里将serverCron设置为每毫秒调用server.hz次,而server.hz的默认执行次数为10次(#define REDIS_DEFAULT_HZ 10)。也就是说,默认情况下serverCron每100ms执行一次。这里给出serverCron的详细分析:if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);这是Redis 2.6添加的新特征:软件看门狗。功能是:如果我们不能够以足够快的速度返回到这里,将提交SIGALRM信号给信号处理程序。这是个调试工具用于诊断Redis的延迟问题。默认情况下,server.watchdog_period=0,也就是说默认情况下watchdog功能是关闭的,需要手动开启。watchdog的使用流程:(1)通过运行时的CONFIG SET命令设置watchdog的开启参数。形如CONFIG SET watchdog-period 500表示运行时间超过500毫秒的操作将会被记录下来。由于serverCron默认每100ms执行一次,所以该看门狗的最小时间间隔为100ms。由于该功能会对性能和稳定性造成影响,所以当诊断完成后,可以再通过上面的命令,将延迟时间设置为0来关闭watchdog的功能。(2)设置完成后,Redis就开始对自身的命令处理进行性能监控。如果Redis发现自己在处理一些操作时不够快,那么就会在日志里记录一条信息,信息包含了在哪一部分占用了时间,并打出具体的调用栈。这样,当出现问题的用户到社区反馈的时候,如果带上日志里的诊断信息,开发者能够更快速准确的定位问题。Redis作者指出,watchdog还是一个实验性功能,开启它可能会对数据产生影响,所以建议在使用时做好数据备份。updateCachedTime();/设置server.unix变量保存服务器的当前unix时间。/这样做的好处是:访问一个全局变量要快于调用time(NULL)过程。接下来会使用到宏定义函数run_with_period(_ms_),具体地宏定义如下:#define run_with_period(_ms_) if (_ms_ = 1000/server.hz) | !(server.cronloops%(_ms_)/(1000/server.hz)其中1000/server.hz表示serverCron执行的周期时间,server.cronloops记录serverCron函数执行的次数。该语句的作用是设置某些语句的执行时间。具体来说,虽然serverCron每隔(1000/server.hz)秒都会执行,但是有些语句可能不必需要执行的如此频繁,于是使用到run_with_period(_ms_)来设置这些语句的间隔时间。需要说明的是,可能设定的时间间隔并不能完全与实际匹配。这主要受serverCron执行的时间间隔-1000/server.hz的影响。举例来说,当1000/server.hz=100时,_ms_在0199时,选取时间间隔为100在200299时,选取时间间隔为200在300399时,选取时间间隔为300run_with_period(100) trackOperationsPerSecond();/将服务器的命令执行次数记录到抽样数组中,用于追踪服务器每秒执行命令的次数(未完待续!)执行完serverCron后,继续回到initServer():/网络通信-端口关联应答(accept)处理器/ 为 TCP 连接关联连接应答(accept)处理器 / 用于接受并应答客户端的 connect() 调用 for (j = 0; j 0 & aeCreateFileEvent(server.el,server.sofd,AE_READABLE, acceptUnixHandler,NULL) = AE_ERR) redisPanic(Unrecoverable error creating server.sofd file event.); /* Open the AOF file if needed. */ / 如果 AOF 持久化功能已经打开

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论