结合redis设计与实现的redis源码学习-19-服务器(server.c)

Redis服务器负责与多个客户端建立网络连接,处理客户端发送三个的命令请求,在数据库中爆粗你客户单执行命令所产生的数据,并通过资源管理来维持服务器自身的运转。

命令请求的执行过程
当服务器与客户端的连接套接字因为客户端的写入而变得可读时,服务器将调用命令请求处理器来执行以下操作:

-1、读取套接字中协议格式的命令请求,并将其保存到客户端状态的输入缓冲区中;
-2、对输入缓冲区中的命令请求进行分析,提取出命令请求中包含的命令参数,以及命令参数的个数,然后分别将参数和参数个数保存到客户单状态的argv属性和argc属性里面;
-3、调用命令执行器,执行客户单指定的命令。
命令执行器做的第一件事就是根据客户单状态的argv[0]参数,在命令表中查找参数所指定的命令,并将命令保存到客户端状态的cmd属性中。

命令执行器的预备操作:

-1、检查客户端状态的cmd指针是否指向NULL,如果是的话,说明用户输入的命令名字找不到实现,返回错误;
-2、根据cmd属性指向的arity属性,检查命令参数是否正确,不正确返回错误;
-3、检查客户的江岸是否已经通过了身份验证,未通过身份验证的客户单只能执行auth命令,如果未通过客户端执行其他命令,返回错误;
-4,如果服务器打开了maxmemory功能,那么在执行命令之前,先检查副武器的内存占用情况,并在有需要的时候回收内存,从而使得接下来的命令可以顺利执行,如果回收失败,那么返回错误;
-5、如果服务器上一次执行bgsave出错,并且打开了stop-writes-on-bgsave-error功能,而且即将执行一个写命令,返回错误;
-6、如果客户端正在订阅频道,或者正在订阅模式,那么服务器智慧执行客户端发来的订阅相关命令;
-7、如果服务器正在进行数据载入,那么客户端发送的命令必须带有l标识才会执行,否则拒绝执行;
-8、如果服务器因为执行lua脚本而超时并进入阻塞状态,那么服务器智慧执行客户端发来的shutdown nosave命令和script kill命令,其他拒绝;
-9、如果客户端正在进行事务,那么服务器智慧执行客户端发来的事务相关命令,其他命令进队列;
-10、如果服务器打开了监视器功能,那么服务器会将要执行的命令和参数等信息发送给监视器。

调用命令实现函数

前面的操作已经将处理器注册到了cmd中,执行后将回复保存到客户端的输出缓冲区中。

执行后续工作

-1、如果服务器开启了慢查询日志功能,那么man插叙你日志模块会检查是否需要为刚刚执行完的命令请求添加一条新的慢查询日志。
-2、根据执行命令所耗费的时长,更新redisCommand的毫秒属性,并将命令的引用计数+1;
-3、如果服务器开启了AOF持久化功能,那么会将刚刚执行的命令请求写入到AOF缓冲区中;
-4、如果有其他从服务器正在复制当前这个服务器,那么服务器会将刚刚执行的命令传递给所有从服务器。

将命令回复发送给客户端

当客户单的套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端,发送完毕后,回复处理器会清空客户单状态的输出缓冲区,为处理下一个命令请求做好准备。

serverCron函数

redis服务器中的serverCron函数默认每隔100ms执行一次,这个函数负责管理服务器资源,并保持服务器自身的良好运转。
功能:
-1、更新服务器时间缓存:会以100ms的频率更新unixtime和mstime;
-2、更新lru时钟:服务器状态中的lruclouk属性保存了副武器的lru时钟,默认每10秒更新一次的时钟缓存,用于计算键的空转时长,通过与对象的lru对比;
-3、更新服务器每秒执行命令次数:100ms的频率执行;
-4、更新服务器内存峰值记录:stat_peak_memory记录了服务器内存峰值的大小,每次执行serverCron都会查看当前的内存数量进行对比,判断是否覆盖。
-5、处理SIGTERM信号:在启动时,redis会为服务器进程的sigterm信号关联处理器,处理器会在服务器接到信号时,打开shutdown_asap标识,每次运行serverCron,程序都会检测这个标识,判断知否关闭服务器。
-6、管理客户端资源:每次调用会检查超时客户端并释放,如果客户单在上一次执行命令后,输入缓冲区的大小超过了一定的长度,那么程序会释放客户单当前的输入缓冲区,并重新创建一个默认大小的输入缓冲区,防止消耗过多内存。
-7、管理数据库资源:每次执行会调用databasesCron,它会检查一部分数据库,删除过期键,在必要时对字典进行收缩操作;
-8、执行被延迟的BGREWRITEAOF:在执行bgsave期间,客户端发来bgrewriteaof命令,那么这个命令会延迟到bgsave命令执行完毕之后;
-9、检查持久化操作的运行状态:只要rdb_child_pid和aof_child_pid其中一个不为-1,就会执行一次wait3,检查子进程是否有信号发来服务器进程。其他情况会判断是否有bgrewriteaof被延迟了,或者自动保存条件被触发,或者aof重写条件被触发;
-10、将aof缓冲区的内容写入aof文件;
-11、关闭异步客户端;
-12、增加cronloops计数器,每次执行都会+1;

