Redis源碼閱讀【8-命令處理生命週期-3】

Redis源碼閱讀【1-簡單動態字符串】

1、介紹

  • 上一節我們講述了客戶端,服務端,事件處理等基礎知識,下面開啓學習Redis服務器的啓動過程,這裏主要分爲server的初始化監聽端口以及等待命令3階段。

2、server初始化

服務器初始化主流程可以分爲7個步驟:

  • 【1】、初始化配置,包括用戶可配置的參數,以及命令表的初始化;
  • 【2】、加載並解析配置文件;
  • 【3】、初始化服務端內部變量;
  • 【4】、創建事件循環aeEventLoop;
  • 【5】、創建socket並啓動監聽;
  • 【6】、創建文件事件與時間事件;
  • 【7】、開啓循環;

整體流程如下圖:
在這裏插入圖片描述

2.1、初始化配置

步驟初始化配置,由函數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.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.client_max_querybuf_len = PROTO_MAX_QUERYBUF_LEN;
    server.saveparams = NULL;
    server.loading = 0;
    server.logfile = zstrdup(CONFIG_DEFAULT_LOGFILE);
    //AOF相關配置
    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);
    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);
    //獲取LRU的時間戳
    server.lruclock = getLRUClock();
    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);

    /* Client output buffer limits */
    //客戶端響應緩衝區空間
    for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++)
        server.client_obuf_limits[j] = clientBufferLimitsDefaults[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 initiialize 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");

    /* Debugging */
    //debug配置
    server.assert_failed = "<no assertion failed>";
    server.assert_file = "<no file>";
    server.assert_line = 0;
    server.bug_report_start = 0;
    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;
    //初始化其它默認配置
    initConfigValues();
}
  • 注意除了以上的配置最底下還有一個initConfigValues函數(定義實現在config.c文件中),裏面有一個循環,在循環裏面初始胡配置,其中循環的結構體是standardConfig,其代碼如下:
void initConfigValues() {
    for (standardConfig *config = configs; config->name != NULL; config++) {
        config->interface.init(config->data);
    }
}

循環的standardConfig本身是一個含義默認值的數組,在config.c文件中還有一個configs數組,裏面放置着各種各樣的默認配置以及,對應讀取的配置字段,通過上面的循環代碼,一一將配置初始化,內容太多了,這裏就不一個個介紹了,大家基本上都能根據配置名稱判斷出,配置的內容:

