顺风车运营研发团队 闫昌
一. Redis编译安装时指定参数, 防止gdb时被优化, 在make时, 增加参数noopt
make noopt
二. 客户端通讯协议
1.客户端与服务端的通讯协议是建立在TCP之上的
2.Redis指定了RESP(Redis SerializationProtocol, Redis序列化协议)实现客户端与服务端的正常交互
3.命令格式: *<参数数量>rn$ <参数1的字节数量>\r\n参数1\r\n$<参数2的字节数量>rn参数2rn
例如: set hello world *3rn$5\r\nset\r\n$5rnworldrn
4.返回结果
状态回复: 在RESP中第一个字节为"+"
错误回复: 在RESP中第一个字节为"-"
整数回复: 在RESP中第一个字符为":"
字符串回复: 在RESP中第一个字符为"$"
多条字符串回复: 在RESP中第一个字节为"*"
redis-cli只能看到最终的执行结果, 因为redis-cli本身就是按照RESP进行了结果解析, 所以看不到中间结果
三. set命令执行流程: set hello world
1.redis-server启动后, 会进行server.c的main主函数里
2.当初始化服务端参数之后, 会进入aeMain函数里
3.aeMain函数里while循环调用aeProcessEvents, aeProcessEvents里调用aeApiPoll等待epoll_wait返回可用fd
4.当有客户端连接请求过来之后, 会调用networking.c的createClient函数, 此函数里调用aeCreateFileEvent, 将第四个参数指定为: readQueryFromClient
5.当客户端连接成功之后, aeApiPoll返回可用的fd后, 进入aeProcessEvents的for (j = 0; j < numevents; j++)循环, 在此例中, 进入rfileProc函数, 此时的rfileProc为上一步的readQueryFromClient方法
6.readQueryFromClient函数, ---- networking.c
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
client *c = (client*) privdata;
int nread, readlen;
size_t qblen;
...
readlen = PROTO_IOBUF_LEN;//16K
...
...
qblen = sdslen(c->querybuf);//此时的值为0
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);//给querybuf分配16K的空间
nread = read(fd, c->querybuf+qblen, readlen);//从fd中读取16K的内容, 此时的fd为客户端向服务端的socket套接字
if (nread == -1) {
if (errno == EAGAIN) {//errno是全局变量, 在read函数后生成的变量, 此变量用一次之后就会消失, 此时EAGAIN表示读取失败
return;
} else {
serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno));
freeClient(c);
return;
}
} else if (nread == 0) {
serverLog(LL_VERBOSE, "Client closed connection");
freeClient(c);
return;
} else if (c->flags & CLIENT_MASTER) {
c->pending_querybuf = sdscatlen(c->pending_querybuf,
c->querybuf+qblen,nread);
}
sdsIncrLen(c->querybuf,nread);//扩展querybuf的长度, 使其长度为读取出的命令的长度
c->lastinteraction = server.unixtime;
....
....
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);
}
}
}
- processInputBuffer函数: ---- networking.c
void processInputBuffer(client *c) {
server.current_client = c;
while(sdslen(c->querybuf)) {
......
......
if (c->argc == 0) {
resetClient(c);
} else {
if (processCommand(c) == C_OK) {//这里进入执行命令函数时
if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
c->reploff = c->read_reploff - sdslen(c->querybuf);
}
if (!(c->flags & CLIENT_BLOCKED) || c->btype != BLOCKED_MODULE)
resetClient(c);
}
if (server.current_client == NULL) break;
}
}
server.current_client = NULL;
}
8.processCommand函数(server.c)的最后调用了call(server.c)方法
9.call方法里调用了c->cmd->proc方法, 而这个方法就是t_string.c的setCommand方法
c->cmd->proc(c);
10.setCommand方法(t_string.c)里调用了setGenericCommand方法
11.setGenericCommand方法调用了setKey方法
12.setKey方法调用了dbAdd或dbOverwrite方法, 将key和val设置到redis数据库
四. 执行流程