Redis Server启动过程从源码来解析(二)

简介: Redis Server启动过程从源码来解析

Redis 参数的设置方法

Redis 对运行参数的设置实际上会经过三轮赋值,分别是默认配置值、命令行启动参数,以及配置文件配置值。

首先,Redis 在 main 函数中会先调用 initServerConfig 函数,为各种参数设置默认值。参数的默认值统一定义在 server.h 文件中,都是以 CONFIG_DEFAULT 开头的宏定义变量。下面的代码显示的是部分参数的默认值,你可以看下。

/* Static server configuration */
// server后台任务的默认运行频率
#define CONFIG_DEFAULT_HZ        10             /* Time interrupt calls/sec. */
// server后台任务的最小运行频率
#define CONFIG_MIN_HZ            1
// server后台任务的最大运行频率
#define CONFIG_MAX_HZ            500
// server监听的默认TCP端口
#define MAX_CLIENTS_PER_CLOCK_TICK 200          /* HZ is adapted based on that. */
#define CONFIG_MAX_LINE    1024
#define CRON_DBS_PER_CALL 16
#define NET_MAX_WRITES_PER_EVENT (1024*64)
#define PROTO_SHARED_SELECT_CMDS 10
#define OBJ_SHARED_INTEGERS 10000
#define OBJ_SHARED_BULKHDR_LEN 32
#define LOG_MAX_LEN    1024 /* Default maximum length of syslog messages.*/
#define AOF_REWRITE_ITEMS_PER_CMD 64
#define AOF_READ_DIFF_INTERVAL_BYTES (1024*10)
#define CONFIG_AUTHPASS_MAX_LEN 512
#define CONFIG_RUN_ID_SIZE 40
#define RDB_EOF_MARK_SIZE 40
#define CONFIG_REPL_BACKLOG_MIN_SIZE (1024*16)          /* 16k */
#define CONFIG_BGSAVE_RETRY_DELAY 5 /* Wait a few secs before trying again. */
#define CONFIG_DEFAULT_PID_FILE "/var/run/redis.pid"
#define CONFIG_DEFAULT_CLUSTER_CONFIG_FILE "nodes.conf"
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
#define CONFIG_DEFAULT_LOGFILE ""
#define NET_HOST_STR_LEN 256 /* Longest valid hostname */
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
#define NET_ADDR_STR_LEN (NET_IP_STR_LEN+32) /* Must be enough for ip:port */
#define NET_HOST_PORT_STR_LEN (NET_HOST_STR_LEN+32) /* Must be enough for hostname:port */
#define CONFIG_BINDADDR_MAX 16
#define CONFIG_MIN_RESERVED_FDS 32
#define CONFIG_DEFAULT_PROC_TITLE_TEMPLATE "{title} {listen-addr} {server-mode}"
/* Synchronous read timeout - slave side */
// 同步读取超时-从机端
#define CONFIG_REPL_SYNCIO_TIMEOUT 5

在 server.h 中提供的默认参数值,一般都是典型的配置值。因此,如果你在部署使用 Redis 实例的过程中,对 Redis 的工作原理不是很了解,就可以使用代码中提供的默认配置。当然,如果你对 Redis 各功能模块的工作机制比较熟悉的话,也可以自行设置运行参数。你可以在启动 Redis 程序时,在命令行上设置运行参数的值。比如,如果你想将 Redis server 监听端口从默认的 6379 修改为 7379,就可以在命令行上设置 port 参数为 7379,如下所示:

./redis-server --port 7379

这里,你需要注意的是,Redis 的命令行参数设置需要使用**两个减号“–”**来表示相应的参数名,否则的话,Redis 就无法识别所设置的运行参数。

Redis 在使用 initServerConfig 函数对参数设置默认配置值后