standardConfig configs[] = {
    /* Bool configs */
    createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL),
    createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL),
    createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, server.io_threads_do_reads, 0,NULL, NULL), /* Read + parse from threads? */
    createBoolConfig("lua-replicate-commands", NULL, IMMUTABLE_CONFIG, server.lua_always_replicate_commands, 1, NULL, NULL),
    createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, server.always_show_logo, 0, NULL, NULL),
    createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, server.protected_mode, 1, NULL, NULL),
    createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, server.rdb_compression, 1, NULL, NULL),
    createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, server.activerehashing, 1, NULL, NULL),
    createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, server.stop_writes_on_bgsave_err, 1, NULL, NULL),
    createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, server.dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
    createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_eviction, 0, NULL, NULL),
    createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_expire, 0, NULL, NULL),
    createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL),
    createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, server.repl_disable_tcp_nodelay, 0, NULL, NULL),
    createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, server.repl_diskless_sync, 0, NULL, NULL),
    createBoolConfig("gopher-enabled", NULL, MODIFIABLE_CONFIG, server.gopher_enabled, 0, NULL, NULL),
    createBoolConfig("aof-rewrite-incremental-fsync", NULL, MODIFIABLE_CONFIG, server.aof_rewrite_incremental_fsync, 1, NULL, NULL),
    createBoolConfig("no-appendfsync-on-rewrite", NULL, MODIFIABLE_CONFIG, server.aof_no_fsync_on_rewrite, 0, NULL, NULL),
    createBoolConfig("cluster-require-full-coverage", NULL, MODIFIABLE_CONFIG, server.cluster_require_full_coverage, 1, NULL, NULL),
    createBoolConfig("rdb-save-incremental-fsync", NULL, MODIFIABLE_CONFIG, server.rdb_save_incremental_fsync, 1, NULL, NULL),
    createBoolConfig("aof-load-truncated", NULL, MODIFIABLE_CONFIG, server.aof_load_truncated, 1, NULL, NULL),
    createBoolConfig("aof-use-rdb-preamble", NULL, MODIFIABLE_CONFIG, server.aof_use_rdb_preamble, 1, NULL, NULL),
    createBoolConfig("cluster-replica-no-failover", "cluster-slave-no-failover", MODIFIABLE_CONFIG, server.cluster_slave_no_failover, 0, NULL, NULL), /* Failover by default. */
    createBoolConfig("replica-lazy-flush", "slave-lazy-flush", MODIFIABLE_CONFIG, server.repl_slave_lazy_flush, 0, NULL, NULL),
    createBoolConfig("replica-serve-stale-data", "slave-serve-stale-data", MODIFIABLE_CONFIG, server.repl_serve_stale_data, 1, NULL, NULL),
    createBoolConfig("replica-read-only", "slave-read-only", MODIFIABLE_CONFIG, server.repl_slave_ro, 1, NULL, NULL),
    createBoolConfig("replica-ignore-maxmemory", "slave-ignore-maxmemory", MODIFIABLE_CONFIG, server.repl_slave_ignore_maxmemory, 1, NULL, NULL),
    createBoolConfig("jemalloc-bg-thread", NULL, MODIFIABLE_CONFIG, server.jemalloc_bg_thread, 1, NULL, updateJemallocBgThread),
    createBoolConfig("activedefrag", NULL, MODIFIABLE_CONFIG, server.active_defrag_enabled, 0, isValidActiveDefrag, NULL),
    createBoolConfig("syslog-enabled", NULL, IMMUTABLE_CONFIG, server.syslog_enabled, 0, NULL, NULL),
    createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, server.cluster_enabled, 0, NULL, NULL),
    createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),

    /* String Configs */
    createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL),
    createStringConfig("unixsocket", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.unixsocket, NULL, NULL, NULL),
    createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL),
    createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL),
    createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
    createStringConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL),
    createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, NULL),
    createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL),
    createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
    createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_filename, "appendonly.aof", isValidAOFfilename, NULL),

    /* Enum Configs */
    createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),
    createEnumConfig("syslog-facility", NULL, IMMUTABLE_CONFIG, syslog_facility_enum, server.syslog_facility, LOG_LOCAL0, NULL, NULL),
    createEnumConfig("repl-diskless-load", NULL, MODIFIABLE_CONFIG, repl_diskless_load_enum, server.repl_diskless_load, REPL_DISKLESS_LOAD_DISABLED, NULL, NULL),
    createEnumConfig("loglevel", NULL, MODIFIABLE_CONFIG, loglevel_enum, server.verbosity, LL_NOTICE, NULL, NULL),
    createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, server.maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
    createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, server.aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL),

    /* Integer configs */
    createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, server.dbnum, 16, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.port, 6379, INTEGER_CONFIG, NULL, NULL), /* TCP port. */
    createIntConfig("io-threads", NULL, IMMUTABLE_CONFIG, 1, 512, server.io_threads_num, 1, INTEGER_CONFIG, NULL, NULL), /* Single threaded by default */
    createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */
    createIntConfig("list-max-ziplist-size", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, server.list_max_ziplist_size, -2, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("tcp-keepalive", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tcpkeepalive, 300, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("cluster-migration-barrier", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_migration_barrier, 1, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("active-defrag-cycle-min", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_min, 1, INTEGER_CONFIG, NULL, NULL), /* Default: 1% CPU min (at lower threshold) */
    createIntConfig("active-defrag-cycle-max", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_max, 25, INTEGER_CONFIG, NULL, NULL), /* Default: 25% CPU max (at upper threshold) */
    createIntConfig("active-defrag-threshold-lower", NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_lower, 10, INTEGER_CONFIG, NULL, NULL), /* Default: don't defrag when fragmentation is below 10% */
    createIntConfig("active-defrag-threshold-upper", NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_upper, 100, INTEGER_CONFIG, NULL, NULL), /* Default: maximum defrag force at 100% fragmentation */
    createIntConfig("lfu-log-factor", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_log_factor, 10, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("lfu-decay-time", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_decay_time, 1, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, server.slave_priority, 100, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */
    createIntConfig("replica-announce-port", "slave-announce-port", MODIFIABLE_CONFIG, 0, 65535, server.slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("tcp-backlog", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
    createIntConfig("cluster-announce-bus-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_bus_port, 0, INTEGER_CONFIG, NULL, NULL), /* Default: Use +10000 offset. */
    createIntConfig("cluster-announce-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.port */
    createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_timeout, 60, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.key_load_delay, 0, INTEGER_CONFIG, NULL, NULL),
    createIntConfig("tracking-table-max-fill", NULL, MODIFIABLE_CONFIG, 0, 100, server.tracking_table_max_fill, 10, INTEGER_CONFIG, NULL, NULL), /* Default: 10% tracking table max fill. */
    createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, server.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */
    createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ),
    createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves),
    createIntConfig("min-replicas-max-lag", "min-slaves-max-lag", MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_min_slaves_max_lag, 10, INTEGER_CONFIG, NULL, updateGoodSlaves),

    /* Unsigned int configs */
    createUIntConfig("maxclients", NULL, MODIFIABLE_CONFIG, 1, UINT_MAX, server.maxclients, 10000, INTEGER_CONFIG, NULL, updateMaxclients),

    /* Unsigned Long configs */
    createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */
    createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL),

    /* Long Long configs */
    createLongLongConfig("lua-time-limit", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.lua_time_limit, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */
    createLongLongConfig("cluster-node-timeout", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL),
    createLongLongConfig("slowlog-log-slower-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL),
    createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL),
    createLongLongConfig("proto-max-bulk-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */
    createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL),
    createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */

    /* Unsigned Long Long configs */
    createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),

    /* Size_t configs */
    createSizeTConfig("hash-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_ziplist_entries, 512, INTEGER_CONFIG, NULL, NULL),
    createSizeTConfig("set-max-intset-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.set_max_intset_entries, 512, INTEGER_CONFIG, NULL, NULL),
    createSizeTConfig("zset-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_entries, 128, INTEGER_CONFIG, NULL, NULL),
    createSizeTConfig("active-defrag-ignore-bytes", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.active_defrag_ignore_bytes, 100<<20, MEMORY_CONFIG, NULL, NULL), /* Default: don't defrag if frag overhead is below 100mb */
    createSizeTConfig("hash-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
    createSizeTConfig("stream-node-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL),
    createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
    createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),

    /* Other configs */
    createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */
    createOffTConfig("auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL),

#ifdef USE_OPENSSL
    createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, NULL), /* TCP port. */
    createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, NULL),
    createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, NULL),
    createBoolConfig("tls-auth-clients", NULL, MODIFIABLE_CONFIG, server.tls_auth_clients, 1, NULL, NULL),
    createBoolConfig("tls-prefer-server-ciphers", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.prefer_server_ciphers, 0, NULL, updateTlsCfgBool),
    createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg),
    createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg),
    createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg),
    createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg),
    createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg),
    createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.protocols, NULL, NULL, updateTlsCfg),
    createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphers, NULL, NULL, updateTlsCfg),
    createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphersuites, NULL, NULL, updateTlsCfg),
