1.处理客户端请求
在server.c 的main方法中,在启动的时候针对网卡的各个ip会创建文件事件用于监听tcp请求。
tcp请求会交给acceptTcpHandler去处理。而readQueryFromClient就是用来处理文件读事件处理的。
对于做java的人来说,其实跟netty里对于连接事件,读事件,写事件监听与处理的模式一样。
2.处理读事件readQueryFromClient
方法所在地:networking.c文件中的readQueryFromClient方法
处理流程:
nread = read(fd, c->querybuf+qblen, readlen); 读取buffer数据
- nread为-1表示读取失败
- nread == 0 表示客户端连接已关闭
sdsIncrLen(c->querybuf,nread); c→querybuf是客户端传递过来的命令字符串sds,这里nread就是读取的命令长度,这里其实就是len进行修复processInputBufferAndReplicate 处理客户端命令
3.processInputBuffer 处理客户端命令
processInputBufferAndReplicate 中使用 processInputBuffer 来进行处理命令,replicationFeedSlavesFromMasterStream 是用来从节点进行同步的。
void processInputBufferAndReplicate(client *c) {
if (!(c->flags & CLIENT_MASTER)) {
processInputBuffer(c);
} else {
size_t prev_offset = c->reploff;
processInputBuffer(c);
size_t applied = c->reploff - prev_offset;
if (applied) {
replicationFeedSlavesFromMasterStream(server.slaves,
c->pending_querybuf, applied);
sdsrange(c->pending_querybuf,applied,-1);
}
}
}
4.processInputBuffer 处理命令
这里需要注意的是,请求处理分为3种
PROTO_REQ_INLINE 处理单行命令(将resp命令拆解为命令数组)PROTO_REQ_MULTIBULK 处理多行命令(将resp命令拆解为命令数组)processCommand 处理命令
strcasecmp(c->argv[0]->ptr,"quit") 校验是不是退出命令lookupCommand(c->argv[0]->ptr) 寻找命令- 校验命令参数的个数与传递过来的命令是否匹配
- 校验是否授权
- 校验可用内存
- 磁盘写权限校验
- 从节点存活校验
- 是否只有只读从节点校验
- 发布订阅命令集校验
- lua脚本校验
- 执行命令
queueMultiCommand 命令集处理call(c,CMD_CALL_FULL); 单命令处理
5.查找命令lookupCommand
struct redisCommand *lookupCommand(sds name) {
return dictFetchValue(server.commands, name);
}
server.commands 又是怎么来的呢?
server.commands = dictCreate(&commandTableDictType,NULL);
populateCommandTable();
在populateCommandTable 方法中,读取redisCommandTable 中的命令对象一一加入server.commands 中
void populateCommandTable(void) {
int j;
int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand);
for (j = 0; j < numcommands; j++) {
struct redisCommand *c = redisCommandTable+j;
char *f = c->sflags;
int retval1, retval2;
while(*f != '\0') {
switch(*f) {
case 'w': c->flags |= CMD_WRITE; break;
case 'r': c->flags |= CMD_READONLY; break;
case 'm': c->flags |= CMD_DENYOOM; break;
case 'a': c->flags |= CMD_ADMIN; break;
case 'p': c->flags |= CMD_PUBSUB; break;
case 's': c->flags |= CMD_NOSCRIPT; break;
case 'R': c->flags |= CMD_RANDOM; break;
case 'S': c->flags |= CMD_SORT_FOR_SCRIPT; break;
case 'l': c->flags |= CMD_LOADING; break;
case 't': c->flags |= CMD_STALE; break;
case 'M': c->flags |= CMD_SKIP_MONITOR; break;
case 'k': c->flags |= CMD_ASKING; break;
case 'F': c->flags |= CMD_FAST; break;
default: serverPanic("Unsupported command flag"); break;
}
f++;
}
retval1 = dictAdd(server.commands, sdsnew(c->name), c);
retval2 = dictAdd(server.orig_commands, sdsnew(c->name), c);
serverAssert(retval1 == DICT_OK && retval2 == DICT_OK);
}
}
6.单命令处理call(c,CMD_CALL_FULL)
核心代码:
dirty = server.dirty;
start = ustime();
c->cmd->proc(c);
duration = ustime()-start;
dirty = server.dirty-dirty;
其实就是调用了redisCommand的proc函数来处理命令了。
这里不得不说一下redisCommand结构,每个命令都封装在这个结构体中。可以看一下第5条,命令
struct redisCommand {
char *name;
redisCommandProc *proc;
int arity;
char *sflags;
int flags;
redisGetKeysProc *getkeys_proc;
int firstkey;
int lastkey;
int keystep;
long long microseconds, calls;
};
7.小结
这里解析到了执行redisCommand的proc函数来执行实际命令,那么接下来需要阅读的就是对各个命令源码的解析和数据结构的使用。今天就先到这了,下班~
|