1 processCommand主流程
int processCommand(redisClient *c)
- quit命令,addReply回复OK,然后设置客户端状态为REDIS_CLOSE_AFTER_REPLY,return REDIS_ERR
- c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr),查找命令
- 如果找不到cmd或者cmd的参数不符合要求,addReplyErrorFormat回复错误命令,调用flagTransaction(c)
- flagTransaction在REDIS_MULT状态下设置REDIS_DIRTY_EXEC,让之后的 EXEC 命令失败。
- 检查认证信息,检查错误时,addReply回复noautherr,调用flagTransaction(c)
- 如果开启了集群模式,那么可能需要进行转向判断,除非(命令的发送者是本节点的主节点,命令没有 key 参数)
- server.cluster->state != REDIS_CLUSTER_OK集群已下线,flagTransaction(c),addReplySds回复错误
- 不能执行多建命令时(两种情况,一种情况是不处于同一个slot,第二种情况是slot正在做迁移),flagTransaction(c),addReplySds回复错误
- 如果针对的槽和键不是本节点处理的,进行转向,根据error_code == REDIS_CLUSTER_REDIR_ASK?返回ASK或者MOVED命令,flagTransaction(c)。
- 如果设置了最大内存,那么检查内存是否超过限制
- 如果内存已超过限制,那么尝试删除过期键来释放内存freeMemoryIfNeeded
- 如果要执行的命令占用大量内存(cmd->flags & REDIS_CMD_DENYOOM),且freeMemoryIfNeeded的返回释放失败时,flagTransaction(c),并addReply向客户端返回内存错误。
- 如果这是一个主服务器(stop_writes_on_bgsave_err打开时),并且这个服务器之前执行 BGSAVE / AOF时发生了错误,flagTransaction(c),addReply返回bgsave错误,addReplySds返回aof错误
- 如果服务器没有足够多状态良好的slave(repl_min_slaves_to_write打开时),则拒绝写入命令,flagTransaction(c),addReply返回noreplicaserr错误
- 如果服务器是只读slave,且客户端不是主服务器,则拒绝写命令,addReply返回roslaveerr错误
- 如果客户端是发布订阅模式时(dictSize(c->pubsub_channels) > 0 || listLength(c->pubsub_patterns) > 0),则只能执行订阅和退订相关命令,否则addReplyError返回错误
- 如果(server.repl_serve_stale_data == 0)且不是一个slaveof命令,则说明masterhostdown,flagTransaction(c)并且addReply返回错误
- 如果服务器正在载入数据到数据库,那么只执行带有 REDIS_CMD_LOADING标识的命令,否则将出错,addReply返回loadingerr错误
- Lua 脚本超时,只允许执行限定的操作,比如 SHUTDOWN 和 SCRIPT KILL,flagTransaction(c)然后addReply返回slowscripterr错误
- 如果c->flags & REDIS_MULTI是MULTI状态,除 EXEC 、DISCARD 、MULTI 和 WATCH 命令之外,其他所有命令都会被入队到事务队列中,即调用queueMultiCommand(c)函数,并addReply回复queued
- 执行命令:call(c,REDIS_CALL_FULL),c->woff = server.master_repl_offset赋值偏移量
- 处理哪些借出了阻塞的键,server.ready_keys不为空时,调用handleClientsBlockedOnLists
2 freeMemoryIfNeeded流程
int freeMemoryIfNeeded()
- 计算出 Redis 目前占用的内存总数,但有两个方面的内存不会计算在内,1)从服务器的输出缓冲区的内存,2)AOF 缓冲区的内存
- 如果目前使用的内存大小比设置的 maxmemory 要小,那么无须执行进一步操作
- 如果占用内存比 maxmemory 要大,但是 maxmemory 策略为不淘汰,那么直接返回
- 计算需要释放多少字节的内存,mem_tofree = mem_used – server.maxmemory,初始化已释放内存的字节数为 0,mem_freed = 0
- while (mem_freed < mem_tofree)遍历,释放内存并记录被释放内存的字节数
- 遍历所有dbnum
- 如果策略是 allkeys-lru 或者 allkeys-random,那么淘汰的目标为所有数据库键
- 如果策略是 volatile-lru 、 volatile-random 或者 volatile-ttl,那么淘汰的目标为带过期时间的数据库键
- 如果使用的是随机策略(volatile-random and allkeys-random),那么从目标字典中随机选出键dictGetRandomKey
- 如果使用的是 LRU 策略(volatile-lru and allkeys-lru),那么从淘汰目标中选出 IDLE 时间最长的那个键
- 调用evictionPoolPopulate生成db->eviction_pool,然后从db->eviction_pool从后往前释放
- 若在evictionPoolPopulate的键能在dict中找到,从eviction_pool释放它,并赋值这个要删除的键bestkey
- 如果使用的是策略为 volatile-ttl,从sample个键中选出过期时间距离当前时间最接近的键,赋值给bestkey
- 删除bestkey
- 调用propagateExpire传播过期键(feedAppendOnlyFile传播到AOF中,replicationFeedSlaves传播到所有slave中)
- 计算删除键所释放的内存数量delta,并mem_freed += delta,对淘汰键的计数器增一,server.stat_evictedkeys++,keys_freed++
- notifyKeyspaceEvent,触发evicted的键时间通知或者键空间通知
- 如果有slave,强制刷新复制缓冲区(因为释放空间会花大量时间,并且会写复制缓冲区),我们释放后,立即刷新复制缓冲区,发送信息给从服务器。
3 call流程
call(c,REDIS_CALL_FULL)
- 如果可以的话,将命令发送给MONITOR,replicationFeedMonitors
- 记录执行前的flags,记录旧的dirty计数器值(上一次RDB持久化后,进行了多少次数据库的修改),记录开始执行时间,初始化server.also_propagate(用来记录需要传播的非本次命令的额外命令,这些命令是由alsoPropagate添加的(好像暂时没见到代码用))
- 执行实现函数c->cmd->proc(c)
- 不将从 Lua 中发出的命令放入 SLOWLOG ,也不进行统计,flags &= ~(REDIS_CALL_SLOWLOG | REDIS_CALL_STATS),其他情况如果有需要的话,将命令放入到SLOWLOG(server.slowlog)里(slowlogPushEntryIfNeeded)
- 更新命令的统计信息,c->cmd->microseconds加上耗时,c->cmd->calls++
- 当flags是REDIS_CALL_PROPAGATE时,如果c->flags & REDIS_FORCE_REPL,需要强制REDIS_PROPAGATE_REPL传播;如果c->flags & REDIS_FORCE_AOF,需要强制REDIS_PROPAGATE_AOF传播;计算dirty的增长,若dirty增长了,说明有写入,需要同时启用 REPL 和 AOF 传播(flags |= (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF)),最后调用propagate进行传播命令
- 恢复c->flags的状态到执行之前,因为call可能会递归执行
- 传播额外的命令(非当前的命令),即遍历server.also_propagate.numops,传播每个server.also_propagate.ops[j]->cmd命令,也是调用propagate函数进行传播,然后释放掉server.also_propagate
- server.stat_numcommands++服务执行额命令数增加。