#endif

    /* NULL Terminator */
    {NULL}
};

2.2、加載並解析配置文件

-當初始化完成配置後,就會進行下一個步驟:加載並解析配置文件,入口函數爲loadServerConfig,函數聲明如下:

void loadServerConfig(char *filename, char *options)
  • 輸入參數filename表示配置文件全路徑名稱,options表示命令行輸入的配置參數,例如我們通常以如下命令啓動Redis服務器:
/home/redis/redis-server /home/redis/redis.conf -p 4000
  • Redis的配置語法是,每一行是一條配置,格式如:配置參數1【參數2】【............】,加載配置文件只需要一行一行的將文件內容讀取到內存中即可。加完成後就會開始解析配置,loadServerConfig的實現如下:
void loadServerConfig(char *filename, char *options) {
    sds config = sdsempty();
    char buf[CONFIG_MAX_LINE+1];

    /* Load the file content */
    if (filename) {
        FILE *fp;
        //對文件名稱校驗
        if (filename[0] == '-' && filename[1] == '\0') {
            fp = stdin;
        } else {
            //打開配置件
            if ((fp = fopen(filename,"r")) == NULL) {
                serverLog(LL_WARNING,
                    "Fatal error, can't open config file '%s'", filename);
                exit(1);
            }
        }//依次將文件中的每行配置寫入 config
        while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
            config = sdscat(config,buf);
        if (fp != stdin) fclose(fp);
    }
    /* Append the additional options */
    if (options) {
        config = sdscat(config,"\n");
        config = sdscat(config,options);
    }
    //開始從讀取出來的字符串解析配置
    loadServerConfigFromString(config);
    //釋放
    sdsfree(config);
}
  • 當配置讀取完成的時候我們會調用loadServerConfigFromString,從字符中解析相對於的配置,代碼如下:
