一、描述
zset是一个有序集合,zset的key不可重复。
二、作用
常用作排行榜等功能
三、使用命令
1. 增加 :zadd
zadd [ redis_key ] [ zset_key ] [ zset_value ] …
例:
zadd player 99 小白 87 小红 2.5 五条
2. 删除 :zrem
例:删除小红
zrem player 小红
3. 增加分数:zincrby
zincrby [ redis_key ] [ zset_key ] [ zset_value ]
例:将小白的99分 +1成100。
zincrby player 1 小白
4. 下标范围查询:zrange
zrange [ redis_key ] [zset开始下标] [zset结束下标] [withscores]
例: 从第一个遍历到最后一个。 在zset默认升序。
zrange player 0 -1
zrange player 0 -1 withscores
不加withscores ,只输出zset的values。
添加withscores ,输出zset的values 和 keys。
5. 分数范围查询:zrangebyscore
zrangebyscore [ redis_key ] [zset最小分数] [zset最大分数] [withscores]
例:查询2到5分的player。升序。
zrangebyscore player 2 5
zrangebyscore player 2 5 withscores
如果最小分数和最大分数互换位置,就可以实现降序。
例:查询2到5分的player。降序。
zrangebyscore player 5 2
6. 分数范围统计个数:zcount
zcount [ redis_key ] [zset最小分数] [zset最大分数]
例:统计符合2到5分总人数
zcount player 2 5
7. 获取下标:zrank
zrank [ redis_key ] [zset value]
zrank player 五条
可以通过这个功能,实现搜索排名。
8. 查询zset总数
**zcard [ redis_key ] **
zcard player
四、底层数据结构 —— ziplist 和 skiplist
zset底层由 ziplist 和 skiplist 实现。
当 zset中存的元素数量 <= 128 并且 每个元素大小 <= 64字节时,使用ziplist,否则使用skiplist。
1. ziplist 跳表结构
struct ziplist<T> {
int32 zlbytes;//内存占用;压缩列表内存重分配 或者计算 zlend 的位置时使用。
int32 zltail_offset;
int16 zllength; //节点数量, 小于65535时,就是真实数量; 等于65535时,必须遍历计算。
T[] entries;
int8 zlend; //0xFF,用于标记压缩列表的末端
}
struct entry {
int<var> prevlen; // 前一个 entry 的字节长度
int<var> encoding; // 元素类型编码
optional byte[] content; // 元素内容
}
2. skiplist跳表结构
typedef struct zset{
//跳跃表
zskiplist *zsl;
//字典 key的保存member,value保存score;
dict *dice;
} zset;
// 跳表
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length; //跳表长度
int level; //跳表索引节点 最大层数
} zskiplist;
// 跳表节点
typedef struct zskiplistNode {
sds ele; //member成员
double score; //分数
struct zskiplistNode *backward; //上个节点指针
struct zskiplistLevel {
struct zskiplistNode *forward; //下个节点指针,用于实现多层链表。
unsigned long span;//记录两个节点之间的距离,用于计算rank排名
} level[];
} zskiplistNode;
增加逻辑
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;
// 输入的 score - member 参数必须是成对出现的
if (c->argc % 2) {
addReply(c,shared.syntaxerr);
return;
}
// 取出所有输入的 score 分值
scores = zmalloc(sizeof(double)*elements);
for (j = 0; j < elements; j++) {
if (getDoubleFromObjectOrReply(c,c->argv[2+j*2],&scores[j],NULL)
!= REDIS_OK) goto cleanup;
}
// 取出有序集合对象
zobj = lookupKeyWrite(c->db,key);
if (zobj == NULL) {
// 有序集合不存在,创建新有序集合
if (server.zset_max_ziplist_entries == 0 ||
server.zset_max_ziplist_value < sdslen(c->argv[3]->ptr))
{
zobj = createZsetObject();
} else {
zobj = createZsetZiplistObject();
}
// 关联对象到数据库
dbAdd(c->db,key,zobj);
} else {
// 对象存在,检查类型
if (zobj->type != REDIS_ZSET) {
addReply(c,shared.wrongtypeerr);
goto cleanup;
}
}
// 处理所有元素
for (j = 0; j < elements; j++) {
score = scores[j];
// 有序集合为 ziplist 编码
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *eptr;
// 查找成员
ele = c->argv[3+j*2];
if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
// 成员已存在
// ZINCRYBY 命令时使用
if (incr) {
score += curscore;
if (isnan(score)) {
addReplyError(c,nanerr);
goto cleanup;
}
}
// 执行 ZINCRYBY 命令时,
// 或者用户通过 ZADD 修改成员的分值时执行
if (score != curscore) {
// 删除已有元素
zobj->ptr = zzlDelete(zobj->ptr,eptr);
// 重新插入元素
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
// 计数器
server.dirty++;
updated++;
}
} else {
// 元素不存在,直接添加
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
// 查看元素的数量,
// 看是否需要将 ZIPLIST 编码转换为有序集合
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
// 查看新添加元素的长度
// 看是否需要将 ZIPLIST 编码转换为有序集合
if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
server.dirty++;
added++;
}
// 有序集合为 SKIPLIST 编码
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
zskiplistNode *znode;
dictEntry *de;
// 编码对象
ele = c->argv[3+j*2] = tryObjectEncoding(c->argv[3+j*2]);
// 查看成员是否存在
de = dictFind(zs->dict,ele);
if (de != NULL) {
// 成员存在
// 取出成员
curobj = dictGetKey(de);
// 取出分值
curscore = *(double*)dictGetVal(de);
// ZINCRYBY 时执行
if (incr) {
score += curscore;
if (isnan(score)) {
addReplyError(c,nanerr);
goto cleanup;
}
}
// 执行 ZINCRYBY 命令时,
// 或者用户通过 ZADD 修改成员的分值时执行
if (score != curscore) {
// 删除原有元素
redisAssertWithInfo(c,curobj,zslDelete(zs->zsl,curscore,curobj));
// 重新插入元素
znode = zslInsert(zs->zsl,score,curobj);
incrRefCount(curobj); /* Re-inserted in skiplist. */
// 更新字典的分值指针
dictGetVal(de) = &znode->score; /* Update score ptr. */
server.dirty++;
updated++;
}
} else {
// 元素不存在,直接添加到跳跃表
znode = zslInsert(zs->zsl,score,ele);
incrRefCount(ele); /* Inserted in skiplist. */
// 将元素关联到字典
redisAssertWithInfo(c,NULL,dictAdd(zs->dict,ele,&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