Redis学习笔记(二)

关于Redis的源码分析,在Redis设计与实现中分析的相当清楚,本人在学习的过程中也是阅读此书。

Redis-server中的MAIN函数

我们从redis的main函数着手,来分析整个redis的启动过程,main函数在redis.c/redis.h中的第2977行


#ifdef INIT_SETPROCTITLE_REPLACEMENT
        spt_init(argc, argv);
#endif

此处用来修改进程名

    setlocale(LC_COLLATE,"");

设置Redis所使用的字符串编码。

    zmalloc_enable_thread_safeness();

设置线程安全zmalloc开关,函数存在于zmalloc.c中的第237行,作用是将zmalloc_thread_safe这个变量设置为1,该变量大多数情况下是用作是否采用加锁操作的布尔判断。

    zmalloc_set_oom_handler(redisOutOfMemoryHandler);

zmalloc_set_oom_handler的函数在zmalloc.c中的241行,用来设置内存溢出之后的处理函数,而redisOutOfMemoryHandler在redis.c的第2960行,在Redis内存溢出之后记录报警日志,然后退出整个程序。

    dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());

函数存在于dict.c中的第90行,使用gettimeofday(&tv,NULL);所取到的精确时间和当前进程的pid异或来生成DICT中的哈希函数的种子。

    server.sentinel_mode = checkForSentinelMode(argc,argv);
    ...
    if (server.sentinel_mode) {
        initSentinelConfig();
        initSentinel();
    }

根据输入的参数判断是否以sentinel模式运行,Redis-sentinel是Redis的集群管理工具,主要是中心节点用来监控其他节点的工作情况并进行故障恢复。

    initServerConfig();

该函数在redis.c中的第1275行,以默认值初始化server的各项属性,具体属性可参见redis.h中第569行所定义的结构体的各项属性及其注释。

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

        /* First argument is the config file name? */
        if (argv[j][0] != '-' || argv[j][1] != '-')
            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. */
        while(j != argc) {
            if (argv[j][0] == '-' && argv[j][1] == '-') {
                /* Option name */
                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 (configfile) server.configfile = getAbsolutePath(configfile);
        resetServerSaveParams();
        loadServerConfig(configfile,options);
        sdsfree(options);
    } 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[0], server.sentinel_mode ? "sentinel" : "redis");
    }

这是redis.c中的第3001-3048行,主要是用来处理Redis-server命令行的一些可选参数选项以及读取参数配置文件内容并初始化server的全局变量的相关代码。

    if (server.daemonize) daemonize();

根据server.daemonize的值决定是否以daemon的方式开启Redis,函数位于redis.c中的第2837行。

    initServer();

初始化整个服务器,函数位于redis.c中的第1519行。主要是初始化各项属性的队列、建立对端口和UNIX域套接字的监听、生成AOF文件句柄等等。

    if (server.daemonize) createPidFile();
    redisSetProcTitle(argv[0]);
    redisAsciiArt();

这三行分别是创建pid文件,设置程序名称以及打印启动时大家都会看到的LOGO。

    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
        loadDataFromDisk();
        if (server.ipfd_count > 0)
            redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
        if (server.sofd > 0)
            redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
    } else {
        sentinelIsRunning();
    }

这里主要是处理sentinel模式和普通模式下,redis中的数据恢复问题。这里我们暂时只看非sentinel模式下的处理方式。其中,除了一些LOG函数之外,有两个特别的函数linuxOvercommitMemoryWarning();loadDataFromDisk();

linuxOvercommitMemoryWarning();位于redis.c的第2821行,主要是用来检查在Linux下运行时,检测关于Linux内核对于内存分配方式策略的选择。该参数位于/proc/sys/vm/overcommit_memory,其值可以是0、1、2。这三个值分别代表的意义是:

  • 0表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。也就是说该模式下将允许轻微的overcommit,而后果严重的将失败。
  • 1表示内核允许分配所有的物理内存,而不管当前的内存状态如何。也就是说无论怎样都会给程序分配新的内存空间。
  • 2表示内核将根据某个值来决定是否分配内存,这个值为swap+某个比例(默认值为50%)下的RAM。超过该值得内存分配申请都将失败。

若Linux中该参数的值为0,则REDIS将LOG一条警告信息。

loadDataFromDisk();函数位于redis.c中的第2944行,根据之前初始化配置信息的内容决定是采用AOF或是RDB方式读取磁盘数据。

    /* Warning the user about suspicious maxmemory setting. */
    if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
        redisLog(REDIS_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);
    }

如源码注释所说,告诉使用者是不是maxmemory这个配置项的数值写错了,因为只指定了不到1MB的空间。

    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeMain(server.el);

aeSetBeforeSleepProc(server.el,beforeSleep);用来设置每次进入事件处理函数之前所需要执行的函数。而aeMain(server.el);函数,位于ae.c中的第450行,在经过了之前的一系列的操作之后,真正的开始Redis事件处理循环。

    aeDeleteEventLoop(server.el);
    return 0;

如果事件轮询结束,释放之前申请的资源,并且退出函数。

至此,整个Redis的启动流程大概分析完毕,中间很多地方只是大概说明了函数的作用,具体做法留待之后详细分析。

你可能感兴趣的:(redis)