文章目錄
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
函數初始化共享對象,例如前面提到的robj
和refcount
字段。執行命令時 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 的創建,綁定,監聽流程。參數fds
與count
可以用作輸出參數,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);
}
- 在《Redis5源碼閱讀【8-命令處理生命週期-中】》中提到,定時任務被抽象爲時間事件,且Redis只創建了一個時間事件,在服務端初始化時創建。此時間事件的處理函數爲
serverCron
,初次創建時1毫秒後就會被觸發。
3.3、開啓事件循環
前面幾個步驟已經完成了服務端的初始化工作,並且在指定的ip
和port
開啓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
是聲明爲static
當timelimit_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
函數,有beforesleep
和aftersleep
兩個函數,分別對於事件處理前的準備和事件處理後的後續工作,大家也可以思考一下,哪些邏輯內容應該放在beforesleep
哪些內容應該放在aftersleep
中。