初始化服务器

-1、初始化服务器状态结构:设置服务器运行id,设置默认配置文件路径,设置默认服务器频率,设置服务器运行架构,设置默认端口号,设置默认rdb条件和aof条件,初始化lru时钟,创建爱你命令表;
-2、载入配置选项:命令行的参数,配置文件的设置;
-3、初始化服务器数据结构:创建客户端状态的链表,创建数据库数组,创建订阅字典,创建订阅模式的链表,更改慢查询日志的slowlog属性,更改lua属性; 为服务器设置进程信号处理器,创建爱你共享对象,打开服务器监听端口,为serverCron创建时间事件,如果aof打开,那么打开现有aof文件,否则创建,初始化服务器的后台I/O模块bio。

还原数据库状态

如果开启了aof,使用aof文件还原数据库状态,否则使用rdb文件。

执行事件循环

server.c,我在这里只写每个函数的功能,后面有时间再讲函数实现贴上来。

/* Low level logging. To use only for very big messages, otherwise serverLog() is to prefer. 低级别的日志,只用于非常大的消息,否则使用serverLog*/
void serverLogRaw(int level, const char *msg);

/* Like serverLogRaw() but with printf-alike support. This is the function that is used across the code. The raw version is only used in order to dump the INFO output on crash. 最终调用的是serverLogRaw,*/
void serverLog(int level, const char *fmt, ...) ;
/* Log a fixed message without printf-alike capabilities, in a way that is safe to call from a signal handler. We actually use this only for signals that are not fatal from the point of view of Redis. Signals that are going to kill the server anyway an* where we need printf-alike features are served by serverLog(). 记录一个没有类型printf功能的固定消息,以一种安全的方式从信号处理程序调用,我们实际上枝江这个用于从Redis的角度来看并非致命的信号,无论如何要杀死服务器的信号都是由serverLog服务的*/
void serverLogFromHandler(int level, const char *msg);

/* Return the UNIX time in microseconds 返回微秒时间*/
long long ustime(void);
/* Return the UNIX time in milliseconds 返回微秒时间,调用ustime*/
mstime_t mstime(void);

/* After an RDB dump or AOF rewrite we exit from children using _exit() instead of exit(), because the latter may interact with the same file objects used by the parent process. However if we are testing the coverage normal exit() is used in order to obtain the right coverage information. 在RDB转储或AOF重写之后,我们使用_exit()而不是exit()从子进程退出,因为后者可能与父进程使用相同的文件对象交互,但是如果我们正在测试的覆盖exit用于获得正确的覆盖信息*/
void exitFromChild(int retcode);

/*====================== Hash table type implementation  ==================== */
//释放val
void dictVanillaFree(void *privdata, void *val);
//释放list(val)
void dictListDestructor(void *privdata, void *val);
//对比key1和key2
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
//不区分大小写的版本用于命令查找表和其他取药区分大小写的非二进制安全标胶的地方
int dictSdsKeyCaseCompare(void *privdata, const void *key1,const void *key2);
//销毁字典对象val(引用计数-1)
void dictObjectDestructor(void *privdata, void *val);
//销毁字典sds(val)
void dictSdsDestructor(void *privdata, void *val);
//对标字典的两个对象,调用dictsdskeycompare
int dictObjKeyCompare(void *privdata, const void *key1,const void *key2);
//计算对象hash值
unsigned int dictObjHash(const void *key);
//计算sds哈希值
unsigned int dictSdsHash(const void *key);
//计算sds哈希
unsigned int dictSdsCaseHash(const void *key) ;
//对比对象值
int dictEncObjKeyCompare(void *privdata, const void *key1,const void *key2);
//计算编码对象哈希值
unsigned int dictEncObjHash(const void *key);
//返回字典是否需要resize
int htNeedsResize(dict *dict);

/* If the percentage of used slots in the HT reaches HASHTABLE_MIN_FILL we resize the hash table to save memory 如果字典中使用的槽的百分比达到HASHTABLE_MIN_FILL,我们要调整散列表的大小以节省内存*/
void tryResizeHashTables(int dbid);
/* Our hash table implementation performs rehashing incrementally while we write/read from the hash table. Still if the server is idle, the hash table will use two tables for a long time. So we try to use 1 millisecond of CPU time at every call of this function to perform some rehahsing.The function returns 1 if some rehashing was performed, otherwise 0 is returned. 我们的散列表实现在我们从哈希表写入/读取的同时递增执行重新散列,如果服务器空闲,哈希表将长时间使用两个表,所以我们是同在每次调用这个函数的时候使用1ms的cpu事件来执行一些rehash,如果实行了rehash返回1*/
int incrementallyRehash(int dbid);
/* This function is called once a background process of some kind terminates, as we want to avoid resizing the hash tables when there is a child in order to play well with copy-on-write (otherwise when a resize happens lots of memory pages are copied). The goal of this function is to update the ability for dict.c to resize the hash tables accordingly to the fact we have o not running childs. 这个函数是在某种类型的后台进程被终止的时候被调用的,因为我们希望避免在有子进程的时候调整hash表的大小,以便和写时复制一起使用,这个函数的目标是更新dict的能力来根据我们没有运行孩子的事实来调整散列表的大小*/
void updateDictResizePolicy(void) 

