初始化服务器状态结构

redis中一个最重要的数据结构是redis_server,会创建一个这个结构的全局变量server,初始化服务器的第一步就是创建一个struct redisServer类型的实例变量server作为服务器的状态,并为结构中的各个属性设置默认值。初始化server变量的工作由redis.c/initServerConfig函数完成,initServerConfig函数中,大部分是对server的属性设置默认值,还有一部分是调用populateCommandTable函数对redis的命令表初始化。全局变量redisCommandTable是redisCommand类型的数组,保存redis支持的所有命令。server.commands是一个dict,保存命令名到redisCommand的映射。populateCommandTable函数会遍历全局redisCommandTable表,把每条命令插入到server.commands中,根据每个命令的属性设置其flags。以下是这个函数的部分代码:


void initServerConfig(void){
    // 
设置服务器的运行id 
    getRandomHexChars(server.runid,REDIS_RUN_ID_SIZE);
    // 
为运行id
加上结尾字符
    server.runid[REDIS_RUN_ID_SIZE] = '\0';
    // 
设置默认配置文件路径
    server.configfile = NULL;
    // 
设置默认服务器频率
    server.hz = REDIS_DEFAULT_HZ;
    // 
设置服务器的运行架构
    server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
    // 
设置默认服务器端口号
    server.port = REDIS_SERVERPORT;
    // ...

  

  /* Command table -- we initiialize it here as it is part of the
  * initial configuration, since command names may be changed via
  * redis.conf using the rename-command directive. */
  // 初始化命令表
  // 在这里初始化是因为接下来读取 .conf 文件时可能会用到这些命令
  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");

  ...
}

 

以下是initServerConfig函数完成的主要工作:
·设置服务器的运行ID。
·设置服务器的默认运行频率。
·设置服务器的默认配置文件路径。
·设置服务器的运行架构。
·设置服务器的默认端口号。
·设置服务器的默认RDB持久化条件和AOF持久化条件。
·初始化服务器的LRU时钟。
·创建命令表。

载入配置选项

在启动服务器时,用户可以通过给定配置参数或者指定配置文件来修改服务器的默认配置。举个例子,如果我们在终端中输入:

$ redis-server --port 10086

那么我们就通过给定配置参数的方式,修改了服务器的运行端口号。另外,如果我们在终端中输入:

$ redis-server redis.conf

那么我们就通过指定配置文件的方式修改了服务器的数据库数量,以及RDB持久化模块的压缩功能。

服务器在用initServerConfig函数初始化完server变量之后,就会开始载入用户给定的配置参数和配置文件,并根据用户设定的配置,对server变量相关属性的值进行修改。

这一部分是在main()函数中实现的,下面是源代码:

// 检查用户是否指定了配置文件,或者配置选项
if (argc >= ) {
int j = ; /* First option to parse in argv[] */
sds options = sdsempty();
char *configfile = NULL; /* Handle special options --help and --version */
// 处理特殊选项 -h 、-v 和 --test-memory
if (strcmp(argv[], "-v") == ||
strcmp(argv[], "--version") == ) version();
if (strcmp(argv[], "--help") == ||
strcmp(argv[], "-h") == ) usage();
if (strcmp(argv[], "--test-memory") == ) {
if (argc == ) {
memtest(atoi(argv[]),);
exit();
} 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();
}
} /* First argument is the config file name? */
// 如果第一个参数(argv[1])不是以 "--" 开头
// 那么它应该是一个配置文件
if (argv[j][] != '-' || argv[j][] != '-')
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. */
// 对用户给定的其余选项进行分析,并将分析所得的字符串追加稍后载入的配置文件的内容之后
// 比如 --port 6380 会被分析为 "port 6380\n"
while(j != argc) {
if (argv[j][] == '-' && argv[j][] == '-') {
/* Option name */
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+);
options = sdscat(options," ");
} else {
/* Option argument */
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
}
j++;
}
//getAbsolutePath()函数用于得到一直文件名的绝对路径
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", argv[], server.sentinel_mode ? "sentinel" : "redis");
}

其中 loadServerConfig(char *filename,char *option)函数主要用于从给定文件中载入服务器配置。

loadServerConfig:完成的功能很简单,就是将文件内容读到字符串中。并将通过命令行传入的配置项追加到该字符串后。后面loadServerConfig会调用loadServerConfigFromString函数:从字符串中解析出配置项,并设置server的相关属性(可参照源代码)。