void initServerConfig(void) {
    int j;
    updateCachedTime(1);
    getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
    server.runid[CONFIG_RUN_ID_SIZE] = '\0';
    changeReplicationId();
    clearReplicationId2();
    server.hz = CONFIG_DEFAULT_HZ; /* Initialize it ASAP, even if it may get
                                      updated later after loading the config.
                                      This value may be used before the server
                                      is initialized. */
    server.timezone = getTimeZone(); /* Initialized by tzset(). */
    server.configfile = NULL;
    server.executable = NULL;
    server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
    server.bindaddr_count = 0;
    server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
    server.ipfd.count = 0;
    server.tlsfd.count = 0;
    server.sofd = -1;
    server.active_expire_enabled = 1;
    server.skip_checksum_validation = 0;
    server.saveparams = NULL;
    server.loading = 0;
    server.loading_rdb_used_mem = 0;
    server.logfile = zstrdup(CONFIG_DEFAULT_LOGFILE);
    server.aof_state = AOF_OFF;
    server.aof_rewrite_base_size = 0;
    server.aof_rewrite_scheduled = 0;
    server.aof_flush_sleep = 0;
    server.aof_last_fsync = time(NULL);
    atomicSet(server.aof_bio_fsync_status,C_OK);
    server.aof_rewrite_time_last = -1;
    server.aof_rewrite_time_start = -1;
    server.aof_lastbgrewrite_status = C_OK;
    server.aof_delayed_fsync = 0;
    server.aof_fd = -1;
    server.aof_selected_db = -1; /* Make sure the first time will not match */
    server.aof_flush_postponed_start = 0;
    server.pidfile = NULL;
    server.active_defrag_running = 0;
    server.notify_keyspace_events = 0;
    server.blocked_clients = 0;
    memset(server.blocked_clients_by_type,0,
           sizeof(server.blocked_clients_by_type));
    server.shutdown_asap = 0;
    server.cluster_configfile = zstrdup(CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
    server.cluster_module_flags = CLUSTER_MODULE_FLAG_NONE;
    server.migrate_cached_sockets = dictCreate(&migrateCacheDictType,NULL);
    server.next_client_id = 1; /* Client IDs, start from 1 .*/
    server.loading_process_events_interval_bytes = (1024*1024*2);
    unsigned int lruclock = getLRUClock();
    atomicSet(server.lruclock,lruclock);
    resetServerSaveParams();
    appendServerSaveParams(60*60,1);  /* save after 1 hour and 1 change */
    appendServerSaveParams(300,100);  /* save after 5 minutes and 100 changes */
    appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
    /* Replication related */
    server.masterauth = NULL;
    server.masterhost = NULL;
    server.masterport = 6379;
    server.master = NULL;
    server.cached_master = NULL;
    server.master_initial_offset = -1;
    server.repl_state = REPL_STATE_NONE;
    server.repl_transfer_tmpfile = NULL;
    server.repl_transfer_fd = -1;
    server.repl_transfer_s = NULL;
    server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
    server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
    server.master_repl_offset = 0;
    /* Replication partial resync backlog */
    server.repl_backlog = NULL;
    server.repl_backlog_histlen = 0;
    server.repl_backlog_idx = 0;
    server.repl_backlog_off = 0;
    server.repl_no_slaves_since = time(NULL);
    /* Failover related */
    server.failover_end_time = 0;
    server.force_failover = 0;
    server.target_replica_host = NULL;
    server.target_replica_port = 0;
    server.failover_state = NO_FAILOVER;
    /* Client output buffer limits */
    for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++)
        server.client_obuf_limits[j] = clientBufferLimitsDefaults[j];
    /* Linux OOM Score config */
    for (j = 0; j < CONFIG_OOM_COUNT; j++)
        server.oom_score_adj_values[j] = configOOMScoreAdjValuesDefaults[j];
    /* Double constants initialization */
    R_Zero = 0.0;
    R_PosInf = 1.0/R_Zero;
    R_NegInf = -1.0/R_Zero;
    R_Nan = R_Zero/R_Zero;
    /* Command table -- we initialize it here as it is part of the
     * initial configuration, since command names may be changed via
     * redis.conf using the rename-command directive. */
    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");
    server.zpopminCommand = lookupCommandByCString("zpopmin");
    server.zpopmaxCommand = lookupCommandByCString("zpopmax");
    server.sremCommand = lookupCommandByCString("srem");
    server.execCommand = lookupCommandByCString("exec");
    server.expireCommand = lookupCommandByCString("expire");
    server.pexpireCommand = lookupCommandByCString("pexpire");
    server.xclaimCommand = lookupCommandByCString("xclaim");
    server.xgroupCommand = lookupCommandByCString("xgroup");
    server.rpoplpushCommand = lookupCommandByCString("rpoplpush");
    server.lmoveCommand = lookupCommandByCString("lmove");
    /* Debugging */
    server.watchdog_period = 0;
    /* By default we want scripts to be always replicated by effects
     * (single commands executed by the script), and not by sending the
     * script to the slave / AOF. This is the new way starting from
     * Redis 5. However it is possible to revert it via redis.conf. */
    server.lua_always_replicate_commands = 1;
    /* Client Pause related */
    server.client_pause_type = CLIENT_PAUSE_OFF;
    server.client_pause_end_time = 0;   
    initConfigValues();
}