下面是核心serverCron啦:called every 100 ms

/* ======================= Cron: called every 100 ms ======================== */
/* Helper function for the activeExpireCycle() function. This function will try to expire the key that is stored in the hash table entry 'de' of the 'expires' hash table of a Redis database. If the key is found to be expired, it is removed from the database and 1 is returned. Otherwise no operation is performed and 0 is returned. When a key is expired, server.stat_expiredkeys is incremented. The parameter 'now' is the current time in milliseconds as is passed to the function to avoid too many gettimeofday() syscalls. 用于activeExpireCycle函数的辅助函数,该函数将尝试过期存储在Redis数据库的expires散列表的散列表象de中的秘钥,如果发现秘钥已经过期,从数据库中删除,返回1,否则返回0*/
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) ;
/* Try to expire a few timed out keys. The algorithm used is adaptive and will use few CPU cycles if there are few expiring keys, otherwise it will get more aggressive to avoid that too much memory is used by keys that can be removed from the keyspace. No more than CRON_DBS_PER_CALL databases are tested at every iteration. This kind of call is used when Redis detects that timelimit_exit is true, so there is more work to do, and we do it more incrementally from the beforeSleep() function of the event loop. Expire cycle type: If type is ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a "fast" expire cycle that takes no longer than EXPIRE_FAST_CYCLE_DURATION microseconds, and is not repeated again before the same amount of time. If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is executed, where the time limit is a percentage of the REDIS_HZ period as specified by the REDIS_EXPIRELOOKUPS_TIME_PERC define. 尝试过期几个超时键,所使用的的算法是自适应的,如果有很少的过期秘钥,将会使用少量的cpu周期,否则会更积极地比喵可以从秘钥空间中移除的秘钥使用太多的内存,每次跌倒中都不会超过CRON_DBS_PER_CALL数据库。这种调用在Redis检测到timelimit_exit为true时使用,所以还有更多的工作要做,而且我们从事事件循环的beforeSleep函数中增加了一些,过气循环类型*/
void activeExpireCycle(int type);
//返回时间
unsigned int getLRUClock(void);
/* Add a sample to the operations per second array of samples. 将样本添加到每秒操作样本数组中*/
void trackInstantaneousMetric(int metric, long long current_reading);
/* Return the mean of all the samples. 返回所有样本的平均值*/
long long getInstantaneousMetric(int metric);
/* Check for timeouts. Returns non-zero if the client was terminated. The function gets the current time in milliseconds as argument since it gets called multiple times in a loop, so calling gettimeofday() for each iteration would be costly without any actual gain. 检查超时,如果客户端被终止,则返回非0值,该函数以毫秒为单位获取当前时间作为参数,因为他在一个循环中被多次调用,所以每次迭代调用gettimeofday很不高效,没有任何实际增益*/
int clientsCronHandleTimeout(client *c, mstime_t now_ms);
/* The client query buffer is an sds.c string that can end with a lot of free space not used, this function reclaims space if needed. The function always returns 0 as it never terminates the client. 客户端查询缓冲区是一个sds.c字符串,可以以很多未使用的空闲空间结束,如果需要,该函数将回收空间,该函数始终返回0,因为它永远不会终止客户端*/
int clientsCronResizeQueryBuffer(client *c) 
//确保每次调用至少处理客户端的numclients/server.hz.由于这个函数被称为server.hz次/秒,我们相信在最坏情况下,我们在一秒内处理所有的客户单。
void clientsCron(void);
/* This function handles 'background' operations we are required to do incrementally in Redis databases, such as active key expiring, resizing, rehashing. 这个函数处理背景操作,我们需要在redis数据库中增量地执行操作,例如活动秘钥过期,调整大小,rehash等*/
void databasesCron(void);
/* We take a cached value of the unix time in the global state because with virtual memory and aging there is to store the current time in objects at every object access, and accuracy is not needed. To access a global var is a lot faster than calling time(NULL) 我们在全局状态下获取unix时间的缓存值,因为虚拟内存和老化会将当前时间存储在每个对象访问的对象中,并且不需要准确性,访问全局变量比调用时间快*/
void updateCachedTime(void) ;
/* This is our timer interrupt, called server.hz times per second. Here is where we do a number of things that need to be done asynchronously. For instance:这是我们的定时器终端,每秒钟叫server.hz次,这里是我们做一些需要异步完成的事情:
 - Active expired keys collection (it is also performed in a lazy way on  lookup).活跃的过期秘钥集合,懒惰执行
 * - Software watchdog.看门狗
 * - Update some statistic.更新统计信息
 * - Incremental rehashing of the DBs hash tables.对数据库哈希表进行增量rehash
 * - Triggering BGSAVE / AOF rewrite, and handling of terminated children.触发bgsave/aof重写,并处理终止的孩子
 * - Clients timeout of different kinds.不同类型的客户端超时
 * - Replication reconnection.复制重新连接
 * - Many more...
 * Everything directly called here will be called server.hz times per second, so in order to throttle execution of things we want to do less frequently a macro is used:在这里直接调用的所有东西都被成为server.hz次,所以为了截止执行的次数,我们不想经常使用一个宏 run_with_period(milliseconds) { .... }
 */
 //这个函数调用了上面的定时函数
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) ;
/* This function gets called every time Redis is entering the main loop of the event driven library, that is, before to sleep for ready file descriptors. 每次redis进入时间驱动库的主循环时,就在就绪文件描述符休眠之前都会调用此函数*/
void beforeSleep(struct aeEventLoop *eventLoop)

