Redis源码-BFS方式浏览main函数

前言

欠下的技术债慢慢还,继续为去年吹过的牛而努力。去年年末的时候意识到自己掌握的知识还不够深入,决定开始看一些开源项目的源码,因为当时 Redis 的兴起,所以瞄准了准备从它下手,之后确实看了一部分内容,比如跳表、网络事件库等等,后来过年就鸽了。今年开始一直熟悉新的业务,比较懒没跟进,最近间歇性踌躇满志又发作了,准备抽时间再捋顺一遍,老规矩,还是从 main() 函数下手。

对于 C/C++ 程序一定是从 main() 函数开头的,这是我们切入的一个点,至于怎么找到 main 函数,每个人有不同的方法,最暴力的方法当然就是全文搜索了,不过较为成熟的项目一般搜索出来都不止一个 main 函数,因为整个项目完整构建下来不止一个程序。

redis 这个项目最起码有服务器和客户端两个程序,源码中至少包含了两个 main 函数,再加上一些测试程序,main 函数在源码中会有很多。再比如 Lua 的源代码中包含和解释器和编译器,如果直接搜索至少会找到两个 main 函数。

redis 服务器程序的 main 函数在文件 src/server.c 中,之前好像是在 redis.c 文件中后来改名了,这都不重要,反正你需要从搜索出来的 main 函数中找到一个开始的地方,这个花不了多少时间。

看代码的方式

标题中提到了 BFS 方式看代码,而 BFS 指的是广度优先搜索,与之相对应的是 DFS 深度优先搜索,对于不含异步调用的单线程程序来说,执行代码是以深度优先搜索的方式,遇到一个函数就调用进去,在函数中又遇到另一个函数再调用进去,当函数执行完成返回到上一层。

为什么选择 BFS 方式看代码呢?因为这样可以在短时间内更全面的了解代码结构,我们先看第一层,当第一层浏览完成之后再进入到第二层,比如我们先看 main 函数,即使 main 函数调用了很多不认识的函数也不要去管,从名字大概判断一些作用就可以了,不用纠结具体的实现内容,当 main 函数全部看完了再进入到第二层去了解它调用的那些函数。

总之使用 BFS 方式看代码就要有一种“不懂装懂”的态度,不然容易陷入细节,无法整体把握。

Redis 服务器的 main 函数

redis 服务器的 main 函数代码量不是很大,总共 200 行左右,我选择了 6.0.6 这个版本 7bf665f125a4771db095c83a7ad6ed46692cd314,因为只是学习源码,没有特殊情况就不更新版本了,保证环境的统一,我先把代码贴一份在这,后面再来慢慢看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
int main(int argc, char **argv) {
struct timeval tv;
int j;

#ifdef REDIS_TEST
if (argc == 3 && !strcasecmp(argv[1], "test")) {
if (!strcasecmp(argv[2], "ziplist")) {
return ziplistTest(argc, argv);
} else if (!strcasecmp(argv[2], "quicklist")) {
quicklistTest(argc, argv);
} else if (!strcasecmp(argv[2], "intset")) {
return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], "zipmap")) {
return zipmapTest(argc, argv);
} else if (!strcasecmp(argv[2], "sha1test")) {
return sha1Test(argc, argv);
} else if (!strcasecmp(argv[2], "util")) {
return utilTest(argc, argv);
} else if (!strcasecmp(argv[2], "endianconv")) {
return endianconvTest(argc, argv);
} else if (!strcasecmp(argv[2], "crc64")) {
return crc64Test(argc, argv);
} else if (!strcasecmp(argv[2], "zmalloc")) {
return zmalloc_test(argc, argv);
}

return -1; /* test not found */
}
#endif

/* We need to initialize our libraries, and the server configuration. */
#ifdef INIT_SETPROCTITLE_REPLACEMENT
spt_init(argc, argv);
#endif
setlocale(LC_COLLATE,"");
tzset(); /* Populates 'timezone' global. */
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
crc64_init();

uint8_t hashseed[16];
getRandomBytes(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed(hashseed);
server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig();
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */
moduleInitModulesSystem();
tlsInit();

/* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */
server.executable = getAbsolutePath(argv[0]);
server.exec_argv = zmalloc(sizeof(char*)*(argc+1));
server.exec_argv[argc] = NULL;
for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);

/* We need to init sentinel right now as parsing the configuration file
* in sentinel mode will have the effect of populating the sentinel
* data structures with master nodes to monitor. */
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}

