Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员,不同的是每个元素都会关联一个double类型的分数,redis通过分数来为集合中的成员进行从小到大的排序,有序集合的成员是唯一的,但分数(score)却可以重复。集合中最大的成员数为 2^32 - 1 。
1. 编码
有序集合对象的编码可以是ZIPLIST或SKIPLIST。
ZIPLIST编码的有序集合对象使用压缩列表作为底层实现,每个元素使用两个挨在一起的结点保存,第一个保存成员member,第二个保存分数score。
SKIPLIST编码的有序集合对象使用ZSET结构作为底层实现,包括了一个跳跃表,一个字典。zsl跳跃表按照分数值从小到大保存了集合的成员,跳跃表结点object属性保存成员,score属性保存成员的分数值。dict字典保存了成员到分数的映射,每个键值对都是一个集合成员,字典的键保存成员,字典的值保存成员的分数。
1 | typedef struct zset { |
使用字典和跳跃表保存有序集合成员是为了更好的优化操作。在获取成员和分数时,使用字典可以有效减少查询时间,在进行RANK等操作时,由于字典的无序,遍历查询效果很低,所以使用跳跃表的方式可以减少查询时间。
2. 编码转换
当有序集合对象满足下面条件时使用ZIPLIST编码:
- 有序集合对象存储的所有元素成员长度小于64字节。
- 有序集合对象存储的元素个数不超过128.
配置文件中zset-max-ziplist-value和zset-max-ziplist-entries可以修改其上限
3. 命令
命令 | 作用 |
---|---|
ZADD | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD | 获取有序集合的成员数 |
ZCOUNT | 计算在有序集合中指定区间分数的成员数 |
ZINCRBY | 有序集合中对指定成员的分数加上增量 increment |
ZINTERSTORE | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
ZLEXCOUNT | 在有序集合中计算指定字典区间内成员数量 |
ZRANGE | 通过索引区间返回有序集合成指定区间内的成员 |
ZRANGEBYLEX | 通过字典区间返回有序集合的成员 |
ZRANGEBYSCORE | 通过分数返回有序集合指定区间内的成员 |
ZRANK | 返回有序集合中指定成员的索引 |
ZREM | 移除有序集合中的一个或多个成员 |
ZREMRANGEBYLEX | 移除有序集合中给定的字典区间的所有成员 |
ZREMRANGEBYRANK | 移除有序集合中给定的排名区间的所有成员 |
ZREMRANGEBYSCORE | 移除有序集合中给定的分数区间的所有成员 |
ZREVRANGE | 返回有序集中指定区间内的成员,通过索引,分数从高到底 |
ZREVRANGEBYSCORE | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
ZREVRANK | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
ZSCORE | 返回有序集中,成员的分数值 |
ZUNIONSTORE | 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
ZSCAN | 迭代有序集合中的元素(包括元素成员和元素分值) |
4. 源码剖析
- 编码转换
1 | void zsetConvert(robj *zobj, int encoding) { |
- 添加成员
1 | int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) { |
- 删除成员
1 | int zsetDel(robj *zobj, sds ele) { |
- ZADD命令底层实现
1 | void zaddGenericCommand(client *c, int flags) { |