服务器初始化操作

/* =========================== Server initialization ======================== */
//创建共享对象
void createSharedObjects(void) ;
//初始化服务器配置/结构体
void initServerConfig(void);
/* Restart the server, executing the same executable that started this instance, with the same arguments and configuration file. The function is designed to directly call execve() so that the new server instance will retain the PID of the previous one.使用相同的参数和配置文件重新启动服务器,执行预启动此实例相同的可执行文件, 该函数被设计为直接调用execve,一变心的服务器实例保留前一个的pid
 * The list of flags, that may be bitwise ORed together, alter the behavior of this function:可能按位或的标志列表会改变次函数的行为
 * RESTART_SERVER_NONE              No flags.
 * RESTART_SERVER_GRACEFULLY        Do a proper shutdown before restarting. 
 * RESTART_SERVER_CONFIG_REWRITE    Rewrite the config file before restarting. On success the function does not return, because the process turns into a different process. On error C_ERR is returned. 在重新启动之前重写配置文件*/
int restartServer(int flags, mstime_t delay) 

/* This function will try to raise the max number of open files accordingly to the configured max number of clients. It also reserves a number of file descriptors (CONFIG_MIN_RESERVED_FDS) for extra operations of persistence, listening sockets, log files and so forth. If it will not be possible to set the limit accordingly to the configured max number of clients, the function will do the reverse setting server.maxclients to the value that we can actually handle. 此功能将尝试根据配置的最大客户端数量提高最大打开文件数,他还保留了一些文件描述符,用于额外的持久性操作,侦听套接字,日志文件等等,如果无法根据配置的最大客户端数量设置限制,则该功能会将server.maxclients设置为我们实际可以处理的值*/
void adjustOpenFilesLimit(void);
/* Check that server.tcp_backlog can be actually enforced in Linux according to the value of /proc/sys/net/core/somaxconn, or warn about it. 根据文件中的值检查server.tcp_backlog是否可以在linux中执行,或者警告他*/
void checkTcpBacklogSettings(void);

/* Initialize a set of file descriptors to listen to the specified 'port' binding the addresses specified in the Redis server configuration. The listening file descriptors are stored in the integer array 'fds' and their number is set in '*count'.The addresses to bind are specified in the global server.bindaddr array and their number is server.bindaddr_count. If the server configuration contains no specific addresses to bind, this function will try to bind * (all addresses) for both the IPv4 and IPv6 protocols. On success the function returns C_OK. On error the function returns C_ERR. For the function to be on error, at least one of the server.bindaddr addresses was impossible to bind, or no bind addresses were specified in the server configuration but the function is not able to bind * for at least one of the IPv4 or IPv6 protocols. 初始化一组文件描述符,以侦听绑定在redis服务器配置中指定的地址的指定端口,在监听文件描述符存储在整数数组fds中,其编号设置为*count。要绑定的地址再全局server.bindaddr数组中指定,其编号为server.bindaddr_count,如果服务器配置不包含特定的地址进行绑定,则次函数将尝试绑定ipv和ipv6协议的所有地址,成功返回C_OK,错误返回C_ERR。为了使函数出错,至少有一个serverbindaddr地址是不可能绑定的,或者在服务器配置中没有指定绑定地址,但是在该函数不能绑定至少一个ipv或ipv6*/
int listenToPort(int port, int *fds, int *count) 
/* 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. 通过info或其他方式重置我们通过CONFIG RESETSTAT重置的统计信息,该函数还用于在服务器启动时在initServer中初始这些字段。*/
void resetServerStats(void);
//初始化服务器
void initServer(void) ;
/* Populates the Redis Command Table starting from the hard coded list we have on top of redis.c file. 从redis.c文件听不的硬编码列表开始填充redis命令表*/
void populateCommandTable(void);
//重置命令表状态
void resetCommandTableStats(void);

/* ========================== Redis OP Array API ============================ */
//初始化redisop数组/命令结构数组
void redisOpArrayInit(redisOpArray *oa) ;
//像redisop数组增加元素
int redisOpArrayAppend(redisOpArray *oa, struct redisCommand *cmd, int dbid,robj **argv, int argc, int target);
//释放redisop数组
void redisOpArrayFree(redisOpArray *oa);