/* Check if we need to start in redis-check-rdb/aof mode. We just execute
* the program main. However the program is part of the Redis executable
* so that we can easily execute an RDB check on loading errors. */
if (strstr(argv[0],"redis-check-rdb") != NULL)
redis_check_rdb_main(argc,argv,NULL);
else if (strstr(argv[0],"redis-check-aof") != NULL)
redis_check_aof_main(argc,argv);

if (argc >= 2) {
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);
}
}

/* First argument is the config file name? */
if (argv[j][0] != '-' || argv[j][1] != '-') {
configfile = argv[j];
server.configfile = getAbsolutePath(configfile);
/* Replace the config file in server.exec_argv with
* its absolute path. */
zfree(server.exec_argv[j]);
server.exec_argv[j] = zstrdup(server.configfile);
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 (!strcmp(argv[j], "--check-rdb")) {
/* Argument has no options, need to skip for parsing. */
j++;
continue;
}
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 == '-') {
serverLog(LL_WARNING,
"Sentinel config from STDIN not allowed.");
serverLog(LL_WARNING,
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
}
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
}

serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");
serverLog(LL_WARNING,
"Redis version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started",
REDIS_VERSION,
(sizeof(long) == 8) ? 64 : 32,
redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0,
(int)getpid());

if (argc == 1) {
serverLog(LL_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 ? "sentinel" : "redis");
} else {
serverLog(LL_WARNING, "Configuration loaded");
}

server.supervised = redisIsSupervised(server.supervised_mode);
int background = server.daemonize && !server.supervised;
if (background) daemonize();

initServer();
if (background || server.pidfile) createPidFile();
redisSetProcTitle(argv[0]);
redisAsciiArt();
checkTcpBacklogSettings();

if (!server.sentinel_mode) {
/* Things not needed when running in Sentinel mode. */
serverLog(LL_WARNING,"Server initialized");
#ifdef __linux__
linuxMemoryWarnings();
#endif
moduleLoadFromQueue();
ACLLoadUsersAtStartup();
InitServerLast();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
serverLog(LL_WARNING,
"You can't have keys in a DB different than DB 0 when in "
"Cluster mode. Exiting.");
exit(1);
}
}
if (server.ipfd_count > 0 || server.tlsfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
if (!server.masterhost) {
redisCommunicateSystemd("STATUS=Ready to accept connections\n");
redisCommunicateSystemd("READY=1\n");
} else {
redisCommunicateSystemd("STATUS=Waiting for MASTER <-> REPLICA sync\n");
}
}
} else {
InitServerLast();
sentinelIsRunning();
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
redisCommunicateSystemd("STATUS=Ready to accept connections\n");
redisCommunicateSystemd("READY=1\n");
}
}

/* Warning the user about suspicious maxmemory setting. */
if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
}

redisSetCpuAffinity(server.server_cpulist);
aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;
}

main 函数分段解释

函数名及参数

1
2
3
4
5
6
7
8
int main(int argc, char **argv) {
struct timeval tv;
int j;

//...
//...
return 0
}

这就是一个标准的 main 函数,参数 argcargv 对于一个命令行程序来说可以是重头戏,肯定会拿来做重度解析的,函数开头还定义了 tvj 两个变量,不知道干嘛的,接着往下看吧。

启动测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifdef REDIS_TEST
if (argc == 3 && !strcasecmp(argv[1], "test")) {
if (!strcasecmp(argv[2], "ziplist")) {
return ziplistTest(argc, argv);
} else if (!strcasecmp(argv[2], "quicklist")) {
quicklistTest(argc, argv);
} else if (!strcasecmp(argv[2], "intset")) {
return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], "zipmap")) {
return zipmapTest(argc, argv);
} else if (!strcasecmp(argv[2], "sha1test")) {
return sha1Test(argc, argv);
} else if (!strcasecmp(argv[2], "util")) {
return utilTest(argc, argv);
} else if (!strcasecmp(argv[2], "endianconv")) {
return endianconvTest(argc, argv);
} else if (!strcasecmp(argv[2], "crc64")) {
return crc64Test(argc, argv);
} else if (!strcasecmp(argv[2], "zmalloc")) {
return zmalloc_test(argc, argv);
}

return -1; /* test not found */
}
#endif

当宏定义 REDIS_TEST 存在,并且参数合适的情况下启动测试程序,argv[0] 肯定是指 redis 服务器喽,那 argv[1] 的值如果是 test,而 argv[2] 的值是 ziplist,那么会调用 ziplist 的测试函数 ziplistTest,如果 argv[2] 的值是 zmalloc,那么会调用测试函数 zmalloc_test,为啥这里函数名命名规范不统一呢?挠头。

程序环境初始化

1
2
3
4
5
6
7
8
9
10
    /* We need to initialize our libraries, and the server configuration. */