接下来,main 函数就会对 Redis 程序启动时的命令行参数进行逐一解析。main 函数会把解析后的参数及参数值保存成字符串,接着,main 函数会调用 loadServerConfig 函数进行第二和第三轮的赋值。

/* Load the server configuration from the specified filename.
 * The function appends the additional configuration directives stored
 * in the 'options' string to the config file before loading.
 *
 * Both filename and options can be NULL, in such a case are considered
 * empty. This way loadServerConfig can be used to just load a file or
 * just load a string. */
void loadServerConfig(char *filename, char config_from_stdin, char *options) {
    sds config = sdsempty();
    char buf[CONFIG_MAX_LINE+1];
    FILE *fp;
    /* Load the file content */
    if (filename) {
        if ((fp = fopen(filename,"r")) == NULL) {
            serverLog(LL_WARNING,
                    "Fatal error, can't open config file '%s': %s",
                    filename, strerror(errno));
            exit(1);
        }
        while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
            config = sdscat(config,buf);
        fclose(fp);
    }
    /* Append content from stdin */
    if (config_from_stdin) {
        serverLog(LL_WARNING,"Reading config from stdin");
        fp = stdin;
        while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
            config = sdscat(config,buf);
    }
    /* Append the additional options */
    if (options) {
        config = sdscat(config,"\n");
        config = sdscat(config,options);
    }
    loadServerConfigFromString(config);
    sdsfree(config);
}

以下代码显示了 main 函数对命令行参数的解析,以及调用 loadServerConfig 函数的过程,你可以看下。

int main(int argc, char **argv) {
  ......
    // 保存命令行参数
    for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);
  ......
    // 如果参数个数大于等于2个   
    if (argc >= 2) {
        j = 1; /* First option to parse in argv[] */
        sds options = sdsempty();
        /* 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);
            }
        }
        /* Parse command line options
         * Precedence wise, File, stdin, explicit options -- last config is the one that matters.
         *
         * First argument is the config file name? */
        if (argv[1][0] != '-') {
            /* Replace the config file in server.exec_argv with its absolute path. */
            server.configfile = getAbsolutePath(argv[1]);
            zfree(server.exec_argv[1]);
            server.exec_argv[1] = zstrdup(server.configfile);
            j = 2; // Skip this arg when parsing options
        }
        // 对每个运行时参数进行解析
        while(j < argc) {
            /* Either first or last argument - Should we read config from stdin? */
            if (argv[j][0] == '-' && argv[j][1] == '\0' && (j == 1 || j == argc-1)) {
                config_from_stdin = 1;
            }
            /* 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 config file
             * and stdin input are parsed (if they exist). */
            // 是否是两个--
            else 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++;
        }
        loadServerConfig(server.configfile, config_from_stdin, options);
        if (server.sentinel_mode) loadSentinelConfigFromQueue();
        sdsfree(options);
    }
    return 0;
}

这里你要知道的是,loadServerConfig 函数是在config.c文件中实现的,该函数是以 Redis 配置文件和命令行参数的解析字符串为参数,将配置文件中的所有配置项读取出来,形成字符串。紧接着,loadServerConfig 函数会把解析后的命令行参数,追加到配置文件形成的配置项字符串