/* ====================== Commands lookup and execution ===================== */
//寻找命令结构体
struct redisCommand *lookupCommand(sds name);
//根据字符串寻找命令
struct redisCommand *lookupCommandByCString(char *s);
/* Lookup the command in the current table, if not found also check in the original table containing the original command names unaffected by redis.conf rename-command statement. This is used by functions rewriting the argument vector such as rewriteClientCommandVector() in order to set client->cmd pointer correctly even if the command was renamed. 查找当前表中的命令,如果没有发现,还检查包含原始命令名称的原始表,不收,redis.conf rename-command语句影响,重写参数向量的函数使用此函数,以便及时命令被重命名也能正确设置client->cmd指针*/
struct redisCommand *lookupCommandOrOriginal(sds name) ;
/* Propagate the specified command (in the context of the specified database id)to AOF and Slaves.将指定的命令传播给AOF和从属
 * flags are an xor between:标志是一个异或之间
 * + PROPAGATE_NONE (no propagation of command at all)不传播
 * + PROPAGATE_AOF (propagate into the AOF file if is enabled)传播给AOF
 * + PROPAGATE_REPL (propagate into the replication link) This should not be used inside commands implementation. Use instead alsoPropagate(), preventCommandPropagation(), forceCommandPropagation().
 */
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,int flags);
/* Used inside commands to schedule the propagation of additional commands after the current command is propagated to AOF / Replication. 'cmd' must be a pointer to the Redis command to replicate, dbid is the database ID the command should be propagated into. Arguments of the command to propagte are passed as an array of redis objects pointers of len 'argc', using the 'argv' vector. The function does not take a reference to the passed 'argv' vector, so it is up to the caller to release the passed argv (but it is usually stack allocated).  The function autoamtically increments ref count of passed objects, so the caller does not need to. 在当前命令传播到AOF/复制后,使用内部命令安排附加命令的传播。cmd必须是要复制的Redis命令的指针,dbid是命令应传播到数据库的标识,要传播的迷恋你高兴的参数作为len argc的redis对象指针传递,使用argv向量,该函数不会引用传递argv向量,所以由调用者来释放传递的argv。该函数自动递增传递对象的引用计数,因此调用者不需要*/
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,int target);
/* It is possible to call the function forceCommandPropagation() inside a Redis command implementation in order to to force the propagation of a specific command execution into AOF / Replication. 可以在redis命令实现中调用函数,以强制将特定命令执行传播到AOF/Replication中*/
void forceCommandPropagation(client *c, int flags) ;
/* Avoid that the executed command is propagated at all. This way we are free to just propagate what we want using the alsoPropagate() API. 避免执行的命令完全被传播,这样,我们可以自由地使用alsopropagate传播我们想要的东西*/
void preventCommandPropagation(client *c);
/* AOF specific version of preventCommandPropagation().AOF特定版本的函数 */
void preventCommandAOF(client *c);
/* Replication specific version of preventCommandPropagation(). 复制特定版本的函数*/
void preventCommandReplication(client *c) ;
/* Call() is the core of Redis execution of a command. The following flags can be passed:call是redis执行命令的核心,以下标志可以传递
 * CMD_CALL_NONE        No flags.无标志
 * CMD_CALL_SLOWLOG     Check command speed and log in the slow log if needed.检查命令速度并在需要时登录慢日志
 * CMD_CALL_STATS       Populate command stats.填充命令统计信息
 * CMD_CALL_PROPAGATE_AOF   Append command to AOF if it modified the dataset or if the client flags are forcing propagation.如果修改了数据及,或者客户端是标志强制传播,向AOF追加命令
 * CMD_CALL_PROPAGATE_REPL  Send command to salves if it modified the dataset or if the client flags are forcing propagation.发送命令,如果修改了数据集,或者客户端是标志强制传播
 * CMD_CALL_PROPAGATE   Alias for PROPAGATE_AOF|PROPAGATE_REPL.
 * CMD_CALL_FULL        Alias for SLOWLOG|STATS|PROPAGATE.
 * The exact propagation behavior depends on the client flags. Specifically:
 * 1. If the client flags CLIENT_FORCE_AOF or CLIENT_FORCE_REPL are set and assuming the corresponding CMD_CALL_PROPAGATE_AOF/REPL is set in the call flags, then the command is propagated even if the dataset was not affected by the command.在调用标志设置,命令被传播,即使数据集是不是由命令影响
 * 2. If the client flags CLIENT_PREVENT_REPL_PROP or CLIENT_PREVENT_AOF_PROP are set, the propagation into AOF or to slaves is not performed even if the command modified the dataset.传播到AOF或从属是不修改数据的
 * Note that regardless of the client flags, if CMD_CALL_PROPAGATE_AOF or CMD_CALL_PROPAGATE_REPL are not set, then respectively AOF or slaves propagation will never occur. Client flags are modified by the implementation of a given command using the following API:
 * forceCommandPropagation(client *c, int flags);
 * preventCommandPropagation(client *c);
 * preventCommandAOF(client *c);
 * preventCommandReplication(client *c);
 */