#ifdef INIT_SETPROCTITLE_REPLACEMENT
spt_init(argc, argv);
#endif
setlocale(LC_COLLATE,"");
tzset(); /* Populates 'timezone' global. */
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
crc64_init();
  1. INIT_SETPROCTITLE_REPLACEMENT 这个宏存在的时候,调用 spt_init 函数来为设置程序标题做准备
  2. setlocale() 用来设置地点信息,这一句应该是设置成依赖操作系统的地点信息,比如中国,韩国等等
  3. tzset() 设置时区,这里可能影响到程序运行后,调整时区是否对程序产生影响
  4. srand(time(NULL)^getpid()); 初始化随机种子
  5. gettimeofday(&tv,NULL); 这里用到了函数开头定义的一个变量 tv,用来获取当前时间
  6. crc64_init(); 循环冗余校验初始化,crc 神奇的存在

初始化配置信息

1
2
3
4
5
6
7
8
9
uint8_t hashseed[16];
getRandomBytes(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed(hashseed);
server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig();
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */
moduleInitModulesSystem();
tlsInit();
  1. 定一个16字节的空间用来存放哈希种子
  2. 随机获取一段16字节数据作为种子
  3. 将刚刚获取的种子数据设置到hash函数中
  4. 分析命令行参数,判断是否是哨兵模式
  5. 初始化服务器配置
  6. ACL 初始化,不用管它具体是什么,进入下一层时自然会看到
  7. 初始化模块系统
  8. tls 初始化,存疑,好奇的话进去看看也可以,好吧,原来是 ssl 那一套,够喝一壶的

存储参数信息

1
2
3
4
5
6
/* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */
server.executable = getAbsolutePath(argv[0]);
server.exec_argv = zmalloc(sizeof(char*)*(argc+1));
server.exec_argv[argc] = NULL;
for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);

这一小节比较简单,注释写的也很清楚,就是将命令行参数存储起来,方便重启 redis 服务

根据参数确定启动方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* We need to init sentinel right now as parsing the configuration file
* in sentinel mode will have the effect of populating the sentinel
* data structures with master nodes to monitor. */
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}

/* Check if we need to start in redis-check-rdb/aof mode. We just execute
* the program main. However the program is part of the Redis executable
* so that we can easily execute an RDB check on loading errors. */
if (strstr(argv[0],"redis-check-rdb") != NULL)
redis_check_rdb_main(argc,argv,NULL);
else if (strstr(argv[0],"redis-check-aof") != NULL)
redis_check_aof_main(argc,argv);

当启用哨兵模式的时候初始化额外的配置,啥是哨兵,现在还不用知道啊,从字面上来看就好了,反正知道命令行里如果指定了哨兵模式就要额外初始化一点东西。

下面这两个参数有点意思,简单扩展下,rdbaofredis 的两种数据落地的持久化方式,这里有意思的地方是判断了 argv[0] 这个参数,一般 argv[0] 是程序的名字,这个是固定不变的,而 redis 这里将程序名字作为参数来判断,也就是说你把可执行程序换个名字运行,它的行为就会发生变化。

处理并加载命令行参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
if (argc >= 2) {
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);
}
}

/* First argument is the config file name? */
if (argv[j][0] != '-' || argv[j][1] != '-') {
configfile = argv[j];
server.configfile = getAbsolutePath(configfile);
/* Replace the config file in server.exec_argv with
* its absolute path. */
zfree(server.exec_argv[j]);
server.exec_argv[j] = zstrdup(server.configfile);
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 (!strcmp(argv[j], "--check-rdb")) {
/* Argument has no options, need to skip for parsing. */
j++;
continue;
}
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 == '-') {
serverLog(LL_WARNING,
"Sentinel config from STDIN not allowed.");
serverLog(LL_WARNING,
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
}
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
}

这段内容很长,但是核心的内容不多,前一部分是判断特殊参数,用来显示程序使用方法,启动内存测试等等,中间部分是分析命令行参数保存到字符串中,最后几行是读取服务器配置文件,并使用字符串中的参数选项覆盖文件中的部分配置。

打印启动和警告信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");
serverLog(LL_WARNING,
"Redis version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started",
REDIS_VERSION,
(sizeof(long) == 8) ? 64 : 32,
redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0,
(int)getpid());

if (argc == 1) {
serverLog(LL_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 ? "sentinel" : "redis");
} else {
serverLog(LL_WARNING, "Configuration loaded");
}

打印 redis 服务器启动信息,比如版本号,pid,警告信息等等,没有实际修改数据。

守护模式和初始化

1
2
3
4
5
6
7
8
9
server.supervised = redisIsSupervised(server.supervised_mode);
int background = server.daemonize && !server.supervised;
if (background) daemonize();