这样一来,配置项字符串就同时包含了配置文件中设置的参数,以及命令行设置的参数。

最后,loadServerConfig 函数会进一步调用 loadServerConfigFromString 函数,对配置项字符串中的每一个配置项进行匹配。一旦匹配成功,loadServerConfigFromString 函数就会按照配置项的值设置 server 的参数。以下代码显示了 loadServerConfigFromString 函数的部分内容。这部分代码是使用了条件分支,来依次比较配置项是否是“timeout”和“tcp-keepalive”,如果匹配上了,就将 server 参数设置为配置项的值。同时,代码还会检查配置项的值是否合理,比如是否小于 0。如果参数值不合理,程序在运行时就会报错。另外对于其他的配置项,loadServerConfigFromString 函数还会继续使用 else if 分支进行判断。

config.c文件中查看

loadServerConfigFromString(char *config) {
   //参数名匹配,检查参数是否为“timeout“
   if (!strcasecmp(argv[0],"timeout") && argc == 2) {
       //设置server的maxidletime参数
       server.maxidletime = atoi(argv[1]);
       //检查参数值是否小于0,小于0则报错
       if (server.maxidletime < 0) {
           err = "Invalid timeout value"; goto loaderr;
       }
   }
  //参数名匹配,检查参数是否为“tcp-keepalive“
  else if (!strcasecmp(argv[0],"tcp-keepalive") && argc == 2) {
      //设置server的tcpkeepalive参数
      server.tcpkeepalive = atoi(argv[1]);
    //检查参数值是否小于0,小于0则报错
      if (server.tcpkeepalive < 0) {
          err = "Invalid tcp-keepalive value"; goto loaderr;
      }
   }
}

好了,到这里,你应该就了解了 Redis server 运行参数配置的步骤,我也画了一张图,以便你更直观地理解这个过程。

在完成参数配置后,main 函数会开始调用 initServer 函数,对 server 进行初始化。所以接下来,我们继续来了解 Redis server 初始化时的关键操作。

initServer:初始化

Redis serverRedis server 的初始化操作,主要可以分成三个步骤。

  • 第一步,Redis server 运行时需要对多种资源进行管理

比如说,和 server 连接的客户端、从库等,Redis 用作缓存时的替换候选集,以及 server 运行时的状态信息,这些资源的管理信息都会在 initServer 函数中进行初始化。我给你举个例子,initServer 函数会创建链表来分别维护客户端和从库,并调用 evictionPoolAlloc 函数(在evict.c中)

/* Create a new eviction pool. */
void evictionPoolAlloc(void) {
    struct evictionPoolEntry *ep;
    int j;
    ep = zmalloc(sizeof(*ep)*EVPOOL_SIZE);
    for (j = 0; j < EVPOOL_SIZE; j++) {
        ep[j].idle = 0;
        ep[j].key = NULL;
        ep[j].cached = sdsnewlen(NULL,EVPOOL_CACHED_SDS_SIZE);
        ep[j].dbid = 0;
    }
    EvictionPoolLRU = ep;
}

采样生成用于淘汰的候选 key 集合。同时,initServer 函数还会调用 resetServerStats 函数(在 server.c 中)重置 server 运行状态信息。

/* Resets the stats that we expose via INFO or other means that we want
 * to reset via CONFIG RESETSTAT. The function is also used in order to
 * initialize these fields in initServer() at server startup. */
