上一小节,分析了client的请求,这一小节主要看处理具体的命令的详细过程:
客户端指令解析完之后,需要执行该指令,执行指令的两个函数为processCommand与call函数,这两个函数除了单纯的执行指令外,还做了许多其他的工作,processCommand中调用了call函数,只分析call函数,其他的工作有点繁琐,等等仔细看完再写另起炉灶。
call函数中调用了具体的执行函数例如:selectCommand,randomkeyCommand,keysCommand等,这里也不一一详解。指令执行完之后,需要将得到的结果集返回给客户端,这部分是如何工作的,下面开始分析。
在networking.c中可以发现许多以addRelpy为前缀的函数名,这些函数都是用来处理各种不同类型的结果的,我们以典型的addReply函数为例,进行分析。
addReply函数
该函数第一步工作就是调用prepareClientToWrite函数为客户端创建一个写文件事件,事件的处理函数即将结果集发送给客户端的函数为sendReplyToClient.
/* This function is called every time we are going to transmit new data
* to the client. The behavior is the following:
*
* If the client should receive new data (normal clients will) the function
* returns REDIS_OK, and make sure to install the write handler in our event
* loop so that when the socket is writable new data gets written.
*
* If the client should not receive new data, because it is a fake client
* or a slave, or because the setup of the write handler failed, the function
* returns REDIS_ERR.
*
* Typically gets called every time a reply is built, before adding more
* data to the clients output buffers. If the function returns REDIS_ERR no
* data should be appended to the output buffers. */
int prepareClientToWrite(redisClient *c) {
if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;
if (c->fd <= 0) return REDIS_ERR; /* Fake client */
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE ||
c->replstate == REDIS_REPL_ONLINE) &&
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
return REDIS_OK;
}
第二步,就是根据相应的条件,将得到的结果rboj数据存储到buf中或者reply链表中。对于存储的策略:redis优先将数据存储在固定大小的buf中,也就是redisClient结构体buf[REDIS_REPLY_CHUNK_BYTES]里,默认大小为16K。如果有数据没有发送完或c->buf空间不足,就会放到c->reply链表里面,链表每个节点都是内存buf,后来的数据放入最后面。具体的处理函数为_addReplyToBuffer和_addReplyStringToList两个函数。
void addReply(redisClient *c, robj *obj) {
if (prepareClientToWrite(c) != REDIS_OK) return;
/* This is an important place where we can avoid copy-on-write
* when there is a saving child running, avoiding touching the
* refcount field of the object if it's not needed.
*
* If the encoding is RAW and there is room in the static buffer
* we'll be able to send the object to the client without
* messing with its page. */
if (obj->encoding == REDIS_ENCODING_RAW) {//字符串类型
//是否能将数据追加到c->buf中
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);//添加到c->reply链表中
} else if (obj->encoding == REDIS_ENCODING_INT) {//整数类型
/* Optimization: if there is room in the static buffer for 32 bytes
* (more than the max chars a 64 bit integer can take as string) we
* avoid decoding the object and go for the lower level approach. */
//追加到c->buf中
if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) {
char buf[32];
int len;
len = ll2string(buf,sizeof(buf),(long)obj->ptr);//整型转string
if (_addReplyToBuffer(c,buf,len) == REDIS_OK)
return;
/* else... continue with the normal code path, but should never
* happen actually since we verified there is room. */
}
obj = getDecodedObject(obj);//64位整数,先转换为字符串
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
decrRefCount(obj);
} else {
redisPanic("Wrong obj->encoding in addReply()");
}
}
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
/**
Server将数据发送给Client,有两种存储数据的缓冲形式,具体参见redisClient结构体
1、Response buffer
int bufpos; //回复
char buf[REDIS_REPLY_CHUNK_BYTES]; //长度为16 * 1024
2、list *reply;
unsigned long reply_bytes; Tot bytes of objects in reply list
int sentlen; 已发送的字节数
如果已经使用reply的形式或者buf已经不够存储,那么就将数据添加到list *reply中
否则将数据添加到buf中
*/
int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
size_t available = sizeof(c->buf)-c->bufpos;//计算出c->buf的剩余长度
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK;
/* If there already are entries in the reply list, we cannot
* add anything more to the static buffer. */
if (listLength(c->reply) > 0) return REDIS_ERR;
/* Check that the buffer has enough space available for this string. */
if (len > available) return REDIS_ERR;
//回复数据追加到buf中
memcpy(c->buf+c->bufpos,s,len);
c->bufpos+=len;
return REDIS_OK;
}
/**
1、如果链表长度为0: 新建一个节点并直接将robj追加到链表的尾部
2、链表长度不为0: 首先取出链表的尾部节点
1)、尾部节点的字符串长度 + robj中ptr字符串的长度 <= REDIS_REPLY_CHUNK_BYTES:
将robj->ptr追加到尾节点的tail->ptr后面
2)、反之: 新建一个节点并直接将robj追加到链表的尾部
*/
void _addReplyObjectToList(redisClient *c, robj *o) {
robj *tail;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
//链表长度为0
if (listLength(c->reply) == 0) {
incrRefCount(o);//增加引用次数
listAddNodeTail(c->reply,o);//添加到链表末尾
c->reply_bytes += zmalloc_size_sds(o->ptr); //计算o->ptr的占用内存大小
} else {
//取出链表尾中的数据
tail = listNodeValue(listLast(c->reply));
/* Append to this object when possible. */
// 如果最后一个节点所保存的回复加上新回复内容总长度小于等于 REDIS_REPLY_CHUNK_BYTES
// 那么将新回复追加到节点回复当中。
if (tail->ptr != NULL &&
sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)
{
c->reply_bytes -= zmalloc_size_sds(tail->ptr);
tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));
c->reply_bytes += zmalloc_size_sds(tail->ptr);
} else {//为新回复单独创建一个节点
incrRefCount(o);
listAddNodeTail(c->reply,o);
c->reply_bytes += zmalloc_size_sds(o->ptr);
}
}
// 如果突破了客户端的最大缓存限制,那么关闭客户端
asyncCloseClientOnOutputBufferLimitReached(c);
}
sendReplyToClient函数
终于到了最后一步,把c->buf与c->reply中的数据发送给客户端即可,发送同样使用的是最原始的write函数。发送完成之后,redis将当前客户端释放,并且删除写事件,代码比较简单,不详细解释。