void loadServerConfigFromString(char *config) {
    char *err = NULL;
    int linenum = 0, totlines, i;
    int slaveof_linenum = 0;
    sds *lines;
    //將字符串分割爲多行
    lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
    //一行行解析
    for (i = 0; i < totlines; i++) {
        sds *argv;
        int argc;
        linenum = i+1;
        lines[i] = sdstrim(lines[i]," \t\r\n");
        //跳過註釋與空行
        if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
        /* Split into arguments */
        //解析配置參數,來判定當前是什麼配置
        argv = sdssplitargs(lines[i],&argc);
        //參數解析失敗,中止初始化
        if (argv == NULL) {
            err = "Unbalanced quotes in configuration line";
            goto loaderr;
        }
        ...............後面都是 if-else 判斷配置並初始化..............
  • 上面的代碼中,函數首先將輸入配置字符串以\n,爲字符分隔符劃分爲多行,totlines記錄總行數,lines數組存儲分割後的配置,數組元素類型爲字符串SDS;for循環遍歷所有配置行,解析配置參數,並根據參數內容設置結構體server各字段。並且開頭爲 # 或者 \0 的字符標記本行爲註釋內容,直接跳過。

2.3、初始化服務器內部變量

  • 完成配置加載和解析後,需要對服務器內部的變量進行一個初始化,比如客戶端鏈表數據庫全局變量共享對象等等,入口函數爲initServer,由於裏面很多重複性內容,這裏講解主要內容,函數邏輯如下:
void initServer(void) {
................................................
    //serverCron函數執行頻率,默認爲10
    server.hz = server.config_hz;
    //當前線程PID
    server.pid = getpid();
  
    //初始化客戶端鏈表
    server.clients = listCreate();

	//創建共享對象
	createSharedObjects();
	
	//創建eventLoop事件循環
 	server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
 ....................................................
    //分配數據庫空間默認16個數據庫
    server.db = zmalloc(sizeof(redisDb)*server.dbnum);

    /* Open the TCP listening socket for the user commands. */
    //監聽TCP端口
    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);
    //監聽TLS
    if (server.tls_port != 0 &&
        listenToPort(server.tls_port,server.tlsfd,&server.tlsfd_count) == C_ERR)
        exit(1);

    //初始化每個db
    for (j = 0; j < server.dbnum; j++) {
        server.db[j].dict = dictCreate(&dbDictType,NULL);
        server.db[j].expires = dictCreate(&keyptrDictType,NULL);
        server.db[j].expires_cursor = 0;
        server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
        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);
    }
.....................................................
     //限制32位最大內存使用空間位3GB 強行設置策略爲 MAXMEMORY_NO_EVICTION
    if (server.arch_bits == 32 && server.maxmemory == 0) {
        serverLog(LL_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.");
        server.maxmemory = 3072LL*(1024*1024); /* 3 GB */
        server.maxmemory_policy = MAXMEMORY_NO_EVICTION;
    }
.....................................................
}
  • 注意數據庫字典的dictType指向的是結構體dbDictType,其定義如下:
dictType dbDictType = {
    dictSdsHash,               //當前字典鍵散列函數 /* hash function */
    NULL,                       /* key dup */
    NULL,                       /* val dup */
    dictSdsKeyCompare,         //當前字典鍵的比較函數 /* key compare */
    dictSdsDestructor,         //當前字典鍵析構函數 /* key destructor */
    dictObjectDestructor       //當前字典對象析構函數
};
  • 數據庫鍵都是SDS類型,鍵散列函數爲dictSdsHash,鍵比較函數爲dictSdsKeyCompare,鍵析構函數爲dictSdsDestructor;數據庫對象是robj對象,值析構函數爲dictObjectDestructor;鍵和值的內存賦值函數都爲NULL

  • 除了以上內容之外,還會調用createSharedObjects函數初始化共享對象,例如前面提到的 robjrefcount 字段。執行命令時 Redis 會返回一些字符串回覆,這些字符串對象同樣在服務器初始化時創建,且永遠不會嘗試釋放這類對象。共享對象都存儲在全局結構體變量shared中,下面來看看shared對應的結構體:

struct sharedObjectsStruct {
    robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
    *colon, *queued, *null[4], *nullarray[4], *emptymap[4], *emptyset[4],
    *emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
    *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
    *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
    *busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
    *unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
    *rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
    *select[PROTO_SHARED_SELECT_CMDS],
    *integers[OBJ_SHARED_INTEGERS],
    *mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
    *bulkhdr[OBJ_SHARED_BULKHDR_LEN];  /* "$<value>\r\n" */
    sds minstring, maxstring;
};

void createSharedObjects(void) {
    int j;

    shared.crlf = createObject(OBJ_STRING,sdsnew("\r\n"));
    shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));
    shared.err = createObject(OBJ_STRING,sdsnew("-ERR\r\n"));
    shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));
    shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));
    shared.cone = createObject(OBJ_STRING,sdsnew(":1\r\n"));
    shared.emptyarray = createObject(OBJ_STRING,sdsnew("*0\r\n"));
    shared.pong = createObject(OBJ_STRING,sdsnew("+PONG\r\n"));
    shared.queued = createObject(OBJ_STRING,sdsnew("+QUEUED\r\n"));
    shared.emptyscan = createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n"));
