void spopCommand(redisClient *c) { robj *set, *ele; int64_t llele; int encoding; if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,set,REDIS_SET)) return; encoding = setTypeRandomElement(set,&ele,&llele); if (encoding == REDIS_ENCODING_INTSET) { addReplyBulkLongLong(c,llele); set->ptr = intsetRemove(set->ptr,llele,NULL); } else { addReplyBulk(c,ele); setTypeRemove(set,ele); } if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]); touchWatchedKey(c->db,c->argv[1]); server.dirty++; }
void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) { dictEntry *de; long seconds; if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return; seconds -= offset; de = dictFind(c->db->dict,key->ptr); if (de == NULL) { addReply(c,shared.czero); return; } /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past * should never be executed as a DEL when load the AOF or in the context * of a slave instance. * * Instead we take the other branch of the IF statement setting an expire * (possibly in the past) and wait for an explicit DEL from the master. */ if (seconds <= 0 && !server.loading && !server.masterhost) { robj *aux; redisAssert(dbDelete(c->db,key)); server.dirty++; /* Replicate/AOF this as an explicit DEL. */ aux = createStringObject("DEL",3); rewriteClientCommandVector(c,2,aux,key); decrRefCount(aux); touchWatchedKey(c->db,key); addReply(c, shared.cone); return; } else { time_t when = time(NULL)+seconds; setExpire(c->db,key,when); addReply(c,shared.cone); touchWatchedKey(c->db,key); server.dirty++; return; } }
void moveCommand(redisClient *c) { robj *o; redisDb *src, *dst; int srcid; /* Obtain source and target DB pointers */ src = c->db; srcid = c->db->id; if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { addReply(c,shared.outofrangeerr); return; } dst = c->db; selectDb(c,srcid); /* Back to the source DB */ /* If the user is moving using as target the same * DB as the source DB it is probably an error. */ if (src == dst) { addReply(c,shared.sameobjecterr); return; } /* Check if the element exists and get a reference */ o = lookupKeyWrite(c->db,c->argv[1]); if (!o) { addReply(c,shared.czero); return; } /* Try to add the element to the target DB */ if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) { addReply(c,shared.czero); return; } incrRefCount(o); /* OK! key moved, free the entry in the source DB */ dbDelete(src,c->argv[1]); server.dirty++; addReply(c,shared.cone); }
void zremrangebyscoreCommand(redisClient *c) { double min; double max; long deleted; robj *zsetobj; zset *zs; if ((getDoubleFromObjectOrReply(c, c->argv[2], &min, NULL) != REDIS_OK) || (getDoubleFromObjectOrReply(c, c->argv[3], &max, NULL) != REDIS_OK)) return; if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,zsetobj,REDIS_ZSET)) return; zs = zsetobj->ptr; deleted = zslDeleteRangeByScore(zs->zsl,min,max,zs->dict); if (htNeedsResize(zs->dict)) dictResize(zs->dict); if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]); if (deleted) touchWatchedKey(c->db,c->argv[1]); server.dirty += deleted; addReplyLongLong(c,deleted); }
void lremCommand(redisClient *c) { robj *subject, *obj; obj = c->argv[3] = tryObjectEncoding(c->argv[3]); int toremove = atoi(c->argv[2]->ptr); int removed = 0; listTypeEntry entry; subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero); if (subject == NULL || checkType(c,subject,REDIS_LIST)) return; /* Make sure obj is raw when we're dealing with a ziplist */ if (subject->encoding == REDIS_ENCODING_ZIPLIST) obj = getDecodedObject(obj); listTypeIterator *li; if (toremove < 0) { toremove = -toremove; li = listTypeInitIterator(subject,-1,REDIS_HEAD); } else { li = listTypeInitIterator(subject,0,REDIS_TAIL); } while (listTypeNext(li,&entry)) { if (listTypeEqual(&entry,obj)) { listTypeDelete(&entry); server.dirty++; removed++; if (toremove && removed == toremove) break; } } listTypeReleaseIterator(li); /* Clean up raw encoded object */ if (subject->encoding == REDIS_ENCODING_ZIPLIST) decrRefCount(obj); if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]); addReplyLongLong(c,removed); if (removed) signalModifiedKey(c->db,c->argv[1]); }
void popGenericCommand(redisClient *c, int where) { robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; robj *value = listTypePop(o,where); if (value == NULL) { addReply(c,shared.nullbulk); } else { char *event = (where == REDIS_HEAD) ? "lpop" : "rpop"; addReplyBulk(c,value); decrRefCount(value); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id); if (listTypeLength(o) == 0) { notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", c->argv[1],c->db->id); dbDelete(c->db,c->argv[1]); } signalModifiedKey(c->db,c->argv[1]); server.dirty++; } }
void rpoplpushCommand(redisClient *c) { robj *sobj, *value; if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,sobj,REDIS_LIST)) return; if (listTypeLength(sobj) == 0) { addReply(c,shared.nullbulk); } else { robj *dobj = lookupKeyWrite(c->db,c->argv[2]); if (dobj && checkType(c,dobj,REDIS_LIST)) return; value = listTypePop(sobj,REDIS_TAIL); rpoplpushHandlePush(c,c->argv[2],dobj,value); /* listTypePop returns an object with its refcount incremented */ decrRefCount(value); /* Delete the source list when it is empty */ if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]); touchWatchedKey(c->db,c->argv[1]); server.dirty++; } }
void sremCommand(redisClient *c) { robj *set; int j, deleted = 0; if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,set,REDIS_SET)) return; for (j = 2; j < c->argc; j++) { if (setTypeRemove(set,c->argv[j])) { deleted++; if (setTypeSize(set) == 0) { dbDelete(c->db,c->argv[1]); break; } } } if (deleted) { signalModifiedKey(c->db,c->argv[1]); server.dirty += deleted; } addReplyLongLong(c,deleted); }
int expireIfNeeded(redisDb *db, robj *key) { mstime_t when = getExpire(db,key);//微秒 mstime_t now; if (when < 0) return 0; /* No expire for this key */ /* Don't expire anything while loading. It will be done later. */ if (server.loading) return 0; /* If we are in the context of a Lua script, we claim that time is * blocked to when the Lua script started. This way a key can expire * only the first time it is accessed and not in the middle of the * script execution, making propagation to slaves / AOF consistent. * See issue #1525 on Github for more information. */ now = server.lua_caller ? server.lua_time_start : mstime(); /* If we are running in the context of a slave, return ASAP: * the slave key expiration is controlled by the master that will * send us synthesized DEL operations for expired keys. * * Still we try to return the right information to the caller, * that is, 0 if we think the key should be still valid, 1 if * we think the key is expired at this time. */ if (server.masterhost != NULL) return now > when; /* Return when this key has not expired */ if (now <= when) return 0; /* Delete the key */ server.stat_expiredkeys++; propagateExpire(db,key); //事件函数的处理 notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED, "expired",key,db->id); //删除该键 return dbDelete(db,key); }
void rpoplpushCommand(redisClient *c) { robj *sobj, *value; int slotnum1 = keyHashSlot(c->argv[1]->ptr, sdslen(c->argv[1]->ptr)); int slotnum2 = keyHashSlot(c->argv[2]->ptr, sdslen(c->argv[2]->ptr)); if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk,slotnum1)) == NULL || checkType(c,sobj,REDIS_LIST)) return; if (listTypeLength(sobj) == 0) { /* This may only happen after loading very old RDB files. Recent * versions of Redis delete keys of empty lists. */ addReply(c,shared.nullbulk); } else { robj *dobj = lookupKeyWrite(c->db,c->argv[2],slotnum2); robj *touchedkey = c->argv[1]; if (dobj && checkType(c,dobj,REDIS_LIST)) return; value = listTypePop(sobj,REDIS_TAIL); /* We saved touched key, and protect it, since rpoplpushHandlePush * may change the client command argument vector (it does not * currently). */ incrRefCount(touchedkey); rpoplpushHandlePush(c,c->argv[2],dobj,value,slotnum2); /* listTypePop returns an object with its refcount incremented */ decrRefCount(value); /* Delete the source list when it is empty */ notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"rpop",touchedkey,c->db->id); if (listTypeLength(sobj) == 0) { dbDelete(c->db,touchedkey,slotnum1); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", touchedkey,c->db->id); } signalModifiedKey(c->db,touchedkey,slotnum1); decrRefCount(touchedkey); server.dirty++; } }
void zremrangebyrankCommand(redisClient *c) { long start; long end; int llen; long deleted; robj *zsetobj; zset *zs; if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,zsetobj,REDIS_ZSET)) return; zs = zsetobj->ptr; llen = zs->zsl->length; /* convert negative indexes */ if (start < 0) start = llen+start; if (end < 0) end = llen+end; if (start < 0) start = 0; /* Invariant: start >= 0, so this test will be true when end < 0. * The range is empty when start > end or start >= length. */ if (start > end || start >= llen) { addReply(c,shared.czero); return; } if (end >= llen) end = llen-1; /* increment start and end because zsl*Rank functions * use 1-based rank */ deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict); if (htNeedsResize(zs->dict)) dictResize(zs->dict); if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]); if (deleted) touchWatchedKey(c->db,c->argv[1]); server.dirty += deleted; addReplyLongLong(c, deleted); }
void zremrangebyscoreCommand(redisClient *c) { zrangespec range; long deleted; robj *o; zset *zs; /* Parse the range arguments. */ if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) { addReplyError(c,"min or max is not a double"); return; } if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_ZSET)) return; zs = o->ptr; deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict); if (htNeedsResize(zs->dict)) dictResize(zs->dict); if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]); if (deleted) touchWatchedKey(c->db,c->argv[1]); server.dirty += deleted; addReplyLongLong(c,deleted); }
void rpoplpushCommand(redisClient *c) { robj *sobj, *value; if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,sobj,REDIS_LIST)) return; if (listTypeLength(sobj) == 0) { /* This may only happen after loading very old RDB files. Recent * versions of Redis delete keys of empty lists. */ addReply(c,shared.nullbulk); } else { robj *dobj = lookupKeyWrite(c->db,c->argv[2]); robj *touchedkey = c->argv[1]; if (dobj && checkType(c,dobj,REDIS_LIST)) return; value = listTypePop(sobj,REDIS_TAIL); /* We saved touched key, and protect it, since rpoplpushHandlePush * may change the client command argument vector. */ incrRefCount(touchedkey); rpoplpushHandlePush(c,c,c->argv[2],dobj,value); /* listTypePop returns an object with its refcount incremented */ decrRefCount(value); /* Delete the source list when it is empty */ if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey); signalModifiedKey(c->db,touchedkey); decrRefCount(touchedkey); server.dirty++; /* Replicate this as a simple RPOP since the LPUSH side is replicated * by rpoplpushHandlePush() call if needed (it may not be needed * if a client is blocking wait a push against the list). */ rewriteClientCommandVector(c,2, resetRefCount(createStringObject("RPOP",4)), c->argv[1]); } }
int expireIfNeeded(redisDb *db, robj *key) { time_t when = getExpire(db,key); if (when < 0) return 0; /* No expire for this key */ /* If we are running in the context of a slave, return ASAP: * the slave key expiration is controlled by the master that will * send us synthesized DEL operations for expired keys. * * Still we try to return the right information to the caller, * that is, 0 if we think the key should be still valid, 1 if * we think the key is expired at this time. */ if (server.masterhost != NULL) { return time(NULL) > when; } /* Return when this key has not expired */ if (time(NULL) <= when) return 0; /* Delete the key */ server.stat_expiredkeys++; propagateExpire(db,key); return dbDelete(db,key); }
void delCommand(redisClient *c) { int deleted = 0, j; for (j = 1; j < c->argc; j++) { if (server.ds_enabled) { lookupKeyRead(c->db,c->argv[j]); /* FIXME: this can be optimized a lot, no real need to load * a possibly huge value. */ } if (dbDelete(c->db,c->argv[j])) { signalModifiedKey(c->db,c->argv[j]); server.dirty++; deleted++; } else if (server.ds_enabled) { if (cacheKeyMayExist(c->db,c->argv[j]) && dsExists(c->db,c->argv[j])) { cacheScheduleIO(c->db,c->argv[j],REDIS_IO_SAVE); deleted = 1; } } } addReplyLongLong(c,deleted); }
void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { int i, j, setnum; int aggregate = REDIS_AGGR_SUM; zsetopsrc *src; robj *dstobj; zset *dstzset; zskiplistNode *znode; dictIterator *di; dictEntry *de; int touched = 0; /* expect setnum input keys to be given */ setnum = atoi(c->argv[2]->ptr); if (setnum < 1) { addReplyError(c, "at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE"); return; } /* test if the expected number of keys would overflow */ if (3+setnum > c->argc) { addReply(c,shared.syntaxerr); return; } /* read keys to be used for input */ src = zmalloc(sizeof(zsetopsrc) * setnum); for (i = 0, j = 3; i < setnum; i++, j++) { robj *obj = lookupKeyWrite(c->db,c->argv[j]); if (!obj) { src[i].dict = NULL; } else { if (obj->type == REDIS_ZSET) { src[i].dict = ((zset*)obj->ptr)->dict; } else if (obj->type == REDIS_SET) { src[i].dict = (obj->ptr); } else { zfree(src); addReply(c,shared.wrongtypeerr); return; } } /* default all weights to 1 */ src[i].weight = 1.0; } /* parse optional extra arguments */ if (j < c->argc) { int remaining = c->argc - j; while (remaining) { if (remaining >= (setnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) { j++; remaining--; for (i = 0; i < setnum; i++, j++, remaining--) { if (getDoubleFromObjectOrReply(c,c->argv[j],&src[i].weight, "weight value is not a double") != REDIS_OK) { zfree(src); return; } } } else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) { j++; remaining--; if (!strcasecmp(c->argv[j]->ptr,"sum")) { aggregate = REDIS_AGGR_SUM; } else if (!strcasecmp(c->argv[j]->ptr,"min")) { aggregate = REDIS_AGGR_MIN; } else if (!strcasecmp(c->argv[j]->ptr,"max")) { aggregate = REDIS_AGGR_MAX; } else { zfree(src); addReply(c,shared.syntaxerr); return; } j++; remaining--; } else { zfree(src); addReply(c,shared.syntaxerr); return; } } } /* sort sets from the smallest to largest, this will improve our * algorithm's performance */ qsort(src,setnum,sizeof(zsetopsrc),qsortCompareZsetopsrcByCardinality); dstobj = createZsetObject(); dstzset = dstobj->ptr; if (op == REDIS_OP_INTER) { /* skip going over all entries if the smallest zset is NULL or empty */ if (src[0].dict && dictSize(src[0].dict) > 0) { /* precondition: as src[0].dict is non-empty and the zsets are ordered * from small to large, all src[i > 0].dict are non-empty too */ di = dictGetIterator(src[0].dict); while((de = dictNext(di)) != NULL) { double score, value; score = src[0].weight * zunionInterDictValue(de); for (j = 1; j < setnum; j++) { dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); if (other) { value = src[j].weight * zunionInterDictValue(other); zunionInterAggregate(&score,value,aggregate); } else { break; } } /* Only continue when present in every source dict. */ if (j == setnum) { robj *o = dictGetEntryKey(de); znode = zslInsert(dstzset->zsl,score,o); incrRefCount(o); /* added to skiplist */ dictAdd(dstzset->dict,o,&znode->score); incrRefCount(o); /* added to dictionary */ } } dictReleaseIterator(di); } } else if (op == REDIS_OP_UNION) { for (i = 0; i < setnum; i++) { if (!src[i].dict) continue; di = dictGetIterator(src[i].dict); while((de = dictNext(di)) != NULL) { double score, value; /* skip key when already processed */ if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL) continue; /* initialize score */ score = src[i].weight * zunionInterDictValue(de); /* because the zsets are sorted by size, its only possible * for sets at larger indices to hold this entry */ for (j = (i+1); j < setnum; j++) { dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); if (other) { value = src[j].weight * zunionInterDictValue(other); zunionInterAggregate(&score,value,aggregate); } } robj *o = dictGetEntryKey(de); znode = zslInsert(dstzset->zsl,score,o); incrRefCount(o); /* added to skiplist */ dictAdd(dstzset->dict,o,&znode->score); incrRefCount(o); /* added to dictionary */ } dictReleaseIterator(di); } } else { /* unknown operator */ redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION); } if (dbDelete(c->db,dstkey)) { touchWatchedKey(c->db,dstkey); touched = 1; server.dirty++; } if (dstzset->zsl->length) { dbAdd(c->db,dstkey,dstobj); addReplyLongLong(c, dstzset->zsl->length); if (!touched) touchWatchedKey(c->db,dstkey); server.dirty++; } else { decrRefCount(dstobj); addReply(c, shared.czero); } zfree(src); }
//函数会在redis每次执行完单个命令,事务块或lua脚本之后被调用 //对于所有被阻塞在client的key来说,只要key被执行了PUSH,那么这个key会被加入到server.ready_keys中 //处理client的阻塞状态 void handleClientsBlockedOnLists(void) { //只要server.ready_keys还有要解除阻塞的key,就循环遍历server.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. */ //备份一个server.ready_keys链表 l = server.ready_keys; //生成一个新的空链表 server.ready_keys = listCreate(); //只要链表中还有就绪的key while(listLength(l) != 0) { listNode *ln = listFirst(l); //链表头结点地址 readyList *rl = ln->value; //保存链表节点的值,每个值都是readyList结构 /* First of all remove this key from db->ready_keys so that * we can safely call signalListAsReady() against this key. */ //从rl->db->ready_keys中删除就绪的key dictDelete(rl->db->ready_keys,rl->key); /* If the key exists and it's a list, serve blocked clients * with data. */ //以读操作将就绪key的值读出来 robj *o = lookupKeyWrite(rl->db,rl->key); //读出的value对象必须是列表类型 if (o != NULL && o->type == OBJ_LIST) { dictEntry *de; /* We serve clients in the same order they blocked for * this key, from the first blocked to the last. */ // blocking_keys是一个字典,字典的键是造成client阻塞的键,字典的值是链表,保存被阻塞的client // 根据key取出被阻塞的client de = dictFind(rl->db->blocking_keys,rl->key); // 链表非空 if (de) { // 获取de节点的值 list *clients = dictGetVal(de); // 获取链表的长度 int numclients = listLength(clients); //遍历链表的所有节点 while(numclients--) { // 第一个client节点地址 listNode *clientnode = listFirst(clients); // 取出节点的值,是一个client类型 client *receiver = clientnode->value; // 从client类型中的target获得要PUSH出的dstkey,该键保存在target中 robj *dstkey = receiver->bpop.target; // 获取弹出的位置,根据BRPOPLPUSH命令 int where = (receiver->lastcmd && receiver->lastcmd->proc == blpopCommand) ? LIST_HEAD : LIST_TAIL; // 从列表中弹出元素 robj *value = listTypePop(o,where); // 弹出成功 if (value) { /* Protect receiver->bpop.target, that will be * freed by the next unblockClient() * call. */ //增加dstkey的引用计数,保护该键,在unblockClient函数中释放 if (dstkey) incrRefCount(dstkey); //取消client的阻塞状态 unblockClient(receiver); // 将value推入造成client阻塞的键上, if (serveClientBlockedOnList(receiver, rl->key,dstkey,rl->db,value, where) == C_ERR) { /* If we failed serving the client we need * to also undo the POP operation. */ // 如果推入失败,则需要键弹出的value还原回去 listTypePush(o,value,where); } // 释放dstkey和value if (dstkey) decrRefCount(dstkey); decrRefCount(value); } else { break; } } } // 如果弹出了所有元素,将key从数据库中删除 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); } //释放原来的ready_keys,因为之前创建了新的链表 listRelease(l); /* We have the new list on place at this point. */ } }
// BRPOP BLPOP 命令的底层实现 // BLPOP key [key ...] timeout void blockingPopGenericCommand(client *c, int where) { robj *o; mstime_t timeout; int j; // 以秒为单位取出timeout值 if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout,UNIT_SECONDS) != C_OK) return; //遍历所有的key for (j = 1; j < c->argc-1; j++) { //以写操作取出当前key的值 o = lookupKeyWrite(c->db,c->argv[j]); // value对象不为空 if (o != NULL) { // 如果value对象的类型不是列表类型,发送类型错误信息 if (o->type != OBJ_LIST) { addReply(c,shared.wrongtypeerr); return; } else { // 列表长度不为0 if (listTypeLength(o) != 0) { /* Non empty list, this is like a non normal [LR]POP. */ // 保存事件名称 char *event = (where == LIST_HEAD) ? "lpop" : "rpop"; // 保存弹出的value对象 robj *value = listTypePop(o,where); serverAssert(value != NULL); // 发送回复给client addReplyMultiBulkLen(c,2); addReplyBulk(c,c->argv[j]); addReplyBulk(c,value); // 释放value decrRefCount(value); // 发送事件通知 notifyKeyspaceEvent(NOTIFY_LIST,event, c->argv[j],c->db->id); //如果弹出元素后列表为空 if (listTypeLength(o) == 0) { //从数据库中删除当前的key dbDelete(c->db,c->argv[j]); // 发送"del"的事件通知 notifyKeyspaceEvent(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 == LIST_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 & CLIENT_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); }
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *ele, *dstset = NULL; int j, cardinality = 0; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { sets[j] = NULL; continue; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* We need a temp set object to store our union. If the dstkey * is not NULL (that is, we are inside an SUNIONSTORE operation) then * this set object will be the resulting object to set into the target key*/ dstset = createIntsetObject(); /* Iterate all the elements of all the sets, add every element a single * time to the result set */ for (j = 0; j < setnum; j++) { if (op == REDIS_OP_DIFF && j == 0 && !sets[j]) break; /* result set is empty */ if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (op == REDIS_OP_UNION || j == 0) { if (setTypeAdd(dstset,ele)) { cardinality++; } } else if (op == REDIS_OP_DIFF) { if (setTypeRemove(dstset,ele)) { cardinality--; } } decrRefCount(ele); } setTypeReleaseIterator(si); /* Exit when result set is empty. */ if (op == REDIS_OP_DIFF && cardinality == 0) break; } /* Output the content of the resulting set, if not in STORE mode */ if (!dstkey) { addReplyMultiBulkLen(c,cardinality); si = setTypeInitIterator(dstset); while((ele = setTypeNextObject(si)) != NULL) { addReplyBulk(c,ele); decrRefCount(ele); } setTypeReleaseIterator(si); decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); } else { decrRefCount(dstset); addReply(c,shared.czero); } touchWatchedKey(c->db,dstkey); server.dirty++; } zfree(sets); }
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *eleobj, *dstset = NULL; int64_t intobj; void *replylen = NULL; unsigned long j, cardinality = 0; int encoding; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { zfree(sets); if (dstkey) { if (dbDelete(c->db,dstkey)) { touchWatchedKey(c->db,dstkey); server.dirty++; } addReply(c,shared.czero); } else { addReply(c,shared.emptymultibulk); } return; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Sort sets from the smallest to largest, this will improve our * algorithm's performace */ qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality); /* The first thing we should output is the total number of elements... * since this is a multi-bulk write, but at this stage we don't know * the intersection set size, so we use a trick, append an empty object * to the output list and save the pointer to later modify it with the * right length */ if (!dstkey) { replylen = addDeferredMultiBulkLength(c); } else { /* If we have a target key where to store the resulting set * create this key with an empty set inside */ dstset = createIntsetObject(); } /* Iterate all the elements of the first (smallest) set, and test * the element against all the other sets, if at least one set does * not include the element it is discarded */ si = setTypeInitIterator(sets[0]); while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) { for (j = 1; j < setnum; j++) { if (encoding == REDIS_ENCODING_INTSET) { /* intset with intset is simple... and fast */ if (sets[j]->encoding == REDIS_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,intobj)) { break; /* in order to compare an integer with an object we * have to use the generic function, creating an object * for this */ } else if (sets[j]->encoding == REDIS_ENCODING_HT) { eleobj = createStringObjectFromLongLong(intobj); if (!setTypeIsMember(sets[j],eleobj)) { decrRefCount(eleobj); break; } decrRefCount(eleobj); } } else if (encoding == REDIS_ENCODING_HT) { /* Optimization... if the source object is integer * encoded AND the target set is an intset, we can get * a much faster path. */ if (eleobj->encoding == REDIS_ENCODING_INT && sets[j]->encoding == REDIS_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr)) { break; /* else... object to object check is easy as we use the * type agnostic API here. */ } else if (!setTypeIsMember(sets[j],eleobj)) { break; } } } /* Only take action when all sets contain the member */ if (j == setnum) { if (!dstkey) { if (encoding == REDIS_ENCODING_HT) addReplyBulk(c,eleobj); else addReplyBulkLongLong(c,intobj); cardinality++; } else { if (encoding == REDIS_ENCODING_INTSET) { eleobj = createStringObjectFromLongLong(intobj); setTypeAdd(dstset,eleobj); decrRefCount(eleobj); } else { setTypeAdd(dstset,eleobj); } } } } setTypeReleaseIterator(si); if (dstkey) { /* Store the resulting set into the target, if the intersection * is not an empty set. */ dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); } else { decrRefCount(dstset); addReply(c,shared.czero); } touchWatchedKey(c->db,dstkey); server.dirty++; } else { setDeferredMultiBulkLength(c,replylen,cardinality); } zfree(sets); }
// LREM key count value // LREM命令 void lremCommand(client *c) { robj *subject, *obj; obj = c->argv[3]; long toremove; long removed = 0; //将字符串类型的count参数转换为long类型的整数,保存在toremove中 if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != C_OK)) return; //以写操作读取出key对象的value值 subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero); //如果key不存在或value对象不是列表类型则直接返回 if (subject == NULL || checkType(c,subject,OBJ_LIST)) return; listTypeIterator *li; if (toremove < 0) { //如果toremove小于零,则从尾部向头部删除 toremove = -toremove; //创建迭代器,指向尾部元素 li = listTypeInitIterator(subject,-1,LIST_HEAD); } else { //如果toremove大于等于零,则从头部向尾部删除,创建迭代器 li = listTypeInitIterator(subject,0,LIST_TAIL); } listTypeEntry entry; //遍历列表,保存迭代器当前指向的entry while (listTypeNext(li,&entry)) { //如果当前entry的值是obj if (listTypeEqual(&entry,obj)) { //删除当前的entry listTypeDelete(li, &entry); //更新脏键 server.dirty++; //更新计数器 removed++; //如果删除了count个,则跳出循环 if (toremove && removed == toremove) break; } } //释放迭代器 listTypeReleaseIterator(li); //如果删除成功 if (removed) { //当数据库的键被改动,则会调用该函数发送信号 signalModifiedKey(c->db,c->argv[1]); //发送"lrem"时间通知 notifyKeyspaceEvent(NOTIFY_GENERIC,"lrem",c->argv[1],c->db->id); } //如果将列表中的元素全部删除完了 if (listTypeLength(subject) == 0) { //从数据库中删除键key dbDelete(c->db,c->argv[1]); //发送"del"时间通知 notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id); } //发送删除元素的个数给client addReplyLongLong(c,removed); }
void LS3Datastore::dbDelete(const QString& recordUUID) { LS3ElapsedAutoTimer timer("LS3Datastore::dbDelete("+recordUUID+")"); dbDelete(getRecordByUUID(recordUUID)); }
/* The SORT command is the most complex command in Redis. Warning: this code * is optimized for speed and a bit less for readability */ void sortCommand(redisClient *c) { list *operations; unsigned int outputlen = 0; int desc = 0, alpha = 0; long limit_start = 0, limit_count = -1, start, end; int j, dontsort = 0, vectorlen; int getop = 0; /* GET operation counter */ int int_convertion_error = 0; robj *sortval, *sortby = NULL, *storekey = NULL; redisSortObject *vector; /* Resulting vector to sort */ /* Lookup the key to sort. It must be of the right types */ sortval = lookupKeyRead(c->db,c->argv[1]); if (sortval && sortval->type != REDIS_SET && sortval->type != REDIS_LIST && sortval->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); return; } /* Create a list of operations to perform for every sorted element. * Operations can be GET/DEL/INCR/DECR */ operations = listCreate(); listSetFreeMethod(operations,zfree); j = 2; /* options start at argv[2] */ /* Now we need to protect sortval incrementing its count, in the future * SORT may have options able to overwrite/delete keys during the sorting * and the sorted key itself may get destroyed */ if (sortval) incrRefCount(sortval); else sortval = createListObject(); /* The SORT command has an SQL-alike syntax, parse it */ while(j < c->argc) { int leftargs = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"asc")) { desc = 0; } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { desc = 1; } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { alpha = 1; } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return; j+=2; } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { storekey = c->argv[j+1]; j++; } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { sortby = c->argv[j+1]; /* If the BY pattern does not contain '*', i.e. it is constant, * we don't need to sort nor to lookup the weight keys. */ if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1; j++; } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { listAddNodeTail(operations,createSortOperation( REDIS_SORT_GET,c->argv[j+1])); getop++; j++; } else { decrRefCount(sortval); listRelease(operations); addReply(c,shared.syntaxerr); return; } j++; } /* For the STORE option, or when SORT is called from a Lua script, * we want to force a specific ordering even when no explicit ordering * was asked (SORT BY nosort). This guarantees that replication / AOF * is deterministic. * * However in the case 'dontsort' is true, but the type to sort is a * sorted set, we don't need to do anything as ordering is guaranteed * in this special case. */ if ((storekey || c->flags & REDIS_LUA_CLIENT) && (dontsort && sortval->type != REDIS_ZSET)) { /* Force ALPHA sorting */ dontsort = 0; alpha = 1; sortby = NULL; } /* Destructively convert encoded sorted sets for SORT. */ if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); /* Objtain the length of the object to sort. */ switch(sortval->type) { case REDIS_LIST: vectorlen = listTypeLength(sortval); break; case REDIS_SET: vectorlen = setTypeSize(sortval); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ } /* Perform LIMIT start,count sanity checking. */ start = (limit_start < 0) ? 0 : limit_start; end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; if (start >= vectorlen) { start = vectorlen-1; end = vectorlen-2; } if (end >= vectorlen) end = vectorlen-1; /* Optimization: * * 1) if the object to sort is a sorted set. * 2) There is nothing to sort as dontsort is true (BY <constant string>). * 3) We have a LIMIT option that actually reduces the number of elements * to fetch. * * In this case to load all the objects in the vector is a huge waste of * resources. We just allocate a vector that is big enough for the selected * range length, and make sure to load just this part in the vector. */ if (sortval->type == REDIS_ZSET && dontsort && (start != 0 || end != vectorlen-1)) { vectorlen = end-start+1; } /* Load the sorting vector with all the objects to sort */ vector = zmalloc(sizeof(redisSortObject)*vectorlen); j = 0; if (sortval->type == REDIS_LIST) { listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); listTypeEntry entry; while(listTypeNext(li,&entry)) { vector[j].obj = listTypeGet(&entry); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } listTypeReleaseIterator(li); } else if (sortval->type == REDIS_SET) { setTypeIterator *si = setTypeInitIterator(sortval); robj *ele; while((ele = setTypeNextObject(si)) != NULL) { vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } setTypeReleaseIterator(si); } else if (sortval->type == REDIS_ZSET && dontsort) { /* Special handling for a sorted set, if 'dontsort' is true. * This makes sure we return elements in the sorted set original * ordering, accordingly to DESC / ASC options. * * Note that in this case we also handle LIMIT here in a direct * way, just getting the required range, as an optimization. */ zset *zs = sortval->ptr; zskiplist *zsl = zs->zsl; zskiplistNode *ln; robj *ele; int rangelen = vectorlen; /* Check if starting point is trivial, before doing log(N) lookup. */ if (desc) { long zsetlen = dictSize(((zset*)sortval->ptr)->dict); ln = zsl->tail; if (start > 0) ln = zslGetElementByRank(zsl,zsetlen-start); } else { ln = zsl->header->level[0].forward; if (start > 0) ln = zslGetElementByRank(zsl,start+1); } while(rangelen--) { redisAssertWithInfo(c,sortval,ln != NULL); ele = ln->obj; vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; ln = desc ? ln->backward : ln->level[0].forward; } /* The code producing the output does not know that in the case of * sorted set, 'dontsort', and LIMIT, we are able to get just the * range, already sorted, so we need to adjust "start" and "end" * to make sure start is set to 0. */ end -= start; start = 0; } else if (sortval->type == REDIS_ZSET) { dict *set = ((zset*)sortval->ptr)->dict; dictIterator *di; dictEntry *setele; di = dictGetIterator(set); while((setele = dictNext(di)) != NULL) { vector[j].obj = dictGetKey(setele); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } dictReleaseIterator(di); } else { redisPanic("Unknown type"); } redisAssertWithInfo(c,sortval,j == vectorlen); /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { robj *byval; if (sortby) { /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); if (!byval) continue; } else { /* use object itself to sort by */ byval = vector[j].obj; } if (alpha) { if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); } else { if (byval->encoding == REDIS_ENCODING_RAW) { char *eptr; vector[j].u.score = strtod(byval->ptr,&eptr); if (eptr[0] != '\0' || errno == ERANGE || isnan(vector[j].u.score)) { int_convertion_error = 1; } } else if (byval->encoding == REDIS_ENCODING_INT) { /* Don't need to decode the object if it's * integer-encoded (the only encoding supported) so * far. We can just cast it */ vector[j].u.score = (long)byval->ptr; } else { redisAssertWithInfo(c,sortval,1 != 1); } } /* when the object was retrieved using lookupKeyByPattern, * its refcount needs to be decreased. */ if (sortby) { decrRefCount(byval); } } } if (dontsort == 0) { server.sort_desc = desc; server.sort_alpha = alpha; server.sort_bypattern = sortby ? 1 : 0; server.sort_store = storekey ? 1 : 0; if (sortby && (start != 0 || end != vectorlen-1)) pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); else qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); } /* Send command output to the output buffer, performing the specified * GET/DEL/INCR/DECR operations if any. */ outputlen = getop ? getop*(end-start+1) : end-start+1; if (int_convertion_error) { addReplyError(c,"One or more scores can't be converted into double"); } else if (storekey == NULL) { /* STORE option not specified, sent the sorting result to client */ addReplyMultiBulkLen(c,outputlen); for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) addReplyBulk(c,vector[j].obj); listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); decrRefCount(val); } } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } else { robj *sobj = createZiplistObject(); /* STORE option specified, set the sorting result as a List object */ for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) { listTypePush(sobj,vector[j].obj,REDIS_TAIL); } else { listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) val = createStringObject("",0); /* listTypePush does an incrRefCount, so we should take care * care of the incremented refcount caused by either * lookupKeyByPattern or createStringObject("",0) */ listTypePush(sobj,val,REDIS_TAIL); decrRefCount(val); } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } if (outputlen) { setKey(c->db,storekey,sobj); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey, c->db->id); server.dirty += outputlen; } else if (dbDelete(c->db,storekey)) { signalModifiedKey(c->db,storekey); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id); server.dirty++; } decrRefCount(sobj); addReplyLongLong(c,outputlen); } /* Cleanup */ if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) for (j = 0; j < vectorlen; j++) decrRefCount(vector[j].obj); decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); }
/* This function should be called by Redis every time a single command, * a MULTI/EXEC block, or a Lua script, terminated its execution after * being called by a client. * * All the keys with at least one client blocked that received at least * one new element via some PUSH operation are accumulated into * the server.ready_keys list. This function will run the list and will * serve clients accordingly. Note that the function will iterate again and * again as a result of serving BRPOPLPUSH we can have new blocking clients * to serve because of the PUSH side of BRPOPLPUSH. */ void handleClientsBlockedOnLists(void) { 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. */ l = server.ready_keys; server.ready_keys = listCreate(); while(listLength(l) != 0) { robj *o; listNode *ln = listFirst(l); readyList *rl = ln->value; /* First of all remove this key from db->ready_keys so that * we can safely call signalListAsReady() against this key. */ dictDelete(rl->db->ready_keys,rl->key); /* If the key exists and it's a list, serve blocked clients * with data. */ 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. */ 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; robj *dstkey = receiver->bpop.target; int where = (receiver->lastcmd && receiver->lastcmd->proc == blpopCommand) ? REDIS_HEAD : REDIS_TAIL; robj *value = listTypePop(o,where); if (value) { /* Protect receiver->bpop.target, that will be * freed by the next unblockClientWaitingData() * call. */ if (dstkey) incrRefCount(dstkey); unblockClientWaitingData(receiver); 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 { 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. */ } }
void LS3Datastore::dbDelete() { LS3ElapsedAutoTimer timer("LS3Datastore::dbDelete()"); dbDelete(currentRecordNum()); }
void spopWithCountCommand(redisClient *c) { long l; unsigned long count, size; robj *set; /* Get the count argument */ if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != REDIS_OK) return; if (l >= 0) { count = (unsigned) l; } else { addReply(c,shared.outofrangeerr); return; } /* Make sure a key with the name inputted exists, and that it's type is * indeed a set. Otherwise, return nil */ if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,set,REDIS_SET)) return; /* If count is zero, serve an empty multibulk ASAP to avoid special * cases later. */ if (count == 0) { addReply(c,shared.emptymultibulk); return; } size = setTypeSize(set); /* Generate an SPOP keyspace notification */ notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id); server.dirty += count; /* CASE 1: * The number of requested elements is greater than or equal to * the number of elements inside the set: simply return the whole set. */ if (count >= size) { /* We just return the entire set */ sunionDiffGenericCommand(c,c->argv+1,1,NULL,REDIS_OP_UNION); /* Delete the set as it is now empty */ dbDelete(c->db,c->argv[1]); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id); /* Propagate this command as an DEL operation */ rewriteClientCommandVector(c,2,shared.del,c->argv[1]); signalModifiedKey(c->db,c->argv[1]); server.dirty++; return; } /* Case 2 and 3 require to replicate SPOP as a set of SERM commands. * Prepare our replication argument vector. Also send the array length * which is common to both the code paths. */ robj *propargv[3]; propargv[0] = createStringObject("SREM",4); propargv[1] = c->argv[1]; addReplyMultiBulkLen(c,count); /* Common iteration vars. */ robj *objele; int encoding; int64_t llele; unsigned long remaining = size-count; /* Elements left after SPOP. */ /* If we are here, the number of requested elements is less than the * number of elements inside the set. Also we are sure that count < size. * Use two different strategies. * * CASE 2: The number of elements to return is small compared to the * set size. We can just extract random elements and return them to * the set. */ if (remaining*SPOP_MOVE_STRATEGY_MUL > count) { while(count--) { encoding = setTypeRandomElement(set,&objele,&llele); if (encoding == REDIS_ENCODING_INTSET) { objele = createStringObjectFromLongLong(llele); } else { incrRefCount(objele); } /* Return the element to the client and remove from the set. */ addReplyBulk(c,objele); setTypeRemove(set,objele); /* Replicate/AOF this command as an SREM operation */ propargv[2] = objele; alsoPropagate(server.sremCommand,c->db->id,propargv,3, REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL); decrRefCount(objele); } } else { /* CASE 3: The number of elements to return is very big, approaching * the size of the set itself. After some time extracting random elements * from such a set becomes computationally expensive, so we use * a different strategy, we extract random elements that we don't * want to return (the elements that will remain part of the set), * creating a new set as we do this (that will be stored as the original * set). Then we return the elements left in the original set and * release it. */ robj *newset = NULL; /* Create a new set with just the remaining elements. */ while(remaining--) { encoding = setTypeRandomElement(set,&objele,&llele); if (encoding == REDIS_ENCODING_INTSET) { objele = createStringObjectFromLongLong(llele); } else { incrRefCount(objele); } if (!newset) newset = setTypeCreate(objele); setTypeAdd(newset,objele); setTypeRemove(set,objele); decrRefCount(objele); } /* Assign the new set as the key value. */ incrRefCount(set); /* Protect the old set value. */ dbOverwrite(c->db,c->argv[1],newset); /* Tranfer the old set to the client and release it. */ setTypeIterator *si; si = setTypeInitIterator(set); while((encoding = setTypeNext(si,&objele,&llele)) != -1) { if (encoding == REDIS_ENCODING_INTSET) { objele = createStringObjectFromLongLong(llele); } else { incrRefCount(objele); } addReplyBulk(c,objele); /* Replicate/AOF this command as an SREM operation */ propargv[2] = objele; alsoPropagate(server.sremCommand,c->db->id,propargv,3, REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL); decrRefCount(objele); } setTypeReleaseIterator(si); decrRefCount(set); } /* Don't propagate the command itself even if we incremented the * dirty counter. We don't want to propagate an SPOP command since * we propagated the command as a set of SREMs operations using * the alsoPropagate() API. */ decrRefCount(propargv[0]); preventCommandPropagation(c); }
/* The SORT command is the most complex command in Redis. Warning: this code * is optimized for speed and a bit less for readability */ void sortCommand(redisClient *c) { list *operations; unsigned int outputlen = 0; int desc = 0, alpha = 0; long limit_start = 0, limit_count = -1, start, end; int j, dontsort = 0, vectorlen; int getop = 0; /* GET operation counter */ int int_convertion_error = 0; robj *sortval, *sortby = NULL, *storekey = NULL; redisSortObject *vector; /* Resulting vector to sort */ /* Lookup the key to sort. It must be of the right types */ sortval = lookupKeyRead(c->db,c->argv[1]); if (sortval && sortval->type != REDIS_SET && sortval->type != REDIS_LIST && sortval->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); return; } /* Create a list of operations to perform for every sorted element. * Operations can be GET/DEL/INCR/DECR */ operations = listCreate(); listSetFreeMethod(operations,zfree); j = 2; /* Now we need to protect sortval incrementing its count, in the future * SORT may have options able to overwrite/delete keys during the sorting * and the sorted key itself may get destroied */ if (sortval) incrRefCount(sortval); else sortval = createListObject(); /* The SORT command has an SQL-alike syntax, parse it */ while(j < c->argc) { int leftargs = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"asc")) { desc = 0; } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { desc = 1; } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { alpha = 1; } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return; j+=2; } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { storekey = c->argv[j+1]; j++; } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { sortby = c->argv[j+1]; /* If the BY pattern does not contain '*', i.e. it is constant, * we don't need to sort nor to lookup the weight keys. */ if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1; j++; } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { listAddNodeTail(operations,createSortOperation( REDIS_SORT_GET,c->argv[j+1])); getop++; j++; } else { decrRefCount(sortval); listRelease(operations); addReply(c,shared.syntaxerr); return; } j++; } /* If we have STORE we need to force sorting for deterministic output * and replication. We use alpha sorting since this is guaranteed to * work with any input. */ if (storekey && dontsort) { dontsort = 0; alpha = 1; sortby = NULL; } /* Destructively convert encoded sorted sets for SORT. */ if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); /* Load the sorting vector with all the objects to sort */ switch(sortval->type) { case REDIS_LIST: vectorlen = listTypeLength(sortval); break; case REDIS_SET: vectorlen = setTypeSize(sortval); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ } vector = zmalloc(sizeof(redisSortObject)*vectorlen); j = 0; if (sortval->type == REDIS_LIST) { listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); listTypeEntry entry; while(listTypeNext(li,&entry)) { vector[j].obj = listTypeGet(&entry); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } listTypeReleaseIterator(li); } else if (sortval->type == REDIS_SET) { setTypeIterator *si = setTypeInitIterator(sortval); robj *ele; while((ele = setTypeNextObject(si)) != NULL) { vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } setTypeReleaseIterator(si); } else if (sortval->type == REDIS_ZSET) { dict *set = ((zset*)sortval->ptr)->dict; dictIterator *di; dictEntry *setele; di = dictGetIterator(set); while((setele = dictNext(di)) != NULL) { vector[j].obj = dictGetKey(setele); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } dictReleaseIterator(di); } else { redisPanic("Unknown type"); } redisAssertWithInfo(c,sortval,j == vectorlen); /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { robj *byval; if (sortby) { /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); if (!byval) continue; } else { /* use object itself to sort by */ byval = vector[j].obj; } if (alpha) { if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); } else { if (byval->encoding == REDIS_ENCODING_RAW) { char *eptr; vector[j].u.score = strtod(byval->ptr,&eptr); if (eptr[0] != '\0' || errno == ERANGE || isnan(vector[j].u.score)) { int_convertion_error = 1; } } else if (byval->encoding == REDIS_ENCODING_INT) { /* Don't need to decode the object if it's * integer-encoded (the only encoding supported) so * far. We can just cast it */ vector[j].u.score = (long)byval->ptr; } else { redisAssertWithInfo(c,sortval,1 != 1); } } /* when the object was retrieved using lookupKeyByPattern, * its refcount needs to be decreased. */ if (sortby) { decrRefCount(byval); } } } /* We are ready to sort the vector... perform a bit of sanity check * on the LIMIT option too. We'll use a partial version of quicksort. */ start = (limit_start < 0) ? 0 : limit_start; end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; if (start >= vectorlen) { start = vectorlen-1; end = vectorlen-2; } if (end >= vectorlen) end = vectorlen-1; server.sort_dontsort = dontsort; if (dontsort == 0) { server.sort_desc = desc; server.sort_alpha = alpha; server.sort_bypattern = sortby ? 1 : 0; if (sortby && (start != 0 || end != vectorlen-1)) pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); else qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); } /* Send command output to the output buffer, performing the specified * GET/DEL/INCR/DECR operations if any. */ outputlen = getop ? getop*(end-start+1) : end-start+1; if (int_convertion_error) { addReplyError(c,"One or more scores can't be converted into double"); } else if (storekey == NULL) { /* STORE option not specified, sent the sorting result to client */ addReplyMultiBulkLen(c,outputlen); for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) addReplyBulk(c,vector[j].obj); listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); decrRefCount(val); } } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } else { robj *sobj = createZiplistObject(); /* STORE option specified, set the sorting result as a List object */ for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) { listTypePush(sobj,vector[j].obj,REDIS_TAIL); } else { listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) val = createStringObject("",0); /* listTypePush does an incrRefCount, so we should take care * care of the incremented refcount caused by either * lookupKeyByPattern or createStringObject("",0) */ listTypePush(sobj,val,REDIS_TAIL); decrRefCount(val); } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } if (outputlen) { setKey(c->db,storekey,sobj); server.dirty += outputlen; } else if (dbDelete(c->db,storekey)) { signalModifiedKey(c->db,storekey); server.dirty++; } decrRefCount(sobj); addReplyLongLong(c,outputlen); } /* Cleanup */ if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) for (j = 0; j < vectorlen; j++) decrRefCount(vector[j].obj); decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); }
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *ele, *dstset = NULL; int j, cardinality = 0; int diff_algo = 1; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { sets[j] = NULL; continue; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Select what DIFF algorithm to use. * * Algorithm 1 is O(N*M) where N is the size of the element first set * and M the total number of sets. * * Algorithm 2 is O(N) where N is the total number of elements in all * the sets. * * We compute what is the best bet with the current input here. */ if (op == REDIS_OP_DIFF && sets[0]) { long long algo_one_work = 0, algo_two_work = 0; for (j = 0; j < setnum; j++) { if (sets[j] == NULL) continue; algo_one_work += setTypeSize(sets[0]); algo_two_work += setTypeSize(sets[j]); } /* Algorithm 1 has better constant times and performs less operations * if there are elements in common. Give it some advantage. */ algo_one_work /= 2; diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2; if (diff_algo == 1 && setnum > 1) { /* With algorithm 1 it is better to order the sets to subtract * by decreasing size, so that we are more likely to find * duplicated elements ASAP. */ qsort(sets+1,setnum-1,sizeof(robj*), qsortCompareSetsByRevCardinality); } } /* We need a temp set object to store our union. If the dstkey * is not NULL (that is, we are inside an SUNIONSTORE operation) then * this set object will be the resulting object to set into the target key*/ dstset = createIntsetObject(); if (op == REDIS_OP_UNION) { /* Union is trivial, just add every element of every set to the * temporary set. */ for (j = 0; j < setnum; j++) { if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (setTypeAdd(dstset,ele)) cardinality++; decrRefCount(ele); } setTypeReleaseIterator(si); } } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 1) { /* DIFF Algorithm 1: * * We perform the diff by iterating all the elements of the first set, * and only adding it to the target set if the element does not exist * into all the other sets. * * This way we perform at max N*M operations, where N is the size of * the first set, and M the number of sets. */ si = setTypeInitIterator(sets[0]); while((ele = setTypeNextObject(si)) != NULL) { for (j = 1; j < setnum; j++) { if (!sets[j]) continue; /* no key is an empty set. */ if (sets[j] == sets[0]) break; /* same set! */ if (setTypeIsMember(sets[j],ele)) break; } if (j == setnum) { /* There is no other set with this element. Add it. */ setTypeAdd(dstset,ele); cardinality++; } decrRefCount(ele); } setTypeReleaseIterator(si); } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 2) { /* DIFF Algorithm 2: * * Add all the elements of the first set to the auxiliary set. * Then remove all the elements of all the next sets from it. * * This is O(N) where N is the sum of all the elements in every * set. */ for (j = 0; j < setnum; j++) { if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (j == 0) { if (setTypeAdd(dstset,ele)) cardinality++; } else { if (setTypeRemove(dstset,ele)) cardinality--; } decrRefCount(ele); } setTypeReleaseIterator(si); /* Exit if result set is empty as any additional removal * of elements will have no effect. */ if (cardinality == 0) break; } } /* Output the content of the resulting set, if not in STORE mode */ if (!dstkey) { addReplyMultiBulkLen(c,cardinality); si = setTypeInitIterator(dstset); while((ele = setTypeNextObject(si)) != NULL) { addReplyBulk(c,ele); decrRefCount(ele); } setTypeReleaseIterator(si); decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ int deleted = dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); notifyKeyspaceEvent(REDIS_NOTIFY_SET, op == REDIS_OP_UNION ? "sunionstore" : "sdiffstore", dstkey,c->db->id); } else { decrRefCount(dstset); addReply(c,shared.czero); if (deleted) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", dstkey,c->db->id); } signalModifiedKey(c->db,dstkey); server.dirty++; } zfree(sets); }
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si;//迭代器 robj *eleobj, *dstset = NULL; int64_t intobj; void *replylen = NULL; unsigned long j, cardinality = 0; int encoding; for (j = 0; j < setnum; j++) {//得到所有的集合 robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) {//任何一个集合不存在,那么总的交集就为空 zfree(sets); if (dstkey) { if (dbDelete(c->db,dstkey)) { signalModifiedKey(c->db,dstkey); server.dirty++; } addReply(c,shared.czero); } else { addReply(c,shared.emptymultibulk); } return; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Sort sets from the smallest to largest, this will improve our * algorithm's performance */ //按照集合元素个数从小到大排序 qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality); /* The first thing we should output is the total number of elements... * since this is a multi-bulk write, but at this stage we don't know * the intersection set size, so we use a trick, append an empty object * to the output list and save the pointer to later modify it with the * right length */ if (!dstkey) { replylen = addDeferredMultiBulkLength(c); } else { /* If we have a target key where to store the resulting set * create this key with an empty set inside */ dstset = createIntsetObject(); } /* Iterate all the elements of the first (smallest) set, and test * the element against all the other sets, if at least one set does * not include the element it is discarded */ /** 求多个集合交集的算法思想: 首先按照集合元素个数对集合进行qsort,然后遍历排序后的第一个集合中的元素,查看该元素在 其他集合中是否存在,如果在其他集合中都存在,那么该元素为一个结果 */ si = setTypeInitIterator(sets[0]); while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) { for (j = 1; j < setnum; j++) { if (sets[j] == sets[0]) continue;//这段代码没意义啊 if (encoding == REDIS_ENCODING_INTSET) {//intset /* intset with intset is simple... and fast */ //集合sets[j]编码为intset if (sets[j]->encoding == REDIS_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,intobj))//在集合sets[j]中没有找到集合sets[0]的intobj { break; /* in order to compare an integer with an object we * have to use the generic function, creating an object * for this */ } else if (sets[j]->encoding == REDIS_ENCODING_HT) {//集合sets[j]编码为HT,sets[0]为INTSET eleobj = createStringObjectFromLongLong(intobj);//将sets[0]中的intobj转换为sds if (!setTypeIsMember(sets[j],eleobj)) {//如果eleobj不在集合sets[j]中 decrRefCount(eleobj); break; } decrRefCount(eleobj); } } else if (encoding == REDIS_ENCODING_HT) {//HT /* Optimization... if the source object is integer * encoded AND the target set is an intset, we can get * a much faster path. */ if (eleobj->encoding == REDIS_ENCODING_INT && sets[j]->encoding == REDIS_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr)) { break; /* else... object to object check is easy as we use the * type agnostic API here. */ } else if (!setTypeIsMember(sets[j],eleobj)) { break; } } } /* Only take action when all sets contain the member */ if (j == setnum) { if (!dstkey) { if (encoding == REDIS_ENCODING_HT) addReplyBulk(c,eleobj); else addReplyBulkLongLong(c,intobj); cardinality++; } else {//添加到临时目标集合 if (encoding == REDIS_ENCODING_INTSET) { eleobj = createStringObjectFromLongLong(intobj); setTypeAdd(dstset,eleobj); decrRefCount(eleobj); } else { setTypeAdd(dstset,eleobj); } } } } setTypeReleaseIterator(si); if (dstkey) { /* Store the resulting set into the target, if the intersection * is not an empty set. */ int deleted = dbDelete(c->db,dstkey);//覆盖原来的目标集合 if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sinterstore", dstkey,c->db->id); } else {//空集 decrRefCount(dstset); addReply(c,shared.czero); if (deleted) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", dstkey,c->db->id); } signalModifiedKey(c->db,dstkey); server.dirty++; } else { setDeferredMultiBulkLength(c,replylen,cardinality); } zfree(sets); }
/* * * slotsrestore key ttl val [key ttl val ...] * */ void slotsrestoreCommand(redisClient *c) { if (c->argc < 4 || (c->argc - 1) % 3 != 0) { addReplyErrorFormat(c, "wrong number of arguments for 'slotsrestore' command"); return; } int n = (c->argc - 1) / 3; long long *ttls = zmalloc(sizeof(long long) * n); robj **vals = zmalloc(sizeof(robj *) * n); for (int i = 0; i < n; i ++) { vals[i] = NULL; } for (int i = 0; i < n; i ++) { robj *key = c->argv[i * 3 + 1]; robj *ttl = c->argv[i * 3 + 2]; robj *val = c->argv[i * 3 + 3]; if (lookupKeyWrite(c->db, key) != NULL) { redisLog(REDIS_WARNING, "slotsrestore: slot = %d, key = '%s' already exists", slots_num(key->ptr, NULL), (char *)key->ptr); } if (getLongLongFromObjectOrReply(c, ttl, &ttls[i], NULL) != REDIS_OK) { goto cleanup; } else if (ttls[i] < 0) { addReplyError(c, "invalid ttl value, must be >= 0"); goto cleanup; } rio payload; int type; if (verifyDumpPayload(val->ptr, sdslen(val->ptr)) != REDIS_OK) { addReplyError(c, "dump payload version or checksum are wrong"); goto cleanup; } rioInitWithBuffer(&payload, val->ptr); if (((type = rdbLoadObjectType(&payload)) == -1) || ((vals[i] = rdbLoadObject(type, &payload)) == NULL)) { addReplyError(c, "bad data format"); goto cleanup; } } for (int i = 0; i < n; i ++) { robj *key = c->argv[i * 3 + 1]; long long ttl = ttls[i]; robj *val = vals[i]; dbDelete(c->db, key); dbAdd(c->db, key, val); incrRefCount(val); if (ttl) { setExpire(c->db, key, mstime() + ttl); } signalModifiedKey(c->db, key); server.dirty ++; } addReply(c, shared.ok); cleanup: for (int i = 0; i < n; i ++) { if (vals[i] != NULL) { decrRefCount(vals[i]); } } zfree(vals); zfree(ttls); }