顺风车运营研发团队 闫昌
一. Redis编译安装时指定参数, 防止gdb时被优化, 在make时, 增长参数nooptredis
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中第一个字节为"*"app
redis-cli只能看到最终的执行结果, 由于redis-cli自己就是按照RESP进行告终果解析, 因此看不到中间结果
三. set命令执行流程: set hello worldsocket
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); } } }
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)方法oop
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数据库spa
四. 执行流程unix