listTypeTryConversion:尝试将列表对象转换成linkedlist编码
void listTypeTryConversion(robj *subject, robj *value) {
// 确保 subject 为 ZIPLIST 编码
if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
if (sdsEncodedObject(value) &&
// 看字符串是否过长
sdslen(value->ptr) > server.list_max_ziplist_value)
// 将编码转换为双端链表
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
}
listTypePush:将value添加到列表对象的头部或尾部
void listTypePush(robj *subject, robj *value, int where) {
/* Check if we need to convert the ziplist */
// 是否需要转换编码?
listTypeTryConversion(subject,value);
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
// ZIPLIST
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
// 取出对象的值,因为 ZIPLIST 只能保存字符串或整数
value = getDecodedObject(value);
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
decrRefCount(value);
// 双端链表
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
if (where == REDIS_HEAD) {
listAddNodeHead(subject->ptr,value);
} else {
listAddNodeTail(subject->ptr,value);
}
incrRefCount(value);
// 未知编码
} else {
redisPanic("Unknown list encoding");
}
}
listTypePop:弹出一个对象
robj *listTypePop(robj *subject, int where) {
robj *value = NULL;
// ZIPLIST
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
// 决定弹出元素的位置
int pos = (where == REDIS_HEAD) ? 0 : -1;
p = ziplistIndex(subject->ptr,pos);
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
// 为被弹出元素创建对象
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else {
value = createStringObjectFromLongLong(vlong);
}
/* We only need to delete an element when it exists */
// 从 ziplist 中删除被弹出元素
subject->ptr = ziplistDelete(subject->ptr,&p);
}
// 双端链表
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = subject->ptr;
listNode *ln;
if (where == REDIS_HEAD) {
ln = listFirst(list);
} else {
ln = listLast(list);
}
// 删除被弹出节点
if (ln != NULL) {
value = listNodeValue(ln);
incrRefCount(value);
listDelNode(list,ln);
}
// 未知编码
} else {
redisPanic("Unknown list encoding");
}
// 返回节点对象
return value;
}
listTypeLength:返回节点的数量
unsigned long listTypeLength(robj *subject) {
// ZIPLIST
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
return ziplistLen(subject->ptr);
// 双端链表
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
return listLength((list*)subject->ptr);
// 未知编码
} else {
redisPanic("Unknown list encoding");
}
}
pushGenericCommand:根据where将value添加到头部或尾部
void pushGenericCommand(redisClient *c, int where) {
int j, waiting = 0, pushed = 0;
// 取出列表对象
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
// 如果列表对象不存在,那么可能有客户端在等待这个键的出现
int may_have_waiting_clients = (lobj == NULL);
if (lobj && lobj->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr);
return;
}
// 将列表状态设置为就绪
if (may_have_waiting_clients) signalListAsReady(c,c->argv[1]);
// 遍历所有输入值,并将它们添加到列表中
for (j = 2; j < c->argc; j++) {
// 编码值
c->argv[j] = tryObjectEncoding(c->argv[j]);
// 如果列表对象不存在,那么创建一个,并关联到数据库
if (!lobj) {
lobj = createZiplistObject();
dbAdd(c->db,c->argv[1],lobj);
}
// 将值推入到列表
listTypePush(lobj,c->argv[j],where);
pushed++;
}
// 返回添加的节点数量
addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
// 如果至少有一个元素被成功推入,那么执行以下代码
if (pushed) {
char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
}
server.dirty += pushed;
}
void lpushCommand(redisClient *c) {
pushGenericCommand(c,REDIS_HEAD);
}
void rpushCommand(redisClient *c) {
pushGenericCommand(c,REDIS_TAIL);
}
pushxGenericCommand:将值插入到某个值的前面或后面
void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
robj *subject;
listTypeIterator *iter;
listTypeEntry entry;
int inserted = 0;
// 取出列表对象
if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,subject,REDIS_LIST)) return;
// 执行的是 LINSERT 命令
if (refval != NULL) {
// 看保存值 value 是否需要将列表编码转换为双端链表
listTypeTryConversion(subject,val);
/* Seek refval from head to tail */
// 在列表中查找 refval 对象
iter = listTypeInitIterator(subject,0,REDIS_TAIL);
while (listTypeNext(iter,&entry)) {
if (listTypeEqual(&entry,refval)) {
// 找到了,将值插入到节点的前面或后面
listTypeInsert(&entry,val,where);
inserted = 1;
break;
}
}
listTypeReleaseIterator(iter);
if (inserted) {
/* Check if the length exceeds the ziplist length threshold. */
// 查看插入之后是否需要将编码转换为双端链表
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert",
c->argv[1],c->db->id);
server.dirty++;
} else {
/* Notify client of a failed insert */
// refval 不存在,插入失败
addReply(c,shared.cnegone);
return;
}
// 执行的是 LPUSHX 或 RPUSHX 命令
} else {
char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
listTypePush(subject,val,where);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
server.dirty++;
}
addReplyLongLong(c,listTypeLength(subject));
}
blpopCommand:阻塞弹出命令
void blpopCommand(redisClient *c) {
blockingPopGenericCommand(c,REDIS_HEAD);
}
blockingPopGenericCommand:遍历所有输入的列表键,只要有一个列表键可以pop,就返回,否则如果都不能pop,就阻塞
void blockingPopGenericCommand(redisClient *c, int where) {
robj *o;
mstime_t timeout;
int j;
// 取出 timeout 参数
if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout,UNIT_SECONDS)
!= REDIS_OK) return;
// 遍历所有列表键
for (j = 1; j < c->argc-1; j++) {
// 取出列表键
o = lookupKeyWrite(c->db,c->argv[j]);
// 有非空列表?
if (o != NULL) {
if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr);
return;
} else {
// 非空列表
if (listTypeLength(o) != 0) {
/* Non empty list, this is like a non normal [LR]POP. */
char *event = (where == REDIS_HEAD) ? "lpop" : "rpop";
// 弹出值
robj *value = listTypePop(o,where);
redisAssert(value != NULL);
// 回复客户端
addReplyMultiBulkLen(c,2);
// 回复弹出元素的列表
addReplyBulk(c,c->argv[j]);
// 回复弹出值
addReplyBulk(c,value);
decrRefCount(value);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,
c->argv[j],c->db->id);
// 删除空列表
if (listTypeLength(o) == 0) {
dbDelete(c->db,c->argv[j]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
c->argv[j],c->db->id);
}
signalModifiedKey(c->db,c->argv[j]);
server.dirty++;
/* Replicate it as an [LR]POP instead of B[LR]POP. */
// 传播一个 [LR]POP 而不是 B[LR]POP
rewriteClientCommandVector(c,2,
(where == REDIS_HEAD) ? shared.lpop : shared.rpop,
c->argv[j]);
return;
}
}
}
}
/* If we are inside a MULTI/EXEC and the list is empty the only thing
* we can do is treating it as a timeout (even with timeout 0). */
// 如果命令在一个事务中执行,那么为了不产生死等待
// 服务器只能向客户端发送一个空回复
if (c->flags & REDIS_MULTI) {
addReply(c,shared.nullmultibulk);
return;
}
/* If the list is empty or the key does not exists we must block */
// 所有输入列表键都不存在,只能阻塞了
blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL);
}
blockForKeys:阻塞客户端
void blockForKeys(redisClient *c, robj **keys, int numkeys, mstime_t timeout, robj *target) {
dictEntry *de;
list *l;
int j;
// 设置阻塞状态的超时和目标选项
c->bpop.timeout = timeout;
// target 在执行 RPOPLPUSH 命令时使用
c->bpop.target = target;
if (target != NULL) incrRefCount(target);
// 关联阻塞客户端和键的相关信息
for (j = 0; j < numkeys; j++) {
/* If the key already exists in the dict ignore it. */
// c->bpop.keys 是一个集合(值为 NULL 的字典)
// 它记录所有造成客户端阻塞的键
// 以下语句在键不存在于集合的时候,将它添加到集合
if (dictAdd(c->bpop.keys,keys[j],NULL) != DICT_OK) continue;
incrRefCount(keys[j]);
/* And in the other "side", to map keys -> clients */
// c->db->blocking_keys 字典的键为造成客户端阻塞的键
// 而值则是一个链表,链表中包含了所有被阻塞的客户端
// 以下程序将阻塞键和被阻塞客户端关联起来
de = dictFind(c->db->blocking_keys,keys[j]);
if (de == NULL) {
// 链表不存在,新创建一个,并将它关联到字典中
int retval;
/* For every key we take a list of clients blocked for it */
l = listCreate();
retval = dictAdd(c->db->blocking_keys,keys[j],l);
incrRefCount(keys[j]);
redisAssertWithInfo(c,keys[j],retval == DICT_OK);
} else {
l = dictGetVal(de);
}
// 将客户端填接到被阻塞客户端的链表中
listAddNodeTail(l,c);
}
blockClient(c,REDIS_BLOCKED_LIST);
}
signalListAsReady:如果有客户端因为这个键被阻塞,他会被放到ready_keys 中保存
void signalListAsReady(redisClient *c, robj *key) {
readyList *rl;
/* No clients blocking for this key? No need to queue it. */
// 没有客户端被这个键阻塞,直接返回
if (dictFind(c->db->blocking_keys,key) == NULL) return;
/* Key was already signaled? No need to queue it again. */
// 这个键已经被添加到 ready_keys 中了,直接返回
if (dictFind(c->db->ready_keys,key) != NULL) return;
/* Ok, we need to queue this key into server.ready_keys. */
// 创建一个 readyList 结构,保存键和数据库
// 然后将 readyList 添加到 server.ready_keys 中
rl = zmalloc(sizeof(*rl));
rl->key = key;
rl->db = c->db;
incrRefCount(key);
listAddNodeTail(server.ready_keys,rl);
/* We also add the key in the db->ready_keys dictionary in order
* to avoid adding it multiple times into a list with a simple O(1)
* check.
*
* 将 key 添加到 c->db->ready_keys 集合中,防止重复添加
*/
incrRefCount(key);
redisAssert(dictAdd(c->db->ready_keys,key,NULL) == DICT_OK);
}
handleClientsBlockedOnLists: 对所有被阻塞在某个客户端的 key 来说,只要这个 key 被执行了某种 PUSH 操作 那么这个 key 就会被放到 serve.ready_keys 去。 这个函数会遍历整个 serve.ready_keys 链表, 并将里面的 key 的元素弹出给被阻塞客户端, 从而解除客户端的阻塞状态。
void handleClientsBlockedOnLists(void) {
// 遍历整个 ready_keys 链表
while(listLength(server.ready_keys) != 0) {
list *l;
/* Point server.ready_keys to a fresh list and save the current one
* locally. This way as we run the old list we are free to call
* signalListAsReady() that may push new elements in server.ready_keys
* when handling clients blocked into BRPOPLPUSH. */
// 备份旧的 ready_keys ,再给服务器端赋值一个新的
l = server.ready_keys;
server.ready_keys = listCreate();
while(listLength(l) != 0) {
// 取出 ready_keys 中的首个链表节点
listNode *ln = listFirst(l);
// 指向 readyList 结构
readyList *rl = ln->value;
/* First of all remove this key from db->ready_keys so that
* we can safely call signalListAsReady() against this key. */
// 从 ready_keys 中移除就绪的 key
dictDelete(rl->db->ready_keys,rl->key);
/* If the key exists and it's a list, serve blocked clients
* with data. */
// 获取键对象,这个对象应该是非空的,并且是列表
robj *o = lookupKeyWrite(rl->db,rl->key);
if (o != NULL && o->type == REDIS_LIST) {
dictEntry *de;
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
// 取出所有被这个 key 阻塞的客户端
de = dictFind(rl->db->blocking_keys,rl->key);
if (de) {
list *clients = dictGetVal(de);
int numclients = listLength(clients);
while(numclients--) {
// 取出客户端
listNode *clientnode = listFirst(clients);
redisClient *receiver = clientnode->value;
// 设置弹出的目标对象(只在 BRPOPLPUSH 时使用)
robj *dstkey = receiver->bpop.target;
// 从列表中弹出元素
// 弹出的位置取决于是执行 BLPOP 还是 BRPOP 或者 BRPOPLPUSH
int where = (receiver->lastcmd &&
receiver->lastcmd->proc == blpopCommand) ?
REDIS_HEAD : REDIS_TAIL;
robj *value = listTypePop(o,where);
// 还有元素可弹出(非 NULL)
if (value) {
/* Protect receiver->bpop.target, that will be
* freed by the next unblockClient()
* call. */
if (dstkey) incrRefCount(dstkey);
// 取消客户端的阻塞状态
unblockClient(receiver);
// 将值 value 推入到造成客户度 receiver 阻塞的 key 上
if (serveClientBlockedOnList(receiver,
rl->key,dstkey,rl->db,value,
where) == REDIS_ERR)
{
/* If we failed serving the client we need
* to also undo the POP operation. */
listTypePush(o,value,where);
}
if (dstkey) decrRefCount(dstkey);
decrRefCount(value);
} else {
// 如果执行到这里,表示还有至少一个客户端被键所阻塞
// 这些客户端要等待对键的下次 PUSH
break;
}
}
}
// 如果列表元素已经为空,那么从数据库中将它删除
if (listTypeLength(o) == 0) dbDelete(rl->db,rl->key);
/* We don't call signalModifiedKey() as it was already called
* when an element was pushed on the list. */
}
/* Free this item. */
decrRefCount(rl->key);
zfree(rl);
listDelNode(l,ln);
}
listRelease(l); /* We have the new list on place at this point. */
}
}