void call(client *c, int flags);
/* If this function gets called we already read a whole command, arguments are in the client argv/argc fields. processCommand() execute the command or prepare the server for a bulk read from If C_OK is returned the client is still alive and valid and other operations can be performed by the caller. Otherwise if C_ERR is returned the client was destroyed (i.e. after QUIT). 如果这个函数被调用,我们已经读取了一个完整的命令,参数在客户端argv/argc字段中,执行命令或准备服务器进行批量读取,如果返回C_OK,则客户端仍然有效并且可以由调用者执行其他操作,否则,如果返回C_ERR,则客户端被销毁*/
int processCommand(client *c) ;

/*================================== Shutdown =============================== */

/* Close listening sockets. Also unlink the unix domain socket if unlink_unix_socket is non-zero. 关闭监听套接字,如果不为0,还要取消unix域套接字的连接*/
void closeListeningSockets(int unlink_unix_socket);
//为shutdown做准备
int prepareForShutdown(int flags);


/*================================== Commands =============================== */

/* Return zero if strings are the same, non-zero if they are not. The comparison is performed in a way that prevents an attacker to obtain information about the nature of the strings just monitoring the execution time of the function.如果字符串相同则返回0,否则返回非0,比较的执行方式是防止攻击者只是监视函数的执行时间而获取关于字符串的性质的信息,
 Note that limiting the comparison length to strings up to 512 bytes we can avoid leaking any information about the password length and any possible branch misprediction related leak.将比较长度限制为最多512字节的字符串,我们可以避免泄露有关密码长度和任何可能的分支预测失误相关泄露的信息
 */
int time_independent_strcmp(char *a, char *b);
//认证命令
void authCommand(client *c);
/* The PING command. It works in a different way if the client is in in Pub/Sub mode. ping命令,如果客户端处于pub/sub模式,将以不同的方式工作*/
void pingCommand(client *c) ;
//返回命令
void echoCommand(client *c);
//时间命令,返回时间
void timeCommand(client *c);
/* Helper function for addReplyCommand() to output flags. 辅助函数的输出标志*/
int addReplyCommandFlag(client *c, struct redisCommand *cmd, int f, char *reply);
/* Output the representation of a Redis command. Used by the COMMAND command. 输出redis命令的表示形式,由command命令使用*/
void addReplyCommand(client *c, struct redisCommand *cmd);
/* COMMAND <subcommand> <args> */
void commandCommand(client *c) ;
/* Convert an amount of bytes into a human readable string in the form of 100B, 2G, 100M, 4K, and so forth. 将一定数量的字节转换为100b,2g,100m,4k等人类可读的字符串*/
void bytesToHuman(char *s, unsigned long long n);
/* Create the string returned by the INFO command. This is decoupled by the INFO command itself as we need to report the same information on memory corruption problems. 创建由INFO命令返回的字符串,这是由INFO命令本身分离的,因为我们需要报告关于内存损坏问题的相同信息*/
sds genRedisInfoString(char *section) ;
//调用genRedisInfoSring返回sds
void infoCommand(client *c) ;
//monitor命令
void monitorCommand(client *c);
/* ============================ Maxmemory directive  ======================== */

/* freeMemoryIfNeeded() gets called when 'maxmemory' is set on the config file to limit the max memory used by the server, before processing a command. The goal of the function is to free enough memory to keep Redis under the configured memory limit.当处理命令之前,在配置文件上设置maxmemory以限制服务器使用的最大内存时,会调用freeMemoryIfNeeded。该功能的目标是释放足够的内存,使redis保持在配置的内存限制之下
 The function starts calculating how many bytes should be freed to keep Redis under the limit, and enters a loop selecting the best keys to evict accordingly to the configured policy. If all the bytes needed to return back under the limit were freed the function returns C_OK, otherwise C_ERR is returned, and the caller should block the execution of commands that will result in more memory used by the server.该函数开始计算应该释放多少个字节以保持redis超出限制,并进入一个循环,选择最佳秘钥以相应地根据配置的策略逐出。楚国所有需要返回的字节都被释放,函数返回C_OK,否则返回C_ERR,调用者应该组织执行那些会导致服务器使用更多内存的命令
 * ------------------------------------------------------------------------
 LRU approximation algorithm Redis uses an approximation of the LRU algorithm that runs in constant memory. Every time there is a key to expire, we sample N keys (with N very small, usually in around 5) to populate a pool of best keys to evict of M keys (the pool size is defined by MAXMEMORY_EVICTION_POOL_SIZE).redis使用lru近似算法在恒定内存中运行的lru算法的近似值,每当有一个秘钥过期时,我们采样n个秘钥,以填充一个最佳秘钥池以驱逐m个秘钥。
 The N keys sampled are added in the pool of good keys to expire (the one with an old access time) if they are better than one of the current keys in the pool.被抽样的n个秘钥被添加到好的秘钥池中,如果他们比池中的当前秘钥中的一个好,则过期。
 After the pool is populated, the best key we have in the pool is expired. However note that we don't remove keys from the pool when they are deleted so the pool may contain keys that no longer exist.当池被填充之后,我们在池中拥有的最好的秘钥已经过期,但是请注意,我们不删除池中的秘钥时,他们被删除,所以池可能包含不再存在的秘钥,当我们试图驱逐一个秘钥,并且池中的所有条目都不存在时,我们再次填充它,这一次如果在整个数据库中至少有一个可以被驱逐的秘钥,那么我们将确保该池至少有一个可以被驱逐的秘钥。
 When we try to evict a key, and all the entries in the pool don't exist we populate it again. This time we'll be sure that the pool has at least one key that can be evicted, if there is at least one key that can be evicted in the whole database. */