.........................................................

  • 從上面可以看出,大部分共享對象都是共享指令,且都是RESP協議格式,我們知道Redis是基於RESP協議的,通過緩存RESP的指令,方便快速響應客戶端,以及減少內存開銷。

2.4、創建事件循環eventLoop

  • 初始化完成內部變量後,就是創建事件循環eventLoop,即分配結構體所需內存,並且初始化結構體各字段;epoll就是在這個時候創建的,在initServer函數裏面,執行完createSharedObjects創建共享對象之後,代碼如下:
void initServer(void) {
..................................................
	//創建事件循環
	server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
..................................................
}

//創建eventLoop
aeEventLoop *aeCreateEventLoop(int setsize) {
    aeEventLoop *eventLoop;
    int i;

    if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
    eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
    eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
    if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
    eventLoop->setsize = setsize;
    eventLoop->lastTime = time(NULL);
    eventLoop->timeEventHead = NULL;
    eventLoop->timeEventNextId = 0;
    eventLoop->stop = 0;
    eventLoop->maxfd = -1;
    eventLoop->beforesleep = NULL;
    eventLoop->aftersleep = NULL;
    eventLoop->flags = 0;
    if (aeApiCreate(eventLoop) == -1) goto err;
    /* Events with mask == AE_NONE are not set. So let's initialize the
     * vector with it. */
    for (i = 0; i < setsize; i++)
        eventLoop->events[i].mask = AE_NONE;
    return eventLoop;

err:
    if (eventLoop) {
        zfree(eventLoop->events);
        zfree(eventLoop->fired);
        zfree(eventLoop);
    }
    return NULL;
}
  • 在創建eventLoop的時候,輸入參數setsize理論上等於用戶配置的最大客戶端數目即可,但是爲了確保安全,這裏設置setsize等於最大客戶端數目加128。函數aeApiCreate內部調用epoll_create創建epoll,並初始化結構體eventLoop的字段apidata可以參考前面的文章關於eventLoop的創建

