管中窥豹之Redis源码分析<一>
Redis命令行内容解析
在工作中发现Linux下C程序对于进程间通信对于单个节点的共享内存使用越来越频繁,当每个节点都需要反复装载同样的共享内存,对物理资源需求越来越大。随着业务的增长,装载共享内存的数据库表由百万级增加之千万级,随着业务不断发展,需要装载的共享内存大小仍然会继续增加,集群中节点数目的几何增加,对物理资源是一个极大的挑战。
于是乎,独立共享内存,建立一个内存集群的想法油然而生,在目前开源的内存产品种redis集群似乎成了不二的选择。虽然一直关注Redis开源产品很久,系统详细的了解其构成和原理还是很少,萌生出深入了解、剖析Redis源码想法。说到不如做到,就着手阅读Redis源码。
脱离写C代码已经很久,虽然勉强能够看懂C代码,可是很多Redis源代码的细节还是让我觉得是一种艺术,不得不记录下来分享给大家。在此之前也拜读过很多开源爱好者从不同角度剖析Redis源码的文章,切入点不同,看到的Redis产品艺术呈现也不一样。既然阅读源码,本系列的文章主要从程序运行的角度,一步一步剖析源码直至命令的实现和处理方式,逐渐深入分析艺术的呈现。
废话不多说,下面开始管中窥豹,以计算机的方式来阅读Redis源码,如何启动和编译Redis源码不再此处赘述。
众所周知,C程序的入口都是从main函数开始,当然Redis也不例外,源码分析当然得从Redis的server端的源码开始。
Redis的server段程序从redis.c程序开始。
<span style="font-size:14px;">int main(int argc, char **argv)</span>
Redis的server端从上述main函数切入,函数中传入了两个参数argc和argv。其中argc表示执行redis.c程序时带入参数的个数,argv表示传入参数的具体内容。此处,argv采用了一个指向指针的变量argv。这么说可能有点抽象,不妨使用一个具体的例子来解读这么做的好处。
创建一个main函数,执行下述代码:
<span style="font-size:14px;"> char *a = "hello";
char *(*b) = &a;
printf("&b:%p,*b:%s,*b:%p ,b:%p, &a:%p, a:%p, *a:%c, a:%s\n", &b,*b,*b,b, &a, a, *a, a);</span>
这段代码的运行结果:
<span style="font-size:14px;">&b:0xbf867060,*b:hello,*b:0x804873e ,b:0xbf86705c, &a:0xbf86705c, a:0x804873e, *a:h, a:hello</span>
其中b可以类比main函数中的argv变量。
代码片段分析:
1、定义一个指针类型变量a,并将字符串“hello”的起始地址赋给a
2、定义一个指向指针类型的变量b,并将a的地址赋给×b,变量b的地址由编译器自动分配
从执行结果来看,b的地址是系统自动分配的,×b的地址为a的地址,×b的内容为a的内容
变量在内存中的表现形式分析如下:
内存分配 | ||||
堆区 | 常量区 | |||
*b--> | a--> | 0x804873e | h | |
e | ||||
b--> | 0xbf867060 | l | ||
l | ||||
o |
了解了main函数参数传递的过程,回过来分析redis.c中解析服务端解析命令行的参数。
redis.c中下述代码解析了,服务端程序是如何解析命令行参数的。
<span style="font-size:14px;">server.sentinel_mode = checkForSentinelMode(argc,argv);</span>
方法checkForSentinelMode是解析命令行参数的函数。
<span style="font-size:14px;">int checkForSentinelMode(int argc, char **argv) {
int j;
if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
for (j = 1; j < argc; j++)
if (!strcmp(argv[j],"--sentinel")) return 1;
return 0;
}</span>
当argv【0】为redis sentinel是表示Redis集群采用主从&&share高可用模式,后面分析Redis集群高可用方式时阐述此过程。
本节源码分析着重分析redis装载参数的配置文件的过程,redis.c程序中判断了集群高可用模式后,服务器端开始装载配置参数,对应源码中的initServerConfig方法。
无论采用哪种高可用模式,server启动的时候都会按照redis.h中宏定义的参数装载redis启动参数,例如:端口为6397,中断时间为10s等,详情见redis.h头文件。
根据执行redis服务端程序带入的参数argv,判断redis输出的结果。
argv【0】表示执行程序的全路径,源码分析采用的是redis-3.0.5,argv【0】则为:redis-server
argv【1】表示带入的第一个字符串参数,例如:redis-server -v;redis-server --version,表示输出当前redis的版本。
由于argv【0】对应为源程序的全路径,所以不带参数的情况下,argc为1;带参数的时候argc的值>=2,所以在redis.c中判断argc>=2的时候开始解析命令行参数。
<span style="font-size:14px;"> 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);
}
}</span>
上述代码片段表明,在2个参数的情况下,redis-server程序只有查看帮助和查看版本的两种情况;当3个参数的时候redis-server只有test-memory的情况。其他参数都是默认为config文件方式解析。下个章节进行详细源码分析。