此步完成后,server中的简单属性(整数、字符串)基本都设置完成。

初始化服务器数据结构

在之前执行initServerConfig函数初始化server状态时,程序只创建了命令表一个数据结构,不过除了命令表之外,服务器状态还包含其他数据结构,比如:

  • server.clients链表,这个链表记录了所有与服务器相连的客户端的状态结构,链表的每个节点都包含了一个redisClient结构实例;
  • server.db数组,数组中包含了服务器的所有数据库;
  • 用于保存频道订阅信息的server.pubsub_channels字典,以及用于保存模式订阅信息的server.pubsub_patterns链表;
  • 用于执行Lua脚本的Lua环境server.lua;
  • 用于保存慢查询日志的server.slowlog属性。

此时服务器将调用initServer函数为上面提到的这些数据结构进行分配内存,并在需要的时候为其关联初始化值。

除了上面这些外,initServer还进行了一些非常重要的设置操作,其中包括:

  • 为服务器设置进程信号处理器;
  • 创建共享对象:这些对象包含Redis服务器经常用到的一些值,比如包含"OK"回复的字符串对象,包含"ERR"回复的字符串对象,包含整数1到10000的字符串对象等等,服务器通过重用这些共享对象来避免反复创建相同的对象;
  • 打开服务器的监听端口,并为监听套接字关联连接应答事件处理器,等待服务器正式运行时接受客户端的连接;
  • 为serverCron函数创建时间事件,等待服务器正式运行时执行serverCron函数;
  • 如果AOF持久化功能已经打开,那么打开现有的AOF文件,如果AOF文件不存在,那么创建并打开一个新的AOF文件,为AOF写入做好准备;
  • 初始化服务器的后台I/O模块(bio),为将来的I/O操作做好准备。