3、監聽端口

前面已經介紹了:1、初始化配置,2、加載並解析配置文件,3、初始化服務端內部變量,4、創建事件循環aeEventLoop。當完成這些操作後,Redis將創建socket並啓動監聽,同時創建對應的文件事件與時間事件並開始事件循環。下面將介紹以下幾個步驟:

  • 【5】、創建socket並啓動監聽;
  • 【6】、創建文件事件與時間事件;
  • 【7】、開啓循環;

3.1、創建socket並啓動監聽

用戶可以通過指令port配置socket的端口號,指令bind配置socket綁定IP地址;注意指令bind可配置多個IP地址,中間用空格隔開;創建socket時只需要循環所有IP地址即可。函數實現如下:

//入參分別是 端口 fds是數組指向所有socket文件描述符 count存儲socket數量
int listenToPort(int port, int *fds, int *count) {
    int j;

    /* Force binding of 0.0.0.0 if no bind address is specified, always
     * entering the loop if j == 0. */
    //如果沒有指定 IP ,設置爲 NULL
    if (server.bindaddr_count == 0) server.bindaddr[0] = NULL;
    //遍歷所有需要bind的 ip
    for (j = 0; j < server.bindaddr_count || j == 0; j++) {
        //如果爲空,用戶沒有指定,使用默認值,即綁定IPV4 又 綁定 IPV6
        if (server.bindaddr[j] == NULL) {
            int unsupported = 0;        
            //創建socket並啓動監聽,文件描述符存儲在fds數組作爲返回參數(IPV6)
            fds[*count] = anetTcp6Server(server.neterr,port,NULL,
                server.tcp_backlog);
            if (fds[*count] != ANET_ERR) {
                //設置socket 爲非阻塞
                anetNonBlock(NULL,fds[*count]);
                (*count)++;
            } else if (errno == EAFNOSUPPORT) {
                unsupported++;
                serverLog(LL_WARNING,"Not listening to IPv6: unsupported");
            }

            if (*count == 1 || unsupported) {              
                //創建socket並啓動監聽,文件描述符存儲在fds數組作爲返回參數(IPV4)
                fds[*count] = anetTcpServer(server.neterr,port,NULL,
                    server.tcp_backlog);
                if (fds[*count] != ANET_ERR) {
                    //設置socket 爲非阻塞
                    anetNonBlock(NULL,fds[*count]);
                    (*count)++;
                } else if (errno == EAFNOSUPPORT) {
                    unsupported++;
                    serverLog(LL_WARNING,"Not listening to IPv4: unsupported");
                }
            }
                 
            if (*count + unsupported == 2) break;
        } else if (strchr(server.bindaddr[j],':')) {
            //IP裏面有帶 : 肯定是IPV6
            //創建socket並啓動監聽,文件描述符存儲在fds數組作爲返回參數(IPV6)
            fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        } else {
            //如果沒有 : 而且還有 IP 那就是 IPV4
            /* Bind IPv4 address. */
            //創建socket並啓動監聽,文件描述符存儲在fds數組作爲返回參數(IPV4)
            fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        }
        if (fds[*count] == ANET_ERR) {
            serverLog(LL_WARNING,
                "Could not create server TCP listening socket %s:%d: %s",
                server.bindaddr[j] ? server.bindaddr[j] : "*",
                port, server.neterr);
                if (errno == ENOPROTOOPT     || errno == EPROTONOSUPPORT ||
                    errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
                    errno == EAFNOSUPPORT    || errno == EADDRNOTAVAIL)
                    continue;
            return C_ERR;
        }
        //設置socket 爲非阻塞
        anetNonBlock(NULL,fds[*count]);
        (*count)++;
    }
    return C_OK;
}

