redis学习笔记——初始化
初始化服务器状态结构
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学习笔记——初始化的更多相关文章
- redis 学习笔记(6)-cluster集群搭建
上次写redis的学习笔记还是2014年,一转眼已经快2年过去了,在段时间里,redis最大的变化之一就是cluster功能的正式发布,以前要搞redis集群,得借助一致性hash来自己搞shardi ...
- Redis学习笔记~目录
回到占占推荐博客索引 百度百科 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合). ...
- Redis学习笔记4-Redis配置详解
在Redis中直接启动redis-server服务时, 采用的是默认的配置文件.采用redis-server xxx.conf 这样的方式可以按照指定的配置文件来运行Redis服务.按照本Redi ...
- Redis学习笔记7--Redis管道(pipeline)
redis是一个cs模式的tcp server,使用和http类似的请求响应协议.一个client可以通过一个socket连接发起多个请求命令.每个请求命令发出后client通常会阻塞并等待redis ...
- Redis学习笔记一:数据结构与对象
1. String(SDS) Redis使用自定义的一种字符串结构SDS来作为字符串的表示. 127.0.0.1:6379> set name liushijie OK 在如上操作中,name( ...
- Redis学习笔记之ABC
Redis学习笔记之ABC Redis命令速查 官方帮助文档 中文版本1 中文版本2(反应速度比较慢) 基本操作 字符串操作 set key value get key 哈希 HMSET user:1 ...
- (转)redis 学习笔记(1)-编译、启动、停止
redis 学习笔记(1)-编译.启动.停止 一.下载.编译 redis是以源码方式发行的,先下载源码,然后在linux下编译 1.1 http://www.redis.io/download 先 ...
- Redis学习笔记(二)-key相关命令【转载】
转自 Redis学习笔记(二)-key相关命令 - 点解 - 博客园http://www.cnblogs.com/leny/p/5638764.html Redis支持的各种数据类型包括string, ...
- Redis学习笔记(三)Redis支持的5种数据类型的总结
继续Redis学习笔记(二)来说说剩余的三种数据类型. 三.列表类型(List) 1.介绍 列表类型可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的一段片段.列表类型内部是 ...
随机推荐
- CSS优先级问题以及jQuery中的.eq()遍历方法和:eq()选择器的差别
在写一个TAB选项卡的时候遇到几个有意思的问题,记录下来 先把代码贴出来 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transition ...
- Template Mode 'HTML5' is deprecated与UnsatisfiedDependencyException
org.thymeleaf.templatemode.TemplateMode : [THYMELEAF][main] Template Mode 'HTML5' is deprecated. Usi ...
- 【bzoj1132】[POI2008]Tro 计算几何
题目描述 平面上有N个点. 求出所有以这N个点为顶点的三角形的面积和 N<=3000 输入 第一行给出数字N,N在[3,3000] 下面N行给出N个点的坐标,其值在[0,10000] 输出 保留 ...
- saltstack认证报错问题
认证报错 [root@saltstack01 ~]# salt '*' test.ping saltstack01: True saltstack03: Minion did not return. ...
- springBoot service层 事务控制
springBoot使用事物比较简单,在Application启动类s上添加@EnableTransactionManagement注解,然后在service层的方法上添加@Transactional ...
- vmware漏洞之二——简评:实战VMware虚拟机逃逸漏洞
下文取自360,是vmware exploit作者自己撰写的.本文从实验角度对作者的文章进行解释,有助于学习和理解.文章虚线内或红色括号内为本人撰写. ------------------------ ...
- 洛谷P1886滑动窗口
题目传送门 理解题意:给定一个数列和窗口范围k,求依次向右移动窗口时每次窗口内的最大和最小值. 没什么思维难度,一边扫过去,用两个数组maxx和minn记录每个窗口内的最大最小值,移动过程中用两个变量 ...
- CodeForces 809D Hitchhiking in the Baltic States(FHQ-Treap)
题意 给你长度为$n$的序列,序列中的每个元素$i$有一个区间限制$[l_i,r_i]$,你从中选出一个子序列,并给它们标号$x_i$,要求满足 $,∀i<j,x_i<x_j$,且$, ∀ ...
- 洛谷——P2121 拆地毯
P2121 拆地毯 题目背景 还记得 NOIP 2011 提高组 Day1 中的铺地毯吗?时光飞逝,光阴荏苒,三年过去了.组织者精心准备的颁奖典礼早已结束,留下的则是被人们踩过的地毯.请你来解决类似于 ...
- C++中cin、cin.get()、cin.getline()、getline()、gets()等函数的用法(转)
学C++的时候,这几个输入函数弄的有点迷糊:这里做个小结,为了自己复习,也希望对后来者能有所帮助,如果有差错的地方还请各位多多指教(本文所有程序均通过VC 6.0运行) 1.cin 2.cin.get ...