initServer();
if (background || server.pidfile) createPidFile();
redisSetProcTitle(argv[0]);
redisAsciiArt();
checkTcpBacklogSettings();

根据守护进程配置和是否受监督来决定是否作为守护进程,什么是受监督,到现在还不知道,但是本着不懂装懂的方式看代码,可以认为我们懂了,后面自然还会有解释的地方。

接着就调用了 initServer(); 函数,这个初始化函数内容是比较长的,之前版本中很多 mian 函数中的内容都移到了这里面,初始化完成后创建 Pid 文件,设置进程名字,显示 redis 的Logo,检查一些配置,这个 backlog 参数之前面试的时候还被问到过,好奇的话可以提前了解一下。

哨兵模式判断启动并加载持久化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 if (!server.sentinel_mode) {
/* Things not needed when running in Sentinel mode. */
serverLog(LL_WARNING,"Server initialized");
#ifdef __linux__
linuxMemoryWarnings();
#endif
moduleLoadFromQueue();
ACLLoadUsersAtStartup();
InitServerLast();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
serverLog(LL_WARNING,
"You can't have keys in a DB different than DB 0 when in "
"Cluster mode. Exiting.");
exit(1);
}
}
if (server.ipfd_count > 0 || server.tlsfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
if (!server.masterhost) {
redisCommunicateSystemd("STATUS=Ready to accept connections\n");
redisCommunicateSystemd("READY=1\n");
} else {
redisCommunicateSystemd("STATUS=Waiting for MASTER <-> REPLICA sync\n");
}
}
} else {
InitServerLast();
sentinelIsRunning();
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
redisCommunicateSystemd("STATUS=Ready to accept connections\n");
redisCommunicateSystemd("READY=1\n");
}
}

这段代码看起来像是再做一些通知提醒,其中比较重要的几个函数是moduleLoadFromQueue()InitServerLast()loadDataFromDisk() ,第一个函数是加载模块的,第二个函数是在模块加载完成之后才能初始化的部分内容,最后一个是从磁盘加载数据到内存,这也是 redis 支持持久化的必要保证。

打印内存警告并启动事件监听

1
2
3
4
5
6
7
8
9
/* Warning the user about suspicious maxmemory setting. */
if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
}

redisSetCpuAffinity(server.server_cpulist);
aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;

看到这段代码我们就来到了 main 函数结尾的部分,redisSetCpuAffinity() 是要做些和 CPU 相关的设置或配置,aeMain() 是主逻辑,对于提供服务的程序来说里面大概率是一个死循环,再满足指定的条件下才会打断退出,而 aeDeleteEventLoop() 就是循环结束时清理事件的操作,到此为止 main 函数就执行完啦。

彩蛋

这个 main 函数的代码中有一个神奇的用法不知道大家有没有发现,就是下面这句话:

1
2
3
serverLog(LL_WARNING,
"You can't have keys in a DB different than DB 0 when in "
"Cluster mode. Exiting.");

是不是看起来有些奇怪,不用管这个函数的定义是怎样的,可以告诉大家这个函数的定义类似于 printf 函数,只不过在最前面加了一个整型参数,那么调用这个函数时传了几个参数呢?3个?2个?,这个地方很神奇的会把两个字符串拼接到一起,类似于下面的写法:

1
2
serverLog(LL_WARNING,
"You can't have keys in a DB different than DB 0 when in Cluster mode. Exiting.");

这样的字符串不仅可以分成两行,实际上可以分成任意行,最后都会拼接在一起,是不是很神奇。

总结

  • j 这个变量在 redis 的源码中经常出现,应该是作者的行为习惯吧,有些人爱用 i,而这个作者 antirez 爱用 j
  • 不能一口吃个胖子,看代码也是一样,不能期望一次性把所有的内容都看懂,一段时间后自己的代码都看不懂了,跟别说别人写的了。
  • redis 代码中频繁使用 server 这个变量,从 main 函数分析中也能看到,这个是个全局变量,代表了整个 redis 服务器程序数据。
  • 不懂装懂或者说不求甚解是熟悉代码整体结构的一项优秀品质,这时候只要看个大概就可以了,真到熟悉细节的时候才是需要钻研的时候。
  • 代码风格完全统一还是比较难实现的,从一个 main 函数中也可以看到,大部分函数是驼峰命名法,还要少量的下划线命名和帕斯卡命名。

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

你微笑的模样,提醒着我不要躲藏,坚持原来的方向,哪怕最后遍体鳞伤,困难只会让坚持的人越来越强,共勉~

2020-8-15 23:48:53

Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客