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

文章目录

  • 前言
  • 看代码的方式
  • Redis 服务器的 main 函数
  • main 函数分段解释
    • 函数名及参数
    • 启动测试程序
    • 程序环境初始化
    • 初始化配置信息
    • 存储参数信息
    • 根据参数确定启动方式
    • 处理并加载命令行参数
    • 打印启动和警告信息
    • 守护模式和初始化
    • 哨兵模式判断启动并加载持久化数据
    • 打印内存警告并启动事件监听
  • 彩蛋
  • 总结

Redis源码-BFS方式浏览main函数_第1张图片

前言

欠下的技术债慢慢还,继续为去年吹过的牛而努力。去年年末的时候意识到自己掌握的知识还不够深入,决定开始看一些开源项目的源码,因为当时 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,因为只是学习源码,没有特殊情况就不更新版本了,保证环境的统一,我先把代码贴一份在这,后面再来慢慢看。

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 函数分段解释

函数名及参数

int main(int argc, char **argv) {
     
    struct timeval tv;
    int j;

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

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

启动测试程序

#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,为啥这里函数名命名规范不统一呢?挠头。

程序环境初始化

    /* 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 神奇的存在

初始化配置信息

    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 那一套,够喝一壶的

存储参数信息

    /* 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 服务

根据参数确定启动方式

    /* 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 这里将程序名字作为参数来判断,也就是说你把可执行程序换个名字运行,它的行为就会发生变化。

处理并加载命令行参数

    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");
    }

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

守护模式和初始化

    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 参数之前面试的时候还被问到过,好奇的话可以提前了解一下。

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

     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 支持持久化的必要保证。

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

    /* 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 函数的代码中有一个神奇的用法不知道大家有没有发现,就是下面这句话:

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

是不是看起来有些奇怪,不用管这个函数的定义是怎样的,可以告诉大家这个函数的定义类似于 printf 函数,只不过在最前面加了一个整型参数,那么调用这个函数时传了几个参数呢?3个?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 函数中也可以看到,大部分函数是驼峰命名法,还要少量的下划线命名和帕斯卡命名。

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

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

你可能感兴趣的:(Redis,Redis源码,BFS,redis,数据库,源码分析)