The underlying implementation of Redis ordered collection zset – ZipList (compressed list) and SkipList (skip list)

1. Description

zset is an ordered set, and the key of zset is non-repeatable.

2. Function

Commonly used for functions such as rankings

3. Use commands

1. Add: zadd

zadd [ redis_key ] [ zset_key ] [ zset_value ] …
example:

 zadd player 99 Xiaobai 87 Xiaohong 2.5 Wutiao
2. Delete: zrem

Example: Delete Xiaohong

 zrem player 小红
3. Increase scores: zincrby

zincrby [ redis_key ] [ zset_key ] [ zset_value ]
Example: Add Xiaobai’s 99 points + 100 points.

zincrby player 1 小白
4. Subscript range query: zrange

zrange [ redis_key ] [zset start subscript] [zset end subscript] [withscores]
Example: Traverse from the first to the last. Defaults to ascending in zset.

zrange player 0 -1
zrange player 0 -1 withscores

Without withscores, only the values of zset are output.
Add withscores to output the values and keys of zset.

5. Score range query: zrangebyscore

zrangebyscore [ redis_key ] [zset minimum score] [zset maximum score] [withscores]
Example: Query players with scores from 2 to 5. Ascending order.

zrangebyscore player 2 5
zrangebyscore player 2 5 withscores

If the minimum score and the maximum score swap places, descending order can be achieved.
Example: Query players with scores from 2 to 5. Descending order.

zrangebyscore player 5 2
6. Score range statistics: zcount

zcount [ redis_key ] [zset minimum score] [zset maximum score]
Example: Count the total number of people who match the score from 2 to 5

zcount player 2 5
7. Get the subscript: zrank

zrank [ redis_key ] [zset value]

zrank player 五条

You can achieve search ranking through this function.

8. Query the total number of zsets

**zcard [redis_key] **

zcard player

Related documents

4. Underlying data structure – ziplist and skiplist

The bottom layer of zset is implemented by ziplist and skiplist.
When the number of elements stored in zset <= 128 and the size of each element <= 64 bytes, use ziplist, otherwise use skiplist.

1. ziplist skip list structure
struct ziplist<T> {<!-- -->
int32 zlbytes;//Memory usage; used when reallocating compressed list memory or calculating the position of zlend.
int32 zltail_offset;
int16 zllength; //The number of nodes, when it is less than 65535, is the real number; when it is equal to 65535, it must be traversed and calculated.
T[] entries;
int8 zlend; //0xFF, used to mark the end of the compressed list
}
struct entry {<!-- -->
    int<var> prevlen; // The byte length of the previous entry
    int<var> encoding; // element type encoding
    optional byte[] content; // element content
}
2. skiplist structure
typedef struct zset{<!-- -->
     //jump table
     zskiplist *zsl;
     //Dictionary key saves member, value saves score;
     dict *dice;
} zset;
// Jump table
typedef struct zskiplist {<!-- -->
    struct zskiplistNode *header, *tail;
    unsigned long length; //skip table length
    int level; //Maximum level of jump table index node
} zskiplist;

// Jump table node
typedef struct zskiplistNode {<!-- -->
    sds ele; //member member
    double score; //score
    struct zskiplistNode *backward; //Previous node pointer
    struct zskiplistLevel {<!-- -->
        struct zskiplistNode *forward; //Next node pointer, used to implement multi-layer linked lists.
        unsigned long span; //Record the distance between two nodes, used to calculate rank ranking
    } level[];
} zskiplistNode;
 
