数据结构
typedef struct zskiplistNode {
sds ele;
double score;
// 指向最下层的上一个节点
struct zskiplistNode * backward
// 层,代表跳跃表的一列,包含了每一层的这个节点
struct zskiplistLevel {
// 指向同一层的下一个节点
struct zskiplistNode * forward
unsigned long span;
} level[]; // index越大表示层级越高
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
typedef struct zset {
// 保存一个zset下 value 和 score
dict *dict;
// 指向跳跃表,对score进行排序
zskiplist *zsl;
} zset;
添加元素
命令: ZADD key score value
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
// 越界检查
serverAssert(!isnan(score));
x = zsl->header;
// 从高层往下遍历
for (i = zsl->level - 1; i >= 0; i--) {
rank[i] = i == (zsl->level - 1) ? 0 : rank[i + 1];
// 要插入的节点大于下一个节点的值,则往后走
// 值相同时根据字符长度比较
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
sdscmp(x->level[i].forward->ele, ele) < 0))) {
// 保留这个节点每一层的span
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
// 如果无法往后走了,走到下一层。记录这一层卡住的节点(新高度超过原有最高高度时使用)
update[i] = x
}
// 计算节点的层级
level = zslRandomLevel();
// 如果出现新的高度,将原来记录的节点指向新节点
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
zsl->level = level;
}
// 创建一个跳跃表节点
x = zslCreateNode(level, score, ele);
for (i = 0; i < level; i++) {
// 接链
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;
/* update span covered by update[i] as x is inserted here 更新范围由update [i]覆盖,因为x插入此处 */
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}
/* increment span for untouched levels 增加跨度*/
for (i = level; i < zsl->level; i++) {
update[i]->level[i].span++;
}
/* 更新后退指针 */
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->level[0].forward)
x->level[0].forward->backward = x;
else
zsl->tail = x;
zsl->length++;
return x;
}
获取得分
命令: ZSCORE key value
从数据结构上可以看到,由于zset中存在一个dict,获取一个value的得分,应该是直接从字典拿就可以了
int zsetScore(robj *zobj, sds member, double *score) {
if ( !zobj || !member) return C_ERR;
if ( zobj->encoding == OBJ_ENCODING_ZIPLIST) {
if (zzlFind(zobj->ptr, member, score) == NULL) return C_ERR;
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
// 从zset的字典成员中根据member直接获取entry
dictEntry *de = dictFind(zs->dict, member)
if (de == NULL) return C_ERR
// 从entry中获取score
*score = *(double *) dictGetVal(de);
} else {
serverPanic("Unknow sorted set encoding");
}
return C_OK
}
获取排名
从上面添加元素的过程中可以看到,一个节点的每一层,都会记录一个span,这个span其实就代表了它在这一层和上个节点的间隔。rank的计算就是通过查找元素的过程中累加这个间隔来得到的
unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {
zskiplistNode *x;
unsigned long rank = 0;
int i;
x = zsl->header;
// 从最顶层开始查找,这里和插入的过程比较类似
for (i = zsl->level - 1; i >= 0; i--) {
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
sdscmp(x->level[i].forward->ele, ele) <= 0))) {
// 往前走一个节点,就加一次间隔
rank += x->level[i].span;
x = x->level[i].forward;
}
if (x->ele && sdscmp(x->ele, ele) == 0) {
return rank;
}
}
return 0;
}
参考资料
《redis深度历险》
https://www.jianshu.com/p/c2841d65df4c
https://blog.csdn.net/idwtwt/article/details/80233859