/* Create a new eviction pool. 创建一个新的驱逐池*/
struct evictionPoolEntry *evictionPoolAlloc(void) ;
/* This is an helper function for freeMemoryIfNeeded(), it is used in order to populate the evictionPool with a few entries every time we want to expire a key. Keys with idle time smaller than one of the current keys are added. Keys are always added if there are free entries. We insert keys on place in ascending order, so keys with the smaller idle time are on the left, and keys with the higher idle time on the right. 这是freememoryifneeded的一个辅助函数,它用于每次我们要过期的时候用几个条目填充evictionPool,闲置事件小于当前键之一的键被添加。如果有空闲条目,键总是被添加,我们按剩余放置按键,空闲时间较短的按键在左侧,空闲时间较长的按键在右侧。*/
#define EVICTION_SAMPLES_ARRAY_SIZE 16
void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEntry *pool);
//在需要的时候释放内存
int freeMemoryIfNeeded(void);

main!

#ifdef __linux__
//获取当前系统的内存非配策略,它保存在/proc/sys/vm/overcommit_memory中
int linuxOvercommitMemoryValue(void) {
    FILE *fp = fopen("/proc/sys/vm/overcommit_memory","r");
    char buf[64];

    if (!fp) return -1;
    if (fgets(buf,64,fp) == NULL) {
        fclose(fp);
        return -1;
    }
    fclose(fp);

    return atoi(buf);
}
//内存警告
void linuxMemoryWarnings(void) {
    if (linuxOvercommitMemoryValue() == 0) {
        serverLog(LL_WARNING,"WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.");
    }
    if (THPIsEnabled()) {
        serverLog(LL_WARNING,"WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.");
    }
}
#endif /* __linux__ */
//创建pid文件
void createPidFile(void) {
    /* If pidfile requested, but no pidfile defined, use default pidfile path 如果请求pidfile,但没有定义pidfile,使用默认pidfile路径*/
    if (!server.pidfile) server.pidfile = zstrdup(CONFIG_DEFAULT_PID_FILE);

    /* Try to write the pid file in a best-effort way. 尝试以尽力而为的方式编写pid文件*/
    FILE *fp = fopen(server.pidfile,"w");
    if (fp) {
        fprintf(fp,"%d\n",(int)getpid());
        fclose(fp);
    }
}
//守护进程
void daemonize(void) {
    int fd;
    if (fork() != 0) exit(0); /* parent exits 父进程退出*/
    setsid(); /* create a new session 创建新会话*/

    /* Every output goes to /dev/null. If Redis is daemonized but the 'logfile' is set to 'stdout' in the configuration file it will not log at all.每个输出都转到/dev/null。如果redis被守护进程,但在配置文件中将logfile设置为stdout,则根本不会登陆 */
    if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        if (fd > STDERR_FILENO) close(fd);
    }
}
//版本
void version(void) {
    printf("Redis server v=%s sha=%s:%d malloc=%s bits=%d build=%llx\n",
        REDIS_VERSION,
        redisGitSHA1(),
        atoi(redisGitDirty()) > 0,
        ZMALLOC_LIB,
        sizeof(long) == 4 ? 32 : 64,
        (unsigned long long) redisBuildId());
    exit(0);
}
//告诉用法
void usage(void) ;
//打印工作模式
void redisAsciiArt(void);
//信号停止的句柄
static void sigShutdownHandler(int sig);
//设置信号句柄
void setupSignalHandlers(void);
/* Returns 1 if there is --sentinel among the arguments or if argv[0] is exactly "redis-sentinel". 如果参数中有-sentinel或者argv[0]就是redis-sentinel,返回1*/
int checkForSentinelMode(int argc, char **argv);