void initServer() {
int j; // 设置信号处理函数
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
setupSignalHandlers(); // 如果设置开启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 = -; /* 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 = ;
server.clients_paused = ; // 创建共享对象
createSharedObjects();
adjustOpenFilesLimit();
server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
server.db = zmalloc(sizeof(redisDb)*server.dbnum); /* Open the TCP listening socket for the user commands. */
// 打开 TCP 监听端口,用于等待客户端的命令请求
if (server.port != &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
exit(); /* Open the listening Unix domain socket. */
// 打开 UNIX 本地端口
  //初始化监听socket,就是调用socket、bind、listen等,并将socket设置为非阻塞。如果配置了unix domain socket,也会进行相应的初始化
if (server.unixsocket != NULL) {
unlink(server.unixsocket); /* don't care if this fails */
server.sofd = anetUnixServer(server.neterr,server.unixsocket,
server.unixsocketperm, server.tcp_backlog);
if (server.sofd == ANET_ERR) {
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
exit();
}
anetNonBlock(NULL,server.sofd);
} /* Abort if there are no listening sockets at all. */
if (server.ipfd_count == && server.sofd < ) {
redisLog(REDIS_WARNING, "Configured to not listen anywhere, exiting.");
exit();
} /* Create the Redis databases, and initialize other internal state. */
// 创建并初始化数据库结构
for (j = ; 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(&setDictType,NULL);
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
server.db[j].eviction_pool = evictionPoolAlloc();
server.db[j].id = j;
server.db[j].avg_ttl = ;
} // 创建 PUBSUB 相关结构
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
server.pubsub_patterns = listCreate();
listSetFreeMethod(server.pubsub_patterns,freePubsubPattern);
listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern); server.cronloops = ;
server.rdb_child_pid = -;
server.aof_child_pid = -;
aofRewriteBufferReset();
server.aof_buf = sdsempty();
server.lastsave = time(NULL); /* At startup we consider the DB saved. */
server.lastbgsave_try = ; /* At startup we never tried to BGSAVE. */
server.rdb_save_time_last = -;
server.rdb_save_time_start = -;
server.dirty = ;
resetServerStats();
/* A few stats we don't want to reset: server startup time, and peak mem. */
server.stat_starttime = time(NULL);
server.stat_peak_memory = ;
server.resident_set_size = ;
server.lastbgsave_status = REDIS_OK;
server.aof_last_write_status = REDIS_OK;
server.aof_last_write_errno = ;
server.repl_good_slaves_count = ;
updateCachedTime(); /* Create the serverCron() time event, that's our main way to process
* background operations. */
// 为 serverCron() 创建时间事件
if(aeCreateTimeEvent(server.el, , serverCron, NULL, NULL) == AE_ERR) {
redisPanic("Can't create the serverCron time event.");
exit();
} /* Create an event handler for accepting new connections in TCP and Unix
* domain sockets. */
// 为 TCP 连接关联连接应答(accept)处理器
// 用于接受并应答客户端的 connect() 调用
for (j = ; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR)
{
redisPanic(
"Unrecoverable error creating server.ipfd file event.");
}
} // 为本地套接字关联应答处理器
if (server.sofd > && 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 持久化功能已经打开,那么打开或创建一个 AOF 文件
if (server.aof_state == REDIS_AOF_ON) {
server.aof_fd = open(server.aof_filename,
O_WRONLY|O_APPEND|O_CREAT,);
if (server.aof_fd == -) {
redisLog(REDIS_WARNING, "Can't open the append-only file: %s",
strerror(errno));
exit();
}
} /* 32 bit instances are limited to 4GB of address space, so if there is
* no explicit limit in the user provided configuration we set a limit
* at 3 GB using maxmemory with 'noeviction' policy'. This avoids
* useless crashes of the Redis instance for out of memory. */
// 对于 32 位实例来说,默认将最大可用内存限制在 3 GB
if (server.arch_bits == && server.maxmemory == ) {
redisLog(REDIS_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.");
server.maxmemory = 3072LL*(*); /* 3 GB */
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
} // 如果服务器以 cluster 模式打开,那么初始化 cluster
if (server.cluster_enabled) clusterInit(); // 初始化复制功能有关的脚本缓存
replicationScriptCacheInit(); // 初始化脚本系统
scriptingInit(); // 初始化慢查询功能
slowlogInit(); // 初始化 BIO 系统
bioInit();
}

还原数据库状态

紧接着,如果在完成了对服务器状态server变量的初始化之后,服务器需要载入RDB文件或者AOF文件,并根据文件记录的内容来还原服务器的数据库状态。

根据服务器是否启用了AOF持久化功能,服务器载入数据时所使用的目标文件会有所不同:

  • 如果服务器启用了AOF持久化功能,那么服务器使用AOF文件来还原数据库状态;
  • 相反地,如果服务器没有启用AOF持久化功能,那么服务器使用RDB文件来还原数据库状态。

当服务器完成数据库状态还原工作之后,服务器将在日志中打印出载入文件并还原数据库状态所耗费的时长:

[5244] 21 Nov 22:43:49.084 * DB loaded from disk: 0.068 seconds

在main函数中导入RDB或者AOF的函数源码如下:

// 如果服务器不是运行在 SENTINEL 模式,那么执行以下代码
if (!server.sentinel_mode) {
/* Things not needed when running in Sentinel mode. */
// 打印问候语
redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION);
#ifdef __linux__
// 打印内存警告
linuxOvercommitMemoryWarning();
#endif
// 从 AOF 文件或者 RDB 文件中载入数据
loadDataFromDisk();
// 启动集群?
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == REDIS_ERR) {
redisLog(REDIS_WARNING,
"You can't have keys in a DB different than DB 0 when in "
"Cluster mode. Exiting.");
exit();
}
}
// 打印 TCP 端口
if (server.ipfd_count > )
redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
// 打印本地套接字端口
if (server.sofd > )
redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
} else {
sentinelIsRunning();
}

main函数的最后,是启动事件循环。在事件循环的每次迭代,sleep之前会调用beforeSleep函数,进行一些异步处理。此处首先设置beforeSleep函数,然后启动aeMain事件循环。当从事件循环退出后,清理事件循环,然后退出。

// 运行事件处理器,一直到服务器关闭为止
aeSetBeforeSleepProc(server.el,beforeSleep);
// 开启事件循环
aeMain(server.el); // 服务器关闭,停止事件循环
aeDeleteEventLoop(server.el);
return ;
}

