集群请求命令处理
在Redis的命令处理函数processCommand
(server.c)中有对集群节点的处理,满足以下条件时进入集群节点处理逻辑中:
- 启用了集群模式,通过
server.cluster_enabled
判断 - 发送命令的节点不是主节点
- 收到的命令中包含了key参数或者命令是EXEC,EXEC命令与MULTI结合使用,用于执行事务
条件三的判断条件有些绕,!cmdHasMovableKeys(c->cmd) && c->cmd->firstkey == 0
意味着命令中没有key参数,c->cmd->proc != execCommand
表示当前命令不是EXEC,然后对(!cmdHasMovableKeys(c->cmd) && c->cmd->firstkey == 0 && c->cmd->proc != execCommand)
整体做了取反操作,那么看以下两种情况:
- 如果命令中带有Key,那么
!cmdHasMovableKeys(c->cmd)
就已返回false,又因为对整体做了取反操作,所以条件成立,意味着收到命令中带有Key时需要执行重定向处理 - 如果收到的命令是EXEC,
c->cmd->proc != execCommand
返回false,对整体取反变成true,所以条件也成立,意味着收到EXEC命令的时候执行重定向处理
1 | int processCommand(client *c) { |
MULTI命令的处理
上面说到,如果是EXEC
命令时,也会进入到集群节点处理逻辑,EXEC
命令一般与MULTI
结合使用,用于执行事务。比如以下例子中,使用MULTI
开启事务,执行对a账户增1,b账户减1的操作,可以看到返回结果为QUEUED
,命令被缓存起来,直到执行EXEC
命令,Redis才开始提交命令:
1 | 127.0.0.1:6379> MULTI |
由于集群也需要对EXEC
命令处理,所以先看一下MULTI
命令的处理逻辑,MULTI
命令对应的执行函数为multiCommand
,可以看到它在处理的时候为客户端设置了CLIENT_MULTI
标记:
1 | void multiCommand(client *c) { |
在Redis的命令处理函数中可以找到对CLIENT_MULTI
的处理逻辑,如果客户端标记中有CLIENT_MULTI,并且当前命令不是EXEC、DISCARD、MULTI、WATCH和RESET,将调用queueMultiCommand
函数,对命令进行缓存:
1 | int processCommand(client *c) { |
MULTI命令结构体定义
在客户端结构体定义中,可以看到使用了multiState
缓存MULTI命令:
1 | // 客户端 |
multiState
MULTI命令对应的结构体为multiState
,multiState
中使用了multiCmd
结构体来缓存具体的命令:
1 | typedef struct multiState { |
MULTI命令的缓存
queueMultiCommand
对MULTI命令缓存的处理在queueMultiCommand函数中,它在multi.c文件中定义:
- 将
multiCmd
加入到缓存数组c->mstate.commands
中,对命令进行缓存 - 将当前命令的内容设置到
multiCmd
中
1 | /* 将当前命令加入到MULTI命令中 */ |
查询节点
getNodeByQuery
getNodeByQuery函数用于根据KEY查询数据所在的节点,处理逻辑如下:
如果是EXEC命令,从客户端获取multiState,multiState中缓存了MULTI命令,如果不是MULTI命令,而是单个命令,同样使用multiState来存放命令,之后就可以统一使用multiState来获取请求中的命令
根据命令的个数进行遍历,处理每一个命令
(1)从命令中获取key的个数,处理每一个key
(2)查询每一个key所在的slot
(3)如果处理的是第一个key,根据所属slot获取所在的节点,记为
n
,有以下三种情况: 情况一:未获取到节点(有可能节点已下线但是还未更新状态),记录错误信息为
CLUSTER_REDIR_DOWN_UNBOUND
,表示key未绑定到slot,返回NULL 情况二:可以查找到节点,并且是当前节点自己,但是key所属slot正在做数据迁出操作(从当前节点迁出),此时将
migrating_slot
置为1 情况三:可以查找到节点,并且不是当前节点自己,但是key所属slot正在迁入到当前节点,此时将
importing_slot
置为1(4)如果处理的不是第一个key,判断当前key所属的slot是否与第一个key的slot一致:
情况一:如果不一致,表示不同的key所属的slot不同,将error_code置为
CLUSTER_REDIR_CROSS_SLOT
,返回NULL 情况二:如果一致,将
multiple_keys
置为1,表示请求中有多个KEY,做一个标记(5)根据
migrating_slot
和importing_slot
的值判断key所属slot是否正在迁出或者迁入,迁出意味着key对应的数据正在从当前节点迁出到其他节点,迁入意味着key对应的数据正在迁入到当前节点,由于数据未迁移完毕,所以这两种情况都需要检查key是否在当前节点的数据库中,如果不在意味着当前节点没有该key的数据,需要记录缺失的KEY的数量,missing_keys
增1根据第二步查询后的结果,进行如下处理:
未查找到节点,也就是
n
为空,返回当前节点自己当前节点不处于正常状态(CLUSTER_OK)
(1)如果未开启allow_reads_when_down(在节点下线时允许读),error_code置为
CLUSTER_REDIR_DOWN_STATE
,并返回NULL(2)当前命令中有写标记,error_code置为
CLUSTER_REDIR_DOWN_RO_STATE
,并返回NULL(3)非以上两种情况,表示开启了allow_reads_when_down,并且是读操作,所以当前节点依旧可以处理请求,继续往下执行
如果数据正在迁出或者正在迁入,并且当前命令是MIGRATE数据迁移的命令,返回当前节点
如果key所在slot数据正在从当前节点迁出,并且当前节点数据库中有缺失的key,error_code置为
CLUSTER_REDIR_ASK
并返回迁出到的那个节点如果key所在slot正在迁入到当前节点,并且当前命令是ASK ,此时如果请求中有多个KEY并且当前节点存在缺失的KEY,表示有些key不在当前节点,error_code置为
CLUSTER_REDIR_UNSTABLE
返回NULL,否则返回当前节点即可如果客户端有只读标记、 当前命令不是写命令、当前节点是从节点并且它的主节点是根据key所属slot查找到的节点,返回当前节点,因为从节点数据是从master节点同步的,而master节点正是要查找的节点,从节点也可以处理读请求
如果查询到的节点不是当前节点,将error_code置为
CLUSTER_REDIR_MOVED
,表示数据已经移动到其他节点,此时返回key所属slot对应的实际节点
1 | clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *error_code) { |
根据Key从DB中查询Value
redisDb
Redis数据库对应的结构体定义为redisDb,里面有个字典类型的对象,存储键值对数据:
1 | typedef struct redisDb { |
lookupKeyRead
lookupKeyRead函数用于从redisDb中根据key查找数据,最终是调用lookupKey函数完成的,根据Key从字典中查找并返回value:
1 | robj *lookupKeyRead(redisDb *db, robj *key) { |
集群重定向
clusterRedirectClient
clusterRedirectClient用于集群重定向处理,在getNodeByQuery函数中,根据查询节点的情况对error_code设置了不同的值,在clusterRedirectClient函数中可以看到对error_code的判断,根据error_code的不同,向客户端响应不同的内容:
- 如果error_code是
CLUSTER_REDIR_CROSS_SLOT
,表示请求中有多个KEY,但是KEY所属slot不在同一个slot中 - 如果error_code是
CLUSTER_REDIR_UNSTABLE
,表示请求中有多个KEY并且在一个slot,但是数据可能正在迁入或迁出的过程中,节点中有缺失的KEY,slot处于一个不稳定的状态 - 如果error_code是
CLUSTER_REDIR_DOWN_STATE
,表示节点处于下线状态 - 如果error_code是
CLUSTER_REDIR_DOWN_RO_STATE
,表示节点处于下线状态,只接收读命令 - 如果error_code是
CLUSTER_REDIR_DOWN_UNBOUND
,标识key未绑定到节点,也就是根据key所属slot未查询到节点 - 如果error_code是
CLUSTER_REDIR_MOVED
或者CLUSTER_REDIR_ASK
:CLUSTER_REDIR_MOVED
表示key所属slot已从当前节点迁出,此时向客户端响应MOVED命令并将迁出后slot以及所在节点ip和端口返回CLUSTER_REDIR_ASK
表示key所属slot正在从当前节点迁出的过程中,请求中的key有可能一部分还未迁出,一部分已经迁出完毕,此时向客户端返回ASK命令,并将slot以及迁出到的目标节点的ip和端口返回
1 | void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) { |
参考
极客时间 - Redis源码剖析与实战(蒋德钧)
zhaiguanjie-Redis源码剖析
Redis版本:redis-6.2.5