Author: Zhang Shihua, R & D team of shunfengche operation
ZADD
ZADD key [NX|XX] [CH] [INCR]score member [score member ...]
Add elements and corresponding score values to an ordered set
NX: do not update the existing key, only add new elements
20: Update only the existing key without adding new elements
CH:abbr:changed. When not specified, only the number of new elements is returned. When specified, the sum of the number of new and updated elements is returned
INCR: reference zincrby
//Distinguish zadd or zincrby the second parameter void zaddCommand(client *c) { zaddGenericCommand(c,ZADD_NONE); }
/* This generic command implements both ZADD and ZINCRBY. */ //Both the zadd and zincrby commands call this function void zaddGenericCommand(client *c, int flags) { static char *nanerr = "resulting score is not a number (NaN)"; robj *key = c->argv[1]; robj *zobj; sds ele; double score = 0, *scores = NULL; int j, elements; int scoreidx = 0; /* The following vars are used in order to track what the command actually * did during the execution, to reply to the client and to trigger the * notification of keyspace change. */ int added = 0; /* Number of new elements added. */ int updated = 0; /* Number of elements with updated score. */ int processed = 0; /* Number of elements processed, may remain zero with options like XX. */ /* Parse options. At the end 'scoreidx' is set to the argument position * of the score of the first score-element pair. */ scoreidx = 2;//Start with the second parameter. First process the nx,xx,ch,incr parameters while(scoreidx < c->argc) { char *opt = c->argv[scoreidx]->ptr; if (!strcasecmp(opt,"nx")) flags |= ZADD_NX; else if (!strcasecmp(opt,"xx")) flags |= ZADD_XX; else if (!strcasecmp(opt,"ch")) flags |= ZADD_CH; else if (!strcasecmp(opt,"incr")) flags |= ZADD_INCR; else break; scoreidx++; } /* Turn options into simple to check vars. */ //Take the corresponding flag from the flag and assign it to the independent variable int incr = (flags & ZADD_INCR) != 0; int nx = (flags & ZADD_NX) != 0; int xx = (flags & ZADD_XX) != 0; int ch = (flags & ZADD_CH) != 0; /* After the options, we expect to have an even number of args, since * we expect any number of score-element pairs. */ //member and score are one-to-one, so they must be multiples of 2. So if they are not multiples of 2 or fundamental //Without member and score, the command syntax error is returned directly elements = c->argc-scoreidx; if (elements % 2 || !elements) { addReply(c,shared.syntaxerr); return; } //How many pairs of < element, score > elements /= 2; /* Now this holds the number of score-element pairs. */ /* Check for incompatible options. */ //nx and xxflag are mutually exclusive. They cannot appear at the same time if (nx && xx) { addReplyError(c, "XX and NX options at the same time are not compatible"); return; } //If there is incr flag, only one pair of < element, score > //Why can't it be many right? if (incr && elements > 1) { addReplyError(c, "INCR option supports a single increment-element pair"); return; } /* Start parsing all the scores, we need to emit any syntax error * before executing additions to the sorted set, as the command should * either execute fully or nothing at all. */ //Check each score in turn scores = zmalloc(sizeof(double)*elements); for (j = 0; j < elements; j++) { //This function checks whether score is a legal value of double type if (getDoubleFromObjectOrReply(c,c->argv[scoreidx+j*2],&scores[j],NULL) != C_OK) goto cleanup; } /* Lookup the key and create the sorted set if does not exist. */ //Find the value of the corresponding ordered set according to the key zobj = lookupKeyWrite(c->db,key); //key does not exist. if (zobj == NULL) { //If xx flag is set, an error will be returned directly if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */ //According to the redis configuration, if the ordered collection is set to not use ziplist storage or the length of the first insert element is greater than //Set the element length value of the maximum ziplist, then use the jump table to store it, otherwise use ziplist if (server.zset_max_ziplist_entries == 0 || server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr)) { zobj = createZsetObject(); } else { zobj = createZsetZiplistObject(); } //Insert key,zobj into the dictionary dbAdd(c->db,key,zobj); //key exists } else { //If it is not an ordered set, it will directly return an error if (zobj->type != OBJ_ZSET) { addReply(c,shared.wrongtypeerr); goto cleanup; } } //elements are < member, score > logarithm for (j = 0; j < elements; j++) { double newscore; score = scores[j]; //retflags is set to the flags variable in the previous section int retflags = flags; ele = c->argv[scoreidx+1+j*2]->ptr; //For each traversal, score is score, ele is member. Call zsetadd to insert zobj int retval = zsetAdd(zobj, score, ele, &retflags, &newscore); if (retval == 0) { addReplyError(c,nanerr); goto cleanup; } //According to retflags, i.e. whether an element is updated or newly added, or not processed (i.e. member exists, and //The score value is consistent with the new setting), update the corresponding count variables (these variables will be returned to the client finally) if (retflags & ZADD_ADDED) added++; if (retflags & ZADD_UPDATED) updated++; if (!(retflags & ZADD_NOP)) processed++; score = newscore; } server.dirty += (added+updated); //Through the flag in the command, different values are returned to the client reply_to_client: if (incr) { /* ZINCRBY or INCR option. */ if (processed) addReplyDouble(c,score); else addReply(c,shared.nullbulk); } else { /* ZADD. */ addReplyLongLong(c,ch ? added+updated : added); } //If there is an update or new addition, the corresponding notification of the watch key and the keyspace should be executed cleanup: zfree(scores); if (added || updated) { signalModifiedKey(c->db,key); notifyKeyspaceEvent(NOTIFY_ZSET, incr ? "zincr" : "zadd", key, c->db->id); } }
ZINCRBY
ZINCRBY key increment member
If the key exists, add an increment to the score of the corresponding member
Otherwise, directly set the score of key to increment
//Call the same function as zadd, equivalent to zadd key incr, and set incr flag void zincrbyCommand(client *c) { zaddGenericCommand(c,ZADD_INCR); }
ZCARD
ZCARD key
Returns the number of elements in an ordered collection
void zcardCommand(client *c) { robj *key = c->argv[1]; robj *zobj; //Find the value corresponding to the key if ((zobj = lookupKeyReadOrReply(c,key,shared.czero)) == NULL || checkType(c,zobj,OBJ_ZSET)) return; //Get the number of elements in zobj through zsetLength addReplyLongLong(c,zsetLength(zobj)); }
unsigned int zsetLength(const robj *zobj) { int length = -1; //If it's ziplist, get the length through the zzlLength function //If the value in the length field is less than uint16 "Max, the length is returned directly. Otherwise, it needs to traverse to get the length if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { length = zzlLength(zobj->ptr); //If it is skiplist, directly return ZSL - > length } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { length = ((const zset*)zobj->ptr)->zsl->length; } else { serverPanic("Unknown sorted set encoding"); } return length; }
ZCOUNT
ZCOUNT key min max
Returns the number of elements with score value between min and max in key
Where min and max can be added (, such as zcount key (5 (10
Left parenthesis means not included. Include without adding
void zcountCommand(client *c) { robj *key = c->argv[1]; robj *zobj; zrangespec range; int count = 0; /* Parse the range arguments */ //Determine the range. Write the maximum, minimum and whether they are included in the range structure if (zslParseRange(c->argv[2],c->argv[3],&range) != C_OK) { addReplyError(c,"min or max is not a float"); return; } /* Lookup the sorted set */ if ((zobj = lookupKeyReadOrReply(c, key, shared.czero)) == NULL || checkType(c, zobj, OBJ_ZSET)) return; //Determine whether the underlying code of zobj is ziplist or skiplist if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *zl = zobj->ptr; unsigned char *eptr, *sptr; double score; //Find the first element in the range /* Use the first element in range as the starting point */ eptr = zzlFirstInRange(zl,&range); /* No "first" element */ if (eptr == NULL) { addReply(c, shared.czero); return; } /* First element is in range */ //In ziplist, member and score are two entries, and after member, score is saved //The overall order is from small to large. When the score s are the same, they are arranged according to the dictionary order of member sptr = ziplistNext(zl,eptr); //So here we get the score from the next entry of the first element score = zzlGetScore(sptr); serverAssertWithInfo(c,zobj,zslValueLteMax(score,&range)); /* Iterate over elements in range */ //Iterate the ziplist. If the score meets the requirements, count + + and continue to iterate. Otherwise, jump out //Finally, count will be returned while (eptr) { score = zzlGetScore(sptr); /* Abort when the node is no longer in range. */ if (!zslValueLteMax(score,&range)) { break; } else { count++; zzlNext(zl,&eptr,&sptr); } } } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = zobj->ptr; zskiplist *zsl = zs->zsl; zskiplistNode *zn; unsigned long rank; //If it's a skip table, the first element is taken first /* Find first element in range */ zn = zslFirstInRange(zsl, &range); /* Use rank of first element, if any, to determine preliminary count */ if (zn != NULL) { //Get the ranking of the first element rank = zslGetRank(zsl, zn->score, zn->ele); count = (zsl->length - (rank - 1)); //If the maximum value is greater than the maximum value in zsl, this count is the number to find /* Find last element in range */ zn = zslLastInRange(zsl, &range); /* Use rank of last element, if any, to determine the actual count */ if (zn != NULL) { //If the maximum value is less than the maximum value in zsl, find the rank of the last element first rank = zslGetRank(zsl, zn->score, zn->ele); //Recalculate the count. After merging with the previous calculation formula //count = (zsl->length-(rankmin-1))-(zsl->length-rankmax)) // = rankmax-rankmin+1 count -= (zsl->length - rank); } } } else { serverPanic("Unknown sorted set encoding"); } //Return to count addReplyLongLong(c, count); }
ZRANGEBYSCORE
ZRANGEBYSCORE key min max WITHSCORES
Get all elements whose score is between min and max in an ordered combination
WithCores: return member and score together
limit offset count: get count elements from offset offset
min and max can be - inf,+inf, indicating negative infinity and positive infinity respectively
//Entry function void zrangebyscoreCommand(client *c) { genericZrangebyscoreCommand(c,0); }
/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE. */ void genericZrangebyscoreCommand(client *c, int reverse) { zrangespec range; robj *key = c->argv[1]; robj *zobj; long offset = 0, limit = -1; int withscores = 0; unsigned long rangelen = 0; void *replylen = NULL; int minidx, maxidx; //This function is used for zrangbyscore and zrevrangebyscore at the same time //They are identified by the reverse parameter in the function //In positive sequence, the second parameter is min, and the third parameter is max. in reverse sequence, the second parameter is min /* Parse the range arguments. */ if (reverse) { /* Range is given as [max,min] */ maxidx = 2; minidx = 3; } else { /* Range is given as [min,max] */ minidx = 2; maxidx = 3; } //Parse out the parameters and assign them to the range variable if (zslParseRange(c->argv[minidx],c->argv[maxidx],&range) != C_OK) { addReplyError(c,"min or max is not a float"); return; } /* Parse optional extra arguments. Note that ZCOUNT will exactly have * 4 arguments, so we'll never enter the following code path. */ if (c->argc > 4) { int remaining = c->argc - 4; int pos = 4; //Analysis of WithCores and limit parameters while (remaining) { if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) { pos++; remaining--; withscores = 1; } else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) { if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL) != C_OK) || (getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) { return; } pos += 3; remaining -= 3; } else { addReply(c,shared.syntaxerr); return; } } } /* Ok, lookup the key and get the range */ if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL || checkType(c,zobj,OBJ_ZSET)) return; //Process according to whether the underlying code of zset is ziplist or skiplist if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *zl = zobj->ptr; unsigned char *eptr, *sptr; unsigned char *vstr; unsigned int vlen; long long vlong; double score; /* If reversed, get the last node in range as starting point. */ //Take the last value or the first value respectively in positive or negative order if (reverse) { eptr = zzlLastInRange(zl,&range); } else { eptr = zzlFirstInRange(zl,&range); } /* No "first" element in the specified interval. */ if (eptr == NULL) { addReply(c, shared.emptymultibulk); return; } /* Get score pointer for the first element. */ serverAssertWithInfo(c,zobj,eptr != NULL); sptr = ziplistNext(zl,eptr); /* We don't know in advance how many matching elements there are in the * list, so we push this object that will represent the multi-bulk * length in the output buffer, and will "fix" it later */ replylen = addDeferredMultiBulkLength(c); /* If there is an offset, just traverse the number of elements without * checking the score because that is done in the next loop. */ //If there is an offset, offset the corresponding element first //Note that there are two pointers passed in zzlNext, which will offset one < member, score > pair at a time //Note that the initial value of offset here is 0. If it is not specified, it will not enter the loop here while (eptr && offset--) { if (reverse) { zzlPrev(zl,&eptr,&sptr); } else { zzlNext(zl,&eptr,&sptr); } } //If there is a limit, enter the loop. Take the limit times. The initial value of limit is - 1, even if it is not specified, it will enter the loop //Until eptr is null or break in the loop while (eptr && limit--) { score = zzlGetScore(sptr); /* Abort when the node is no longer in range. */ //break when out of range if (reverse) { if (!zslValueGteMin(score,&range)) break; } else { if (!zslValueLteMax(score,&range)) break; } /* We know the element exists, so ziplistGet should always succeed */ serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong)); //Take the corresponding value. It may be str, assigned to vstr, vlen in length, or integer, assigned to vlong rangelen++; if (vstr == NULL) { addReplyBulkLongLong(c,vlong); } else { addReplyBulkCBuffer(c,vstr,vlen); } //If the WithCores flag is set, the score is returned if (withscores) { addReplyDouble(c,score); } //Start iteration next node /* Move to next node */ if (reverse) { zzlPrev(zl,&eptr,&sptr); } else { zzlNext(zl,&eptr,&sptr); } } } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = zobj->ptr; zskiplist *zsl = zs->zsl; zskiplistNode *ln; //Same as ziplist, first find the start or end node /* If reversed, get the last node in range as starting point. */ if (reverse) { ln = zslLastInRange(zsl,&range); } else { ln = zslFirstInRange(zsl,&range); } /* No "first" element in the specified interval. */ if (ln == NULL) { addReply(c, shared.emptymultibulk); return; } /* We don't know in advance how many matching elements there are in the * list, so we push this object that will represent the multi-bulk * length in the output buffer, and will "fix" it later */ //When returning to the client, the number of elements will be returned first, but we don't know how many elements need to be returned here, so we will occupy the first position //replylen is a pointer to the len field replylen = addDeferredMultiBulkLength(c); /* If there is an offset, just traverse the number of elements without * checking the score because that is done in the next loop. */ //Processing offset. Forward or backward skip while (ln && offset--) { if (reverse) { ln = ln->backward; } else { ln = ln->level[0].forward; } } //Processing limit while (ln && limit--) { /* Abort when the node is no longer in range. */ if (reverse) { if (!zslValueGteMin(ln->score,&range)) break; } else { if (!zslValueLteMax(ln->score,&range)) break; } rangelen++; addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele)); if (withscores) { addReplyDouble(c,ln->score); } /* Move to next node */ if (reverse) { ln = ln->backward; } else { ln = ln->level[0].forward; } } } else { serverPanic("Unknown sorted set encoding"); } //If you have the WithCores parameter, the number of strings returned to the client is twice if (withscores) { rangelen *= 2; } //Put rangelen in the position pointed by replylen and return it to the client setDeferredMultiBulkLength(c, replylen, rangelen); }
ZRANK
ZRANK key member
Returns the rank of an element in an ordered set
Starting from 0, the element score is from low to high
zrevrank, element fraction from high to low
void zrankGenericCommand(client *c, int reverse) { robj *key = c->argv[1]; robj *ele = c->argv[2]; robj *zobj; long rank; //Find value zobj of ordered set by key if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL || checkType(c,zobj,OBJ_ZSET)) return; serverAssertWithInfo(c,ele,sdsEncodedObject(ele)); //Find ele in zobj (second parameter member) rank = zsetRank(zobj,ele->ptr,reverse); if (rank >= 0) { addReplyLongLong(c,rank); } else { addReply(c,shared.nullbulk); } } void zrankCommand(client *c) { zrankGenericCommand(c, 0); }
long zsetRank(robj *zobj, sds ele, int reverse) { unsigned long llen; unsigned long rank; llen = zsetLength(zobj); //ziplist traverses from the front to the back, comparing the elements in the entry with ele, and rank ing each time++ if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *zl = zobj->ptr; unsigned char *eptr, *sptr; eptr = ziplistIndex(zl,0); serverAssert(eptr != NULL); sptr = ziplistNext(zl,eptr); serverAssert(sptr != NULL); rank = 1; while(eptr != NULL) { if (ziplistCompare(eptr,(unsigned char*)ele,sdslen(ele))) break; rank++; zzlNext(zl,&eptr,&sptr); } if (eptr != NULL) { //If it is in reverse order, the len rank is the reverse rank if (reverse) return llen-rank; else return rank-1; } else { return -1; } //skiplist obtains the rank through zslGetRank. The specific process is to look up the skip table and add the span of the corresponding passing node } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = zobj->ptr; zskiplist *zsl = zs->zsl; dictEntry *de; double score; de = dictFind(zs->dict,ele); if (de != NULL) { score = *(double*)dictGetVal(de); rank = zslGetRank(zsl,score,ele); /* Existing elements always have a rank. */ serverAssert(rank != 0); //Reverse rank if (reverse) return llen-rank; else return rank-1; } else { return -1; } } else { serverPanic("Unknown sorted set encoding"); } }
ZREM
ZREM key member [member ...]
Remove the corresponding member from the ordered set
void zremCommand(client *c) { robj *key = c->argv[1]; robj *zobj; int deleted = 0, keyremoved = 0, j; //Find the corresponding zobj according to the key if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL || checkType(c,zobj,OBJ_ZSET)) return; //Delete the corresponding elements in turn. Check whether zset is empty after each deletion. If it is empty, delete the key, and break for (j = 2; j < c->argc; j++) { if (zsetDel(zobj,c->argv[j]->ptr)) deleted++; if (zsetLength(zobj) == 0) { dbDelete(c->db,key); keyremoved = 1; break; } } //If any member is deleted, notify the keyspace zrem event //If the entire zset is deleted, notify the keyspace del event if (deleted) { notifyKeyspaceEvent(NOTIFY_ZSET,"zrem",key,c->db->id); if (keyremoved) notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id); signalModifiedKey(c->db,key); server.dirty += deleted; } //Number of member s actually deleted returned to the client addReplyLongLong(c,deleted); }
/* Delete the element 'ele' from the sorted set, returning 1 if the element * existed and was deleted, 0 otherwise (the element was not there). */ int zsetDel(robj *zobj, sds ele) { if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *eptr; //ziplist first finds the pointer eptr of ele's location if ((eptr = zzlFind(zobj->ptr,ele,NULL)) != NULL) { //When the element is deleted. Ziplist is deleted, it will resize. Here, the pointer complex value of ziplist after deletion will be given to zobj - > PTR zobj->ptr = zzlDelete(zobj->ptr,eptr); return 1; } } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = zobj->ptr; dictEntry *de; double score; //skiplist now deletes the ele corresponding to zobj - > PTR - > dict. Not really deleted here //Instead, return the dictEntry where the ele is located de = dictUnlink(zs->dict,ele); if (de != NULL) { /* Get the score in order to delete from the skiplist later. */ //Get score through dictEntry score = *(double*)dictGetVal(de); /* Delete from the hash table and later from the skiplist. * Note that the order is important: deleting from the skiplist * actually releases the SDS string representing the element, * which is shared between the skiplist and the hash table, so * we need to delete from the skiplist as the final step. */ //Here, the key and value in dict are actually free dictFreeUnlinkedEntry(zs->dict,de); /* Delete from skiplist. */ //Delete the element from skiplist. ele is the sds shared by hash and skiplist. When deleting from skiplist //This sds will be released, so the element in dict must be deleted before the element in skiplist int retval = zslDelete(zs->zsl,score,ele,NULL); serverAssert(retval); //If the use rate of elements in the hash table is less than 10%, resize the dict if (htNeedsResize(zs->dict)) dictResize(zs->dict); return 1; } } else { serverPanic("Unknown sorted set encoding"); } return 0; /* No such element found. */ }