[Redis learning notes] July 11, 2018 Redis instruction learning 5

Posted by chemoautotroph on Sat, 08 Feb 2020 16:27:26 +0100

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. */
}

Topics: encoding less Redis