从启动Redis服务器,到服务器可以接受外来客户端的网络连接这段时间,Redis需要执行一系列初始化操作。
整个初始化过程可以分为以下六个步骤:
以下各个小节将介绍Redis服务器初始化的各个步骤。
redis.h/redisServer
结构记录了和服务器相关的所有数据,这个结构主要包含以下信息:
- 奴隶信息
- 掌握信息
- 是否运行集群及相关选项。
- 调试信息选项
/ *服务器对象* /
struct redisServer {
/ * General * /
//配置文件路径
char * configfile ; / *绝对配置文件路径,或NULL * /
// serverCron()调用频率
int hz ; / * serverCron()以赫兹调用频率* /
//数据库对象
redisDb * db ;
//支持的命令列表
dict * commands ; / *命令表* /
//没有转化的命令
dict * orig_commands ; / *命令重命名前的命令表。* /
//事件
aeEventLoop * el ;
//每分钟增加一次
unsigned lruclock :22 ; / *每分钟时钟递增,对于LRU * /
unsigned lruclock_padding :10 ;
int shutdown_asap ; / * SHUTDOWN需要尽快* /
int activerehashing ; / * serverCron()中的增量重新散列* /
//验证密码
char * requirepass ; / *传递AUTH命令,或NULL * /
char * pidfile ; / * PID文件路径* /
int arch_bits ; / * 32或64取决于sizeof(长)* /
int cronloops ; / * cron函数运行的次数* /
char runid [ REDIS_RUN_ID_SIZE + 1 ]; / * ID在每个执行官总是不同的。* /
int sentinel_mode ; / *如果此实例是Sentinel,则为True。* /
/ * Networking * /
int port ; / * TCP侦听端口* /
int tcp_backlog ; / * TCP听()积压* /
炭 * bindaddr [ REDIS_BINDADDR_MAX ]; / *地址我们应该绑定到* /
int bindaddr_count ; / * server.bindaddr [] * /
char * unixsocket中的地址数; / * UNIX套接字路径* /
mode_t unixsocketperm ; / * UNIX套接字权限* /
int ipfd [ REDIS_BINDADDR_MAX ]; / * TCP套接字文件描述符* /
int ipfd_count ; / *在ipfd [] * /
int sofd中使用了插槽; / * Unix套接字文件描述符* /
int cfd [ REDIS_BINDADDR_MAX ]; / *集群总线监听套接字* /
int cfd_count ; 在CFD [] * / / *用于时隙
//连接客户端
列表 * 客户端; / *活跃客户
列表 * / list * clients_to_close ; / *客户端异步关闭* /
list * slave , * monitors ; / *从属列表和MONITOR * /
redisClient *current_client ; / *当前客户端,仅用于崩溃报告* /
int clients_paused ; / *如果客户端当前暂停,
则为 True * / mstime_t clients_pause_end_time ; / *我们撤消clients_paused * /
char neterr [ ANET_ERR_LEN ]的时间; / *用于anet.c * /错误缓冲器
字典 * migrate_cached_sockets ; / * MIGRATE缓存套接字* /
/ * RDB / AOF加载信息* /
int loading ; / *如果为true * /
off_t loading_total_bytes ,
我们正在从磁盘加载数据; off_t loading_loaded_bytes ;
time_t loading_start_time ;
off_t loading_process_events_interval_bytes ;
/ *快速指针经常查找命令* /
struct redisCommand * delCommand , * multiCommand , * lpushCommand , * lpopCommand ,
* rpopCommand ;
/ *字段仅用于统计* /
time_t stat_starttime ; / *服务器启动时间* /
long long stat_numcommands ; / *已处理命令数* /
long long stat_numconnections ; / *收到的连接数* /
long long stat_expiredkeys ; / *过期密钥数* /
long long stat_evictedkeys ; / *被驱逐的密钥数量(maxmemory)* /
long long stat_keyspace_hits ; / *成功查找密钥的次数* /
long long stat_keyspace_misses ; / *键的查找失败次数* /
size_t stat_peak_memory ; / *最大使用内存记录* /
long long stat_fork_time ; / *执行最新fork()* /
long long stat_rejected_conn所需的时间; / *由于maxclients * /
long long stat_sync_full而拒绝客户端; / *带有从站的完整resyncs的数量。* /
long long stat_sync_partial_ok ; / *已接受的PSYNC请求数。* /
long long stat_sync_partial_err ; / *未接受的PSYNC请求数。* /
//保存慢日志命令
列表 * slowlog ; / * SLOWLOG命令列表* /
long long slowlog_entry_id ; / * SLOWLOG当前条目ID * /
long long slowlog_log_slower_than ; / * SLOWLOG时间限制(记录)* /
unsigned long slowlog_max_len ; / * SLOWLOG记录的最大项目数* /
/ *以下两项用于跟踪
每秒操作*的
瞬时“负载” 。* / long long ops_sec_last_sample_time ; / *最后一个样本的时间戳(以ms为单位)* /
long long ops_sec_last_sample_ops; / * numcommands in last sample * /
long long ops_sec_samples [ REDIS_OPS_SEC_SAMPLES ];
int ops_sec_idx ;
/ *配置* /
int 详细程度; / * Loglevel in redis.conf * /
int maxidletime ; / *客户端超时(以秒为单位)* /
int tcpkeepalive ; / *如果非零则设置SO_KEEPALIVE。* /
int active_expire_enabled ; / *可以禁用以进行测试。* /
size_t client_max_querybuf_len ; / *限制客户端查询缓冲区长度* /
int dbnum ; / *已配置的DB总数* /
int daemonize ; / *如果作为守护进程运行,则为True * /
clientBufferLimitsConfig client_obuf_limits [REDIS_CLIENT_LIMIT_NUM_CLASSES ];
/ * AOF持久性* /
int aof_state ; / * REDIS_AOF_(ON | OFF | WAIT_REWRITE)* /
int aof_fsync ; / *种类的fsync()策略* /
char * aof_filename ; / * AOF文件的名称* /
int aof_no_fsync_on_rewrite ; / *如果重写是在prog中,请不要fsync。* /
int aof_rewrite_perc ; / *如果%增长> M并且...... * /
off_t aof_rewrite_min_size , 则重写AOF ; / * AOF文件至少为N个字节。* /
off_t aof_rewrite_base_size ; / *最新启动或重写时的AOF大小。* /
off_t aof_current_size ; / * AOF当前大小。* /
int aof_rewrite_scheduled ; / * BGSAVE终止后重写。* /
pid_t aof_child_pid ; / * PID如果重写过程* /
list * aof_rewrite_buf_blocks ; / *在AOF重写期间保持更改。* /
sds aof_buf ; / * AOF缓冲区,在进入事件循环之前写入* /
int aof_fd ; / *当前所选AOF文件的文件描述符* /
int aof_selected_db ; / *当前在AOF中选择的数据库* /
time_t aof_flush_postponed_start ; / *推迟AOF flush的UNIX时间* /
time_t aof_last_fsync ; / *最后一次fsync()* /
time_t的 UNIX时间aof_rewrite_time_last ; / *最后一次AOF重写运行所用的时间。* /
time_t aof_rewrite_time_start ; / *当前AOF重写开始时间。* /
int aof_lastbgrewrite_status ; / * REDIS_OK或REDIS_ERR * /
unsigned long aof_delayed_fsync ; / *延迟AOF fsync()计数器* /
int aof_rewrite_incremental_fsync ; / * fsync在重写时递增?* /
int aof_last_write_status ; / * REDIS_OK或REDIS_ERR * /
int aof_last_write_errno ; / *如果aof_last_write_status是ERR,则有效* /
/ * RDB持久性* /
long long dirty ; / *从上次保存更改DB * /
long long dirty_before_bgsave ; / *用于在失败的BGSAVE * /
pid_t rdb_child_pid 上恢复脏; / * RDB保存子的PID * /
struct saveparam * saveparams ; / *为RDB保存点数组* /
int saveparamslen ; / *保存点数* /
char * rdb_filename ; / * RDB文件的名称* /
int rdb_compression ; / *在RDB中使用压缩?* /
int rdb_checksum ; / *使用RDB校验和?* /
time_t lastsave ; / *上次成功保存的Unix时间* /
time_t lastbgsave_try ; / *上次尝试的Unix时间bgsave * /
time_t rdb_save_time_last ; / *上次RDB保存运行所用的时间。* /
time_t rdb_save_time_start ; / *当前RDB保存开始时间。* /
int lastbgsave_status ; / * REDIS_OK或REDIS_ERR * /
int stop_writes_on_bgsave_err ; / *不允许写入如果不能BGSAVE * /
/ * AOF /复制中的命令传播* /
redisOpArray also_propagate; / *传播的附加命令。* /
/ * Logging * /
char * logfile ; / *日志文件的路径* /
int syslog_enabled ; / *是否启用了syslog?* /
char * syslog_ident ; / * Syslog ident * /
int syslog_facility ; / * Syslog工具* /
/ *复制(master)* /
int slaveseldb ; / *复制输出中的最后一个SELECTed DB * /
long long master_repl_offset ; / *全局复制偏移量* /
int repl_ping_slave_period ; / * Master每N秒ping一次奴隶* /
char * repl_backlog ; / *部分同步的复制积压* /
long long repl_backlog_size ; / * Backlog循环缓冲区大小* /
long long repl_backlog_histlen ; / *积压实际数据长度* /
long long repl_backlog_idx ; / * Backlog循环缓冲区当前偏移量* /
long long repl_backlog_off ; / *
积压缓冲区
中第一个字节的复制偏移量。* / time_t repl_backlog_time_limit ; / *积压
释放
后没有奴隶的时间。* / time_t repl_no_slaves_since ; / *从那时起我们就没有奴隶。
仅在server.slaves len为0时有效。* /
int repl_min_slaves_to_write ; / *写入的最小从站数。* /
int repl_min_slaves_max_lag ; / * 奴隶的最大滞后写入。* /
int repl_good_slaves_count; / *滞后<= max_lag的从站数。* /
/ *复制(从属)* /
char * masterauth ; / * AUTH,密码为master * /
char * masterhost ; / * master * /
int masterport的主机名; / * master的端口* /
int repl_timeout ; / *主机空闲N秒后超时* /
redisClient * 主机; / *作为此slave * /
redisClient * cached_master的主服务器的客户端; / *缓存的主要重用于PSYNC。* /
int repl_syncio_timeout ; / *同步I / O调用超时* /
int repl_state ; / *如果实例是从属的复制状态* /
off_t repl_transfer_size ; / *在同步期间从主机读取的RDB的大小。* /
off_t repl_transfer_read ; / *在同步期间从主设备读取的RDB数量。* /
off_t repl_transfer_last_fsync_off ; / *上次fsync-ed时偏移。* /
int repl_transfer_s ; / * Slave - > Master SYNC socket * /
int repl_transfer_fd ; / * Slave - > Master SYNC临时文件描述符* /
char * repl_transfer_tmpfile ; / * Slave-> master SYNC临时文件名* /
time_t repl_transfer_lastio ; / *最新读取的Unix时间,用于超时* /
int repl_serve_stale_data ; / *在链接断开时提供过时的数据?* /
int repl_slave_ro ; / *奴隶只读?* /
time_t repl_down_since ; / *与master的链接
断开的Unix时间* / int repl_disable_tcp_nodelay ; / * SYNC后禁用TCP_NODELAY?* /
int slave_priority ; / *在INFO中报告并由Sentinel使用。* /
char repl_master_runid [ REDIS_RUN_ID_SIZE + 1 ]; / * PSYNC的主运行ID。* /
long long repl_master_initial_offset ; / *掌握PSYNC偏移量。* /
/ *复制脚本缓存。* /
dict * repl_scriptcache_dict ; / * SHA1所有从属都知道。* /
list * repl_scriptcache_fifo ; / *先进先出LRU驱逐。* /
int repl_scriptcache_size ; / *最大元素数。* /
/ *同步复制。* /
list * clients_waiting_acks ; / *等待WAIT命令的客户端。* /
int get_ack_from_slaves ; / *如果为true,我们发送REPLCONF GETACK。* /
/ *限制* /
unsigned int maxclients ; / *最大并发客户端数* /
unsigned long long maxmemory ; / *要使用的最大内存字节数* /
int maxmemory_policy ; / *关键驱逐政策* /
int maxmemory_samples ; / *随机抽样的精确度* /
/ *阻塞的客户端* /
unsigned int bpop_blocked_clients ; / *列表阻止的客户端数量* /
list * unblocked_clients ; / *在下一个循环之前解锁的客户端
列表 * / list * ready_keys ; / * BLPOP&co * /
/ *
的readyList结构列表排序参数 - qsort_r()仅在BSD下可用,因此我们 必须将此状态设为全局,以便将其传递给sortCompare()* /
int sort_desc ;
int sort_alpha ;
int sort_bypattern ;
int sort_store ;
/ * Zip结构配置,有关更多信息,请参阅redis.conf * /
size_t hash_max_ziplist_entries ;
size_t hash_max_ziplist_value ;
size_t list_max_ziplist_entries ;
size_t list_max_ziplist_value ;
size_t set_max_intset_entries ;
size_t zset_max_ziplist_entries ;
size_t zset_max_ziplist_value ;
time_t unixtime ; / *每个cron周期对Unix时间进行采样。* /
很 长的 mstime ; / *与'unixtime'相似,但分辨率为毫秒。* /
/ * Pubsub * /
dict * pubsub_channels ; / *将频道映射到订阅客户
列表 * / list * pubsub_patterns ; / * pubsub_patterns * /
int notify_keyspace_events的列表; / *通过Pub / Sub传播的事件。这是
REDIS_NOTIFY ...标志的xor。* /
/ * Cluster * /
int cluster_enabled ; / *群集是否已启用?* /
mstime_t cluster_node_timeout ; / *群集节点超时。* /
char * cluster_configfile ; / *群集自动生成的配置文件名。* /
struct clusterState * cluster ; / *集群的状态* /
int cluster_migration_barrier ; / *群集副本迁移障碍。* /
/ * Scripting * /
lua_State * lua ; / * Lua翻译。我们只为所有客户使用一个* /
redisClient * lua_client ; / *“假客户端”从Lua * /
redisClient * lua_caller 查询Redis ; / *现在运行EVAL的客户端,或者NULL * /
dict * lua_scripts ; / * SHA1的字典 - > Lua脚本* /
mstime_t lua_time_limit ; / *脚本超时(以毫秒为单位)* /
mstime_t lua_time_start ; / *脚本的开始时间,毫秒时间* /
int lua_write_dirty ; / *如果在
执行当前脚本
期间调用了写入命令,则为True 。* / int lua_random_dirty ; / *如果在
执行当前脚本
期间调用了随机命令,则为True 。* / int lua_timedout ; / *如果我们达到脚本
执行
的时间限制,则为True 。* / int lua_kill ; / *如果为true,则杀死脚本。* /
/ *断言和错误报告* /
char * assert_failed ;
char * assert_file ;
int assert_line ;
int bug_report_start ; / *如果已记录错误报告标题,则为True。* /
int watchdog_period ; / *软件看门狗周期,单位为ms。0 =关* /
};
程序创建一个的redisServer
结构的实例变量server
,调用函数initServerConfig()
,将server
的各个属性初始化为默认值。
当server
变量的初始化完成之后,程序进入服务器初始化的下一步:读入配置文件。
在初始化服务器的上一步中,程序为server
变量(也即是服务器状态)的各个属性设置了默认值,但这些默认值有时候并不是最合适的:
等等。
为了让使用者按自己的要求配置服务器,Redis允许用户在运行服务器时,提供相应的配置文件(配置文件)或者显示的选项(选项),Redis在server
初始化完变量之后,会读入配置文件和选项,然后根据这些配置来对server
变量的属性值做相应的修改:
如果单纯执行redis-server
命令,那么服务器以默认的配置来运行Redis。
另一方面,如果给Redis服务器送入一个配置文件,那么Redis将按配置文件的设置来更新服务器的状态。
比如说,通过命令,Redis会根据文件的内容来对服务器状态做相应的修改。redis-server /etc/my-redis.conf
my-redis.conf
除此之外,还可以显式地给服务器传入选项,直接修改服务器配置。
举个例子,通过命令,可以让Redis服务器端口变更为。redis-server --port 10086
10086
当然,同时使用配置文件和显式选项也是可以的,如果文件和选项有冲突的地方,那么优先使用选项所指定的配置值。
举个例子,如果运行命令,并且也指定了选项,那么服务器将优先使用(实际上是选项指定的值覆盖了配置文件中的值)。redis-server /etc/my-redis.conf --port 10086
my-redis.conf
port
--port 10086
其实在读入配置文件前,还要判断是不是sentinel,如果sentinel,还需要通过initSentinelConfig()和initSentinel()初始化,才通过resetServerSaveParams()重置param选项,通过loadServerConfig(configfile,options)读入配置文件和显选项。
Redis默认不以daemon进程的方式运行。
若服务器初始化进行到这一步时,程序将创建守护进程来运行Redis,并创建相应的pid文件。
在这一步,初始化程序完成两件事:
server
变量的数据结构子属性分配内存。为数据结构分配内存,并初始化这些数据结构,等同于对相应的功能进行初始化。
比如说,当为订阅与发布所需的链表分配内存之后,订阅与发布功能就处于就绪状态,随时可以为Redis所用了。
在这一步,initServer()完成的主要动作如下:
完成这一步之后,服务器redisAsciiArt()打印出Redis的ASCII LOGO,服务器版本等信息,表示所有功能模块已经就绪,可以等待被使用了:
_._
_.-``__'' - ._
_--``` `_。''-._ Redis 3.0.beta(7a47887b / 1)32位
。-``。-```。```\ / _。,_'' - ._
(',.-`|`,)以独立模式运行
|`-._`-...-`__...-。``-._ |'`_.-'| 港口:6379
| `-._`._ / _.-'| PID:6717
`-._` -._` - 。/ _.-'_.-'
|`-._`-._`-.__.-'_ .-'_ .-'|
| `-._`-._ _.-'_ .-'| http://redis.io
`-._` -._`-.__.-'_ .-'_.-'
|`-._`-._`-.__.-'_ .-'_ .-'|
| `-._`-._ _.-'_ .-'|
`-._` -._`-.__.-'_ .-'_.-'
`-._` -.__.-'_.-'
`-._ _.-'
`-.__.-”
虽然所有功能已经就绪,但这时服务器的数据库还是一片空白,程序还需要将服务器上一次执行时记录的数据载入到当前服务器中,服务器的初始化才算真正完成。
在这一步,如果不为sentinel,程序需要将持久化在RDB或者AOF文件里的数据,载入到服务器进程里面。
如果服务器有启用AOF功能的话,那么使用AOF文件来还原数据;否则,程序使用RDB文件来还原数据。
当执行完这一步时,服务器打印出一段载入完成信息:
[ 6717 ] 22 Feb 11 :59 :14.830 * 从磁盘加载的DB :0.068 秒
如果是集群,还要检查数据的一致性。
到了这一步,服务器的初始化已经完成,程序打开事件循环,开始接受客户端连接。
以下是服务器在这一步打印的信息:
[ 6717 ] 22 年2月 11 :59 :14.830 * 的 服务器 是 现在 准备 要 接受 连接 的 端口 6379