redis学习笔记——初始化的更多相关文章

  1. redis 学习笔记(6)-cluster集群搭建

    上次写redis的学习笔记还是2014年,一转眼已经快2年过去了,在段时间里,redis最大的变化之一就是cluster功能的正式发布,以前要搞redis集群,得借助一致性hash来自己搞shardi ...

  2. Redis学习笔记~目录

    回到占占推荐博客索引 百度百科 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合). ...

  3. Redis学习笔记4-Redis配置详解

    在Redis中直接启动redis-server服务时, 采用的是默认的配置文件.采用redis-server   xxx.conf 这样的方式可以按照指定的配置文件来运行Redis服务.按照本Redi ...

  4. Redis学习笔记7--Redis管道(pipeline)

    redis是一个cs模式的tcp server,使用和http类似的请求响应协议.一个client可以通过一个socket连接发起多个请求命令.每个请求命令发出后client通常会阻塞并等待redis ...

  5. Redis学习笔记一:数据结构与对象

    1. String(SDS) Redis使用自定义的一种字符串结构SDS来作为字符串的表示. 127.0.0.1:6379> set name liushijie OK 在如上操作中,name( ...

  6. Redis学习笔记之ABC

    Redis学习笔记之ABC Redis命令速查 官方帮助文档 中文版本1 中文版本2(反应速度比较慢) 基本操作 字符串操作 set key value get key 哈希 HMSET user:1 ...

  7. (转)redis 学习笔记(1)-编译、启动、停止

    redis 学习笔记(1)-编译.启动.停止   一.下载.编译 redis是以源码方式发行的,先下载源码,然后在linux下编译 1.1 http://www.redis.io/download 先 ...

  8. Redis学习笔记(二)-key相关命令【转载】

    转自 Redis学习笔记(二)-key相关命令 - 点解 - 博客园http://www.cnblogs.com/leny/p/5638764.html Redis支持的各种数据类型包括string, ...

  9. Redis学习笔记(三)Redis支持的5种数据类型的总结

    继续Redis学习笔记(二)来说说剩余的三种数据类型. 三.列表类型(List) 1.介绍 列表类型可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的一段片段.列表类型内部是 ...

随机推荐

  1. 3.spark streaming Job 架构和容错解析

    一.Spark streaming Job 架构 SparkStreaming框架会自动启动Job并每隔BatchDuration时间会自动触发Job的调用. Spark Streaming的Job ...

  2. sql 时间日期格式化

    sql server2000中使用convert来取得datetime数据类型样式(全) 日期数据格式的处理,两个示例: CONVERT(varchar(16), 时间一, 20) 结果:2007-0 ...

  3. POJ 3657 Haybale Guessing(区间染色 并查集)

    Haybale Guessing Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 2384   Accepted: 645 D ...

  4. 查看Linux系统资源占用

    Linux查看进程占用磁盘IO yum install -y itop iotop -oP Linux查看进程网络使用 yum install -y nethogs nethogs nethogs e ...

  5. Jenkins实现CI(Continuous Integration)到CD(Continuous Delivery)

    Pipeline as Code是2.0的精髓所在,是帮助Jenkins实现CI(Continuous Integration)到CD(Continuous Delivery)华丽转身的关键推手.所谓 ...

  6. Xamarin XAML语言教程模板页面TemplatedPage

    Xamarin XAML语言教程模板页面TemplatedPage 模板页面TemplatedPage 在上文中我们提到了TemplatedPage,它被称为模板页面,用来显示控件模版.Templat ...

  7. hdu 4825(Trie)

    Xor Sum Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 132768/132768 K (Java/Others)Total S ...

  8. [COCI2011-2012#7] KAMPANJA

    这个题似曾相识啊,以前是用搜索剪枝+0/1边权bfs做的(题面可以参照上一篇这个题的博客). 有一类问题就是求 包含若干关键点的最小强联通子图大小是多少. 如果关键点数量是变量,那么就是NP问题了.. ...

  9. [Arc080F]Prime Flip

    [Arc080F]Prime Flip Description 你有无限多的"给给全",编号为1,2,3,....开始时,第x1,x2,...,xN个"给给全" ...

  10. 【DFS】奇怪的电梯

    奇怪的电梯 题目描述 有一天桐桐做了一个梦,梦见了一种很奇怪的电梯.大楼的每一层楼都可以停电梯,而且第i层楼(1≤i≤N)上有一个数字K:(0≤Ki≤N).电梯只有四 个按钮:开,关,上,下.上下的层 ...