void resetServerStats(void) {
    int j;
    server.stat_numcommands = 0;
    server.stat_numconnections = 0;
    server.stat_expiredkeys = 0;
    server.stat_expired_stale_perc = 0;
    server.stat_expired_time_cap_reached_count = 0;
    server.stat_expire_cycle_time_used = 0;
    server.stat_evictedkeys = 0;
    server.stat_keyspace_misses = 0;
    server.stat_keyspace_hits = 0;
    server.stat_active_defrag_hits = 0;
    server.stat_active_defrag_misses = 0;
    server.stat_active_defrag_key_hits = 0;
    server.stat_active_defrag_key_misses = 0;
    server.stat_active_defrag_scanned = 0;
    server.stat_fork_time = 0;
    server.stat_fork_rate = 0;
    server.stat_total_forks = 0;
    server.stat_rejected_conn = 0;
    server.stat_sync_full = 0;
    server.stat_sync_partial_ok = 0;
    server.stat_sync_partial_err = 0;
    server.stat_io_reads_processed = 0;
    atomicSet(server.stat_total_reads_processed, 0);
    server.stat_io_writes_processed = 0;
    atomicSet(server.stat_total_writes_processed, 0);
    for (j = 0; j < STATS_METRIC_COUNT; j++) {
        server.inst_metric[j].idx = 0;
        server.inst_metric[j].last_sample_time = mstime();
        server.inst_metric[j].last_sample_count = 0;
        memset(server.inst_metric[j].samples,0,
            sizeof(server.inst_metric[j].samples));
    }
    atomicSet(server.stat_net_input_bytes, 0);
    atomicSet(server.stat_net_output_bytes, 0);
    server.stat_unexpected_error_replies = 0;
    server.stat_total_error_replies = 0;
    server.stat_dump_payload_sanitizations = 0;
    server.aof_delayed_fsync = 0;
}
  • 第二步,在完成资源管理信息的初始化后,initServer 函数会对 Redis 数据库进行初始化

因为一个 Redis 实例可以同时运行多个数据库,所以 initServer 函数会使用一个循环,依次为每个数据库创建相应的数据结构。这个代码逻辑是实现在 initServer 函数中,它会为每个数据库执行初始化操作,包括创建全局哈希表,为过期 key、被 BLPOP 阻塞的 key、将被 PUSH 的 key 和被监听的 key 创建相应的信息表

/* Create the Redis databases, and initialize other internal state. */
for (j = 0; j < server.dbnum; j++) {
    //创建全局哈希表
    server.db[j].dict = dictCreate(&dbDictType,NULL);
    //创建过期key的信息表
    server.db[j].expires = dictCreate(&dbExpiresDictType,NULL);
    server.db[j].expires_cursor = 0;
    //为被BLPOP阻塞的key创建信息表
    server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
    //为将执行PUSH的阻塞key创建信息表
    server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
    //为被MULTI/WATCH操作监听的key创建信息表
    server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
    server.db[j].id = j;
    server.db[j].avg_ttl = 0;
    server.db[j].defrag_later = listCreate();
    listSetFreeMethod(server.db[j].defrag_later,(void (*)(void*))sdsfree);
}
  • 第三步,initServer 函数会为运行的 Redis server 创建事件驱动框架,并开始启动端口监听,用于接收外部请求。

注意,为了高效处理高并发的外部请求,initServer 在创建的事件框架中,针对每个监听 IP 上可能发生的客户端连接,都创建了监听事件,用来监听客户端连接请求。同时,initServer 为监听事件设置了相应的处理函数 acceptTcpHandler

networking.c文件中查看

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
    char cip[NET_IP_STR_LEN];
    UNUSED(el);
    UNUSED(mask);
    UNUSED(privdata);
    while(max--) {
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
        if (cfd == ANET_ERR) {
            if (errno != EWOULDBLOCK)
                serverLog(LL_WARNING,
                    "Accepting client connection: %s", server.neterr);
            return;
        }
        anetCloexec(cfd);
        serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
        acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
    }
}

这样一来,只要有客户端连接到 server 监听的 IP 和端口,事件驱动框架就会检测到有连接事件发生,然后调用 acceptTcpHandler 函数来处理具体的连接。你可以参考以下代码中展示的处理逻辑:

//创建事件循环框架
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
if (server.el == NULL) {
    serverLog(LL_WARNING,"Failed creating the event loop. Error message: '%s'",strerror(errno));
    exit(1);
}
// 给数据库分配内存
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
//开始监听设置的网络端口
/* Open the TCP listening socket for the user commands. */
if (server.port != 0 &&
    listenToPort(server.port,&server.ipfd) == C_ERR) {
    serverLog(LL_WARNING, "Failed listening on port %u (TCP), aborting.", server.port);
    exit(1);
} 
/* Create the timer callback, this is our way to process many background
     * operations incrementally, like clients timeout, eviction of unaccessed
     * expired keys and so forth. */