Add logic
void zaddGenericCommand(redisClient *c, int incr) {<!-- -->

    static char *nanerr = "resulting score is not a number (NaN)";

    robj *key = c->argv[1];
    robj *ele;
    robj *zobj;
    robj *curobj;
    double score = 0, *scores = NULL, curscore = 0.0;
    int j, elements = (c->argc-2)/2;
    int added = 0, updated = 0;

    //The input score - member parameters must appear in pairs
    if (c->argc % 2) {<!-- -->
        addReply(c,shared.syntaxerr);
        return;
    }

    // Get all input score values
    scores = zmalloc(sizeof(double)*elements);
    for (j = 0; j < elements; j + + ) {<!-- -->
        if (getDoubleFromObjectOrReply(c,c->argv[2 + j*2], & amp;scores[j],NULL)
            != REDIS_OK) goto cleanup;
    }

    // Get the ordered collection object
    zobj = lookupKeyWrite(c->db,key);
    if (zobj == NULL) {<!-- -->
        //The ordered set does not exist, create a new ordered set
        if (server.zset_max_ziplist_entries == 0 ||
            server.zset_max_ziplist_value < sdslen(c->argv[3]->ptr))
        {<!-- -->
            zobj = createZsetObject();
        } else {<!-- -->
            zobj = createZsetZiplistObject();
        }
        //Associate objects to the database
        dbAdd(c->db,key,zobj);
    } else {<!-- -->
        //Object exists, check type
        if (zobj->type != REDIS_ZSET) {<!-- -->
            addReply(c,shared.wrongtypeerr);
            goto cleanup;
        }
    }

    // process all elements
    for (j = 0; j < elements; j + + ) {<!-- -->
        score = scores[j];

        // Sorted collection is ziplist encoded
        if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {<!-- -->
            unsigned char *eptr;

            // Find members
            ele = c->argv[3 + j*2];
            if ((eptr = zzlFind(zobj->ptr,ele, & amp;curscore)) != NULL) {<!-- -->

                // member already exists

                // Used when using ZINCRYBY command
                if (incr) {<!-- -->
                    score + = curscore;
                    if (isnan(score)) {<!-- -->
                        addReplyError(c,nanerr);
                        goto cleanup;
                    }
                }

                //When executing the ZINCRYBY command,
                // Or executed when the user modifies the member's score through ZADD
                if (score != curscore) {<!-- -->
                    // Delete existing elements
                    zobj->ptr = zzlDelete(zobj->ptr,eptr);
                    // reinsert element
                    zobj->ptr = zzlInsert(zobj->ptr,ele,score);
                    // counter
                    server.dirty + + ;
                    updated + + ;
                }
            } else {<!-- -->
                //The element does not exist, add it directly
                zobj->ptr = zzlInsert(zobj->ptr,ele,score);

                // Check the number of elements,
                // See if you need to convert the ZIPLIST encoding into an ordered set
                if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
                    zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);

                // Check the length of the newly added element
                // See if you need to convert the ZIPLIST encoding into an ordered set
                if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
                    zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);

                server.dirty + + ;
                added + + ;
            }

        // Sorted set is SKIPLIST encoded
        } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {<!-- -->
            zset *zs = zobj->ptr;
            zskiplistNode *znode;
            dictEntry *de;

            // encoding object
            ele = c->argv[3 + j*2] = tryObjectEncoding(c->argv[3 + j*2]);

            // Check if the member exists
            de = dictFind(zs->dict,ele);
            if (de != NULL) {<!-- -->

                //member exists

                //retrieve members
                curobj = dictGetKey(de);
                // Get the score
                curscore = *(double*)dictGetVal(de);

                // Execute when ZINCRYBY
                if (incr) {<!-- -->
                    score + = curscore;
                    if (isnan(score)) {<!-- -->
                        addReplyError(c,nanerr);

                        goto cleanup;
                    }
                }

                //When executing the ZINCRYBY command,
                // Or executed when the user modifies the member's score through ZADD
                if (score != curscore) {<!-- -->
                    // Delete the original element
                    redisAssertWithInfo(c,curobj,zslDelete(zs->zsl,curscore,curobj));

                    // reinsert element
                    znode = zslInsert(zs->zsl,score,curobj);
                    incrRefCount(curobj); /* Re-inserted in skiplist. */

                    //Update the score pointer of the dictionary
                    dictGetVal(de) = & amp;znode->score; /* Update score ptr. */

                    server.dirty + + ;
                    updated + + ;
                }
            } else {<!-- -->

                // If the element does not exist, add it directly to the jump list
                znode = zslInsert(zs->zsl,score,ele);
                incrRefCount(ele); /* Inserted in skiplist. */

                //Associate the element to the dictionary
                redisAssertWithInfo(c,NULL,dictAdd(zs->dict,ele, & amp;znode->score) == DICT_OK);
                incrRefCount(ele); /* Added to dictionary. */

                server.dirty + + ;
                added + + ;
            }
        } else {<!-- -->
            redisPanic("Unknown sorted set encoding");
        }
    }

    if (incr) /* ZINCRBY */
        addReplyDouble(c,score);
    else /* ZADD */
        addReplyLongLong(c,added);

cleanup:
    zfree(scores);
    if (added || updated) {<!-- -->
        signalModifiedKey(c->db,key);
        notifyKeyspaceEvent(REDIS_NOTIFY_ZSET,
            incr ? "zincr" : "zadd", key, c->db->id);
    }
}
//https://blog.csdn.net/qq_27198345/article/details/108674817