輸入的port表示用戶配置的端口號,server結構體的bindaddr_count字段存儲用戶配置的IP地址數目,bindaddr字段存儲用戶配置的所有IP地址。函數aneTcpServer實現了 socket 的創建,綁定,監聽流程。參數fdscount可以用作輸出參數,fds數組存儲創建的所有 socket 文件描述符,count存儲 socket 數目。

  • 注意:所有創建的socket都會設置爲非阻塞模式,原因在於Redis使用了IO多路複用模式,其要求socket獨寫必須是非阻塞的,函數anetNonBlock通過系統調用fcntl設置socket爲非阻塞模式。

3.2、創建文件事件與時間事件

上面已經完成了socket的創建以及端口的監聽,但是還需要創建相應的事件進行監聽,下面將會介紹文件事件和時間事件的創建。

	 //對IP socket 創建文件事件
    for (j = 0; j < server.ipfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
            {
                serverPanic(
                    "Unrecoverable error creating server.ipfd file event.");
            }
    }
    
     //對 tls 創建文件事件
    for (j = 0; j < server.tlsfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,
            acceptTLSHandler,NULL) == AE_ERR)
            {
                serverPanic(
                    "Unrecoverable error creating server.tlsfd file event.");
            }
    }
  • server結構體的ipfd_count字段存儲創建的監聽socket數目,ipfd數組存儲的是所有監聽socket文件描述符,需要遍歷所有的監聽socket,爲其創建對應的文件事件。可以看到監聽事件的處理函數爲acceptTcpHandler(後面的指令處理和這個函數有關),實現了socket連接請求的accept,以及客戶端對象的創建。
	//創建時間事件
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create event loop timers.");
        exit(1);
    }

3.3、開啓事件循環

前面幾個步驟已經完成了服務端的初始化工作,並且在指定的ipport開啓socket監聽等待客戶端連接請求,同時創建了文件事件時間事件;此時只需要開啓事件循環等待事件即可。

/**
 * 事件驅動的循環主入口
 * @param eventLoop
 */
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0; //設置停止標記爲(不停止)
    while (!eventLoop->stop) { //除非停止標誌被設置,不然循環不會停止
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);//函數不爲空,先執行阻塞函數
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);//執行事件
    }
}

事件處理主函數aeProcessEvents《Redis5源碼閱讀【8-命令處理生命週期-中】》已經介紹過,這裏需要重點關注函數beforesleep,它在每次事件循環開始,即Redis阻塞等待文件事件之前執行。函數beforesleep會執行一些不是很浪費時間的操作,如:集羣相關操作過期鍵的刪除操作向客戶端返回命令回覆等。

3.3.1、過期鍵刪除

前面提到,在aeMain->beforesleep中會觸發多種短時間操作,過期鍵操作就是其中一個。Redis過期鍵刪除有兩種策略:

  • 1、訪問數據庫時,校驗該鍵是否過期,如果過期則刪除;
  • 2、週期性刪除過期鍵,beforesleep函數與serverCron函數都會執行。

server結構體的active_expire_enabled字段表示是否開啓週期性刪除過期鍵策略,用戶可通過set active-expire 指令配置;masterhost字段存儲當前Redis服務器的master服務器的域名,如果爲NULL說明當前服務器不是某個服務器的slave。注意到這裏依然是調用函數activeExpireCycle執行過期鍵刪除,只是參數傳遞的是ACTIVE_EXPIRE_CYCLE_FAST表示快速過期刪除。

回顧一下前面activeExpireCycle的實現,函數計算出timelimit即函數最大執行時間,循環刪除過期鍵時會校驗函數執行時間是否超過此限制,超出則結束循環。顯然快速過期鍵刪除時只需要縮短timelimit即可,函數實現如下:

void activeExpireCycle(int type) {
// 省略。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
 	static int timelimit_exit = 0;      /* Time limit hit in previous call? */
    static long long last_fast_cycle = 0; /* When last fast cycle ran. */
//省略。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
	 //上次activeExpirCycle函數是否已經執行完畢(時間用完之間返回)
     if (!timelimit_exit &&
         server.stat_expired_stale_perc < config_cycle_acceptable_stale)
         return;
     //當前時間距離上次執行快速過期刪除是否已經超過2000微秒
     if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
         return;
//省略。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
   //快速過期鍵刪除時,函數執行時間不超過1000微秒
     if (type == ACTIVE_EXPIRE_CYCLE_FAST)
         timelimit = config_cycle_fast_duration; /* in microseconds. */

}

執行快速過期刪除有很多限制,當函數activeExpireCycle正在執行時直接返回;當上次執行快速過期鍵刪除的時間小於2000微秒時直接返回。timelimit_exit是聲明爲statictimelimit_exit的值爲1的時候,由此便可通過變量timelimit_exit判斷函數activeExpireCycle是否正在執行。變量last_fast_cycle也是聲明爲static。同時可以看到當執行快速過期刪除時,設置函數activeExpireCycle的最大執行時間爲1000微秒。

3.3.2、創建client

函數aeProcessEvents爲事件處理主函數,它首先查找最近發生的時間事件,調用epoll_wait阻塞等待文件事件的發生並設置超時事件;待epoll_wait返回時,處理觸發的文件事件;最後處理時間事件。步驟6中已經創建了文件事件,爲監聽socket的讀事件,事件處理函爲acceptTcpHandler,即當客戶端發起socket連接請求時,服務端會執行函數acceptTcpHandler處理。acceptTcpHandler函數主要做了以下三件事情:

  • 1、接受(accept)客戶端的連接請求;
  • 2、創建客戶端對象,並初始化對象各個字段;
  • 3、創建文件事件;

其中創建客戶端事件是通過函數createClient實現,conn爲接受客戶端連接請求後的socket封裝的connection

client *createClient(connection *conn) {
    client *c = zmalloc(sizeof(client));

    /* passing NULL as conn it is possible to create a non connected client.
     * This is useful since all the commands needs to be executed
     * in the context of a client. When commands are executed in other
     * contexts (for instance a Lua script) we need a non connected client. */
    if (conn) {
        //設置爲非阻塞
        connNonBlock(conn);
        //設置TCP_NODELAY
        connEnableTcpNoDelay(conn);
        //如果服務端配置了tcpkeepalive,則設置SO_KEEPALIVE
        if (server.tcpkeepalive)
            connKeepAlive(conn,server.tcpkeepalive);
        connSetReadHandler(conn, readQueryFromClient);
        connSetPrivateData(conn, c);
    }
 //初始化client結構體各字段
 }

爲了能使用I/O多路複用,這裏一樣使用connNonBlock設置socket爲非阻塞模式。
爲了提升網絡效率,服務端會使用Nagle算法,當應用層調用write函數發生數據的時候,TCP並不會一定立馬把數據發送響應給客戶端,根據Nagle算法的規定:

  • 1、當數據包大於一定限制的時候會立即發送;
  • 2、如果數據包中有FIN(TCP斷開連接)字段,則立即發送;
  • 3、如果設置了TCP_NODELAY後,則立即發送;

否則數據包將會等待200毫秒才進行發送,這裏爲了提高Redis響應客戶端的效率設置了TCP_NODELAY。同時爲了避免TCP握手帶來的開銷,這裏設置TCP的SO_KEEPALIVE,即TCP長連接。接收到客戶端請求後,服務端需要創建文件事件等待客戶端的命令請求,可以看到這裏的文件事件處理函數爲readQueryFromClient,當服務器接收到客戶端的命令請求時,會執行此函數。

4、總結

本篇文章基本圍繞着,服務端,初始化配置,讀取配置文件並啓動,直到啓動完成等待客戶端連接的一系列過程,可以用下圖表示整個流程過程:
在這裏插入圖片描述
其中服務器運行部分的本質其實是一個單線程的while循環,在每一個的循環都會取出相應的事件進行處理,圍繞着aeProcessEvents函數,有beforesleepaftersleep兩個函數,分別對於事件處理前的準備和事件處理後的後續工作,大家也可以思考一下,哪些邏輯內容應該放在beforesleep哪些內容應該放在aftersleep中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章