//为server后台任务创建定时事件
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
    serverPanic("Can't create event loop timers.");
    exit(1);
}

那么到这里,Redis server 在完成运行参数设置和初始化后,就可以开始处理客户端请求了。为了能持续地处理并发的客户端请求,server 在 main 函数的最后,会进入事件驱动循环机制。而这就是接下来,我们要了解的事件驱动框架的执行过程。

执行事件驱动框架

事件驱动框架是 Redis server 运行的核心。该框架一旦启动后,就会一直循环执行,每次循环会处理一批触发的网络读写事件。关于事件驱动框架本身的设计思想与实现方法,这里,我们主要是学习 Redis 入口的 main 函数中,是如何转换到事件驱动框架进行执行的。

其实,进入事件驱动框架开始执行并不复杂,main 函数直接调用事件框架的主体函数 aeMain(在ae.c文件中)后,就进入事件处理循环了。

当然,在进入事件驱动循环前,main 函数会分别调用 aeSetBeforeSleepProcaeSetAfterSleepProc 两个函数,来设置每次进入事件循环前 server 需要执行的操作,以及每次事件循环结束后 server 需要执行的操作。下面代码显示了这部分的执行逻辑,你可以看下。

int main(int argc, char **argv) {
   /* Register before and after sleep handlers (note this needs to be done
     * before loading persistence since it is used by processEventsWhileBlocked. */
    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeSetAfterSleepProc(server.el,afterSleep);
    aeMain(server.el);
    aeDeleteEventLoop(server.el);
  ...
}

总结

我们通过 server.c 文件中 main 函数的设计和实现思路,了解了 Redis server 启动后的五个主要阶段。在这五个阶段中,运行参数解析、server 初始化和执行事件驱动框架则是 Redis sever 启动过程中的三个关键阶段。所以相应的,我们需要重点关注以下三个要点。

  • 第一,main 函数是使用 initServerConfig 给 server 运行参数设置默认值,然后会解析命令行参数,并通过 loadServerConfig 读取配置文件参数值,将命令行参数追加至配置项字符串。最后,Redis 会调用 loadServerConfigFromString 函数,来完成配置文件参数和命令行参数的设置。
  • 第二,在 Redis server 完成参数设置后,initServer 函数会被调用,用来初始化 server 资源管理的主要结构,同时会初始化数据库启动状态,以及完成 server 监听 IP 和端口的设置。
  • 第三,一旦 server 可以接收外部客户端的请求后,main 函数会把程序的主体控制权,交给事件驱动框架的入口函数,也就 aeMain 函数。aeMain 函数会一直循环执行,处理收到的客户端请求。到此为止,server.c 中的 main 函数功能就已经全部完成了,程序控制权也交给了事件驱动循环框架,Redis 也就可以正常处理客户端请求了。

实际上,Redis server 的启动过程从基本的初始化操作,到命令行和配置文件的参数解析设置,再到初始化 server 各种数据结构,以及最后的执行事件驱动框架,这是一个典型的网络服务器执行过程,你在开发网络服务器时,就可以作为参考。而且,掌握了启动过程中的初始化操作,还可以帮你解答一些使用中的疑惑。比如,Redis 启动时是先读取 RDB 文件,还是先读取 AOF 文件。如果你了解了 Redis server 的启动过程,就可以从 loadDataFromDisk 函数中看到,Redis server 会先读取 AOF;而如果没有 AOF,则再读取 RDB。所以,掌握 Redis server 启动过程,有助于你更好地了解 Redis 运行细节,这样当你遇到问题时,就知道还可以从启动过程中去溯源 server 的各种初始状态,从而助力你更好地解决问题。

以上来源于极客时间的Redis源码剖析与实战,感兴趣的可以去看看,不过我在他的基础上补充了每个源码来自于哪个类,以及调整了个人的一些格式爱好。

相关文章
|
11月前
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
801 0
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1377 29
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
554 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
12月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
7月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
792 25
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
1573 0
|
8月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
351 1
Redis专题-实战篇二-商户查询缓存

推荐镜像

更多
  • DNS