/* Function called at startup to load RDB or AOF file in memory. 启动时调用的函数在内存中加载RDB或AOF文件*/
void loadDataFromDisk(void);
//超出内存的句柄
void redisOutOfMemoryHandler(size_t allocation_size) ;
//设置处理器名称
void redisSetProcTitle(char *title) ;
//启动模式
int redisSupervisedUpstart(void) ;
int redisSupervisedSystemd(void) ;
//根据模式调用方法
int redisIsSupervised(int mode);
//wonderful
int main(int argc, char **argv) {
    struct timeval tv;
    int j;

    /* We need to initialize our libraries, and the server configuration. 我们需要初始化我们的库和服务器配置*/
#ifdef INIT_SETPROCTITLE_REPLACEMENT
    spt_init(argc, argv);
#endif
    setlocale(LC_COLLATE,"");//设置本地字符串整理
    zmalloc_enable_thread_safeness();//支持线程安全
    zmalloc_set_oom_handler(redisOutOfMemoryHandler);//设置内存超出句柄
    srand(time(NULL)^getpid());//种子是事件或上pid
    gettimeofday(&tv,NULL);//获取时间
    dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());//设置hash函数种子
    server.sentinel_mode = checkForSentinelMode(argc,argv);//判断哨兵模式
    initServerConfig();//初始化服务器设置

    /* Store the executable path and arguments in a safe place in order to be able to restart the server later. 将可执行文件路径和参数存储在安全的地方,以便稍后能够重新启动服务器*/
    server.executable = getAbsolutePath(argv[0]);
    server.exec_argv = zmalloc(sizeof(char*)*(argc+1));
    server.exec_argv[argc] = NULL;
    for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);

    /* We need to init sentinel right now as parsing the configuration file in sentinel mode will have the effect of populating the sentinel data structures with master nodes to monitor. 我们现在需要出示话哨兵,因为解析哨兵模式下的配置文件会影响到主节点要监控的哨兵数据结构*/
    if (server.sentinel_mode) {
        initSentinelConfig();//设置哨兵端口
        initSentinel();//初始化哨兵结构体
    }

    /* Check if we need to start in redis-check-rdb mode. We just execute the program main. However the program is part of the Redis executable so that we can easily execute an RDB check on loading errors. 检查我们是否需要以redis-check-rdb模式启动,我们只是执行程序main,但是该程序是redis可执行文件的一部分,因此我们可以轻松地执行RDB检查加载错误*/
    if (strstr(argv[0],"redis-check-rdb") != NULL)
        redis_check_rdb_main(argc,argv);//执行了这个会直接退出,因为命令是之间测rdb文件

    if (argc >= 2) {
        j = 1; /* First option to parse in argv[] 解析第一个参数*/
        sds options = sdsempty();
        char *configfile = NULL;

        /* 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);
            }
        }

        /* First argument is the config file name? 第一个参数是配置文件名称*/
        if (argv[j][0] != '-' || argv[j][1] != '-') {
            configfile = argv[j];
            server.configfile = getAbsolutePath(configfile);
            /* Replace the config file in server.exec_argv with its absoulte path. 将server.exec_argv中的配置文件替换为其他绝对路径*/
            zfree(server.exec_argv[j]);
            server.exec_argv[j] = zstrdup(server.configfile);
            j++;
        }

        /* 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 file name is parsed, if any. 所有其他选项都被解析并从概念上追加到配置文件中,例如--port 6380会在解析实际文件名后生成字符串 port 6380\n来解析*/
        while(j != argc) {
            if (argv[j][0] == '-' && argv[j][1] == '-') {
                /* Option name */
                if (!strcmp(argv[j], "--check-rdb")) {
                    /* Argument has no options, need to skip for parsing. 参数没有选项,跳过解析*/
                    j++;
                    continue;
                }
                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++;
        }
        if (server.sentinel_mode && configfile && *configfile == '-') {
            serverLog(LL_WARNING,
                "Sentinel config from STDIN not allowed.");
            serverLog(LL_WARNING,
                "Sentinel needs config file on disk to save state.  Exiting...");
            exit(1);
        }
        resetServerSaveParams();//初始化停止选项
        loadServerConfig(configfile,options);//加载服务器配置文件
        sdsfree(options);
    } else {
        serverLog(LL_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
    }

    server.supervised = redisIsSupervised(server.supervised_mode);//设置监督模式
    int background = server.daemonize && !server.supervised;//判断是否后台运行
    if (background) daemonize();//启动守护进程

    initServer();//初始化服务器
    if (background || server.pidfile) createPidFile();//创建pidfile
    redisSetProcTitle(argv[0]);//设置名称
    redisAsciiArt();
    checkTcpBacklogSettings();//检查tcp后台日志

    if (!server.sentinel_mode) {
        /* Things not needed when running in Sentinel mode. 这些在哨兵模式下不需要*/
        serverLog(LL_WARNING,"Server started, Redis version " REDIS_VERSION);
    #ifdef __linux__
        linuxMemoryWarnings();
    #endif
        loadDataFromDisk();
        if (server.cluster_enabled) {
            if (verifyClusterConfigWithData() == C_ERR) {//校验集群配置数据
                serverLog(LL_WARNING,
                    "You can't have keys in a DB different than DB 0 when in "
                    "Cluster mode. Exiting.");
                exit(1);
            }
        }
        if (server.ipfd_count > 0)
            serverLog(LL_NOTICE,"The server is now ready to accept connections on port %d", server.port);
        if (server.sofd > 0)
            serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
    } else {
        sentinelIsRunning();//启动哨兵
    }

    /* Warning the user about suspicious maxmemory setting. 警告用户有关可疑的最大内存设置*/
    if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
        serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
    }

    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeMain(server.el);//事件dispatch,在终止程序前一直在这里
    aeDeleteEventLoop(server.el);//删除事件循环
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值