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

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
全局流量管理 GTM,标准版 1个月
简介: 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源码剖析与实战,感兴趣的可以去看看,不过我在他的基础上补充了每个源码来自于哪个类,以及调整了个人的一些格式爱好。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
存储 NoSQL Java
【面试宝藏】Redis 常见面试题解析
Redis 是内存数据结构存储系统,用作数据库、缓存和消息中间件,支持字符串、哈希、列表等数据类型。它的优点包括高性能、原子操作、持久化和复制。相比 Memcached,Redis 提供数据持久化、丰富数据结构和发布/订阅功能。Redis 采用单线程模型,但通过 I/O 多路复用处理高并发。常见的面试问题涉及持久化机制、过期键删除、回收策略、集群和客户端等。
67 4
|
1月前
|
存储 缓存 NoSQL
【面试宝藏】Redis 常见面试题解析其二
Redis 高级面试题涵盖了哈希槽机制、集群的主从复制、数据丢失可能性、复制机制、最大节点数、数据库选择、连通性测试、事务操作、过期时间和内存优化等。Redis 使用哈希槽实现数据分布,主从复制保障高可用,异步复制可能导致写操作丢失。集群最大支持1000个节点,仅允许单数据库。可通过 `ping` 命令测试连接,使用 `EXPIRE` 设置过期时间,`MULTI/EXEC` 等进行事务处理。内存优化包括合理数据类型、设置过期时间及淘汰策略。Redis 可用作缓存、会话存储、排行榜等场景,使用 `SCAN` 查找特定前缀键,列表实现异步队列,分布式锁则通过 `SET` 命令和 Lua 脚本实现。
34 5
|
10天前
|
NoSQL Redis
The last packet sent successfully to the server was 0 milliseconds ago. 若依修改,redis中的主机忘记修改
The last packet sent successfully to the server was 0 milliseconds ago. 若依修改,redis中的主机忘记修改
|
1月前
|
NoSQL 测试技术 Scala
java.lang.RuntimeException: Can‘t start redis server. Check logs for details.
java.lang.RuntimeException: Can‘t start redis server. Check logs for details.
26 1
|
1月前
|
存储 监控 NoSQL
Redis中的LRU淘汰策略深入解析
Redis的内存管理关键在于处理数据增长与有限内存的矛盾,LRU策略被广泛用于此。LRU基于“不常访问的数据未来访问可能性小”的假设,淘汰最近最少使用的数据。Redis通过双向链表实现,但并非严格LRU,而是采样算法以平衡性能和精度。用户可通过调整`maxmemory-samples`等参数优化。尽管LRU简单高效,但无法区分数据重要性和访问频率,可能误淘汰重要数据。合理设置参数、结合其他策略、监控调优是优化LRU使用的关键。
20 1
|
17天前
|
存储 JSON NoSQL
深入解析RedisJSON:在Redis中直接处理JSON数据
深入解析RedisJSON:在Redis中直接处理JSON数据
|
2月前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
|
10天前
|
NoSQL Java Redis
软件开发常见流程之宝塔初始化安装环境配置,Lam前面不选,直接跳商城,在宝塔内点击软件商城,安Mysql5.7,安java项目管理器,安Ngnix最新版,安Redis
软件开发常见流程之宝塔初始化安装环境配置,Lam前面不选,直接跳商城,在宝塔内点击软件商城,安Mysql5.7,安java项目管理器,安Ngnix最新版,安Redis
|
12天前
|
存储 消息中间件 缓存
apt安装Redis 7
【7月更文挑战第2天】
|
17天前
|
存储 NoSQL 关系型数据库
Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)
Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)
42 1

推荐镜像

更多