暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Redis源码学习(28)-Redis中有序集合对象类型的实现(下)

马基雅维利incoding 2020-08-28
447

有序集合对象命令

ZADD与ZINCRBY命令

Redis提供了两个命令用于向一个有序集合之中插入元素或者修改元素的分值:

  1. ZADD命令,该命令的格式为:

    ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

    这个命令用于将所有指定成员添加到给定key
    所对应的有序集合之中,添加的时候,可以添加多个成员。如果待添加的元素已经存在于有序集合之中,那么会更新对应元素的分值。该命令在score
    member
    前支持一些参数:

    1. NX
      ,如果携带该参数,则只添加新的元素成员。

    2. XX
      ,如果携带该参数,则是只会更新已有的元素成员,但是这个参数与NX
      参数互斥。

    3. INCR
      ,如果携带这个参数,则会对元素的分值进行递增操作,如果给定的元素不存在,那么会向有序集合中插入这个新的元素,并指定其分值为0,然后在执行分值的递增操作。

    4. CH
      ,如果设置了这个参数,那么命令会返回被修改的元素的个数,而在默认的情况下,这个命令则会返回被新增的元素的数。

  2. ZINCRBY命令,该命令的格式为:

    ZINCRBY key increment member

    这个命令用于向key
    对应的有序集合之中的member
    元素的分值上递增increment
    分值。这个命令与携带了INCR
    参数的ZADD命令相似。

由于本质上ZINCRBY命令是一种特殊形式的ZADD命令,因此Redis对于这两个命令的实现,使用了统一的通用接口zaddGenericCommand
,而两个命令对应的函数都是通过调用zaddGenericCommand
来实现的。

void zaddGenericCommand(client *c, int flags);void zaddCommand(client *c);void zincrbyCommand(client *c);

zaddGenericCommand
这个函数中:

  1. 调用lookupKeyWrite
    在内存数据库中查找给定key
    对应的有序集合对象。

  2. 如果对象不存在,那么判断输入数据的大小,选择createZsetObject
    或者createZsetZiplistObject
    接口i来创建一个有序集合对象。

  3. 循环调用zsetAdd
    将客户端输入的数据更新到key
    所对应的有序集合之中。

ZREM命令

在了解有序集合对象插入新元素的命令之后,我们可以来看一个删除有序集合内元素的命令ZREM,这个名的格式为:

ZREM key member [member ...]

这个命令的含义为从key
对应的有序集合之中删除一个或者多个member
元素。参数member
所对应的元素可以不存在与有序集合之中,这个命令在执行成功之后,会返回被删除元素成员的个数。

void zremCommand(client *c);

zremCommand
这个函数是Redis用于实现ZREM命令的,在通过调用lookupKeyWriteOrReply
函数查找到key
对应的有序集合后,循环调用zsetDel
将成员元素从有序集合中删除,最后如果有序集合中成员个数为0的话,那么就会将其从内存数据库之中删除。

ZREMRANGEBYRANK、ZREMRANGEBYSCORE与ZREMRANGEBYLEX命令

在介绍过Redis中用于从有序集合中删除元素的命令ZREM之后,我们可以看一下Redis用于按照条件对有序集合进行批量删除的一组命令:

  1. ZREMRANGEBYRANK,这个命令用于从key
    所指定的有序集合中删除指定排名区间内的所有元素,这个命令的格式为:

    ZREMRANGEBYRANK key start stop

    在这个命令之中的下标start
    以及stop
    可以是从0开始的正向排名,也可以是以-1开始的反向排名。命令执行成功后,会返回被删除的成员的个数。

  2. ZREMRANGEBYSCORE,这个命令用于从key
    所指定的有序集合中删除指定分值区间内的所有成员,这个命令的格式为:

    ZREMRANGEBYSCORE key min max

    这个命令执行成功后,会返回被删除的成员的个数。

  3. ZREMRANGEBYLEX,这个命令用于从key
    所指定的有序集合中删除名称按照字典顺序区间内的所有成员,这个命令的格式为:

    ZREMRANGEBYLEX key min max

    执行这条命令的前提为,有序集合之中的所有的元素的分值必须一致,因为有序集合中,是优先按照分值排序,在分值相同的基础上,在按照元素的字典顺序进行排序的。因此,如果在一个分值不同的有序集合之中指向这个命令,将会导致错误产生。

void zremrangeGenericCommand(client *c, int rangetype);void zremrangebyrankCommand(client *c);void zremrangebyscoreCommand(client *c);void zremrangebylexCommand(client *c);

Redis对于上述的三个命令定义了一个通过的函数接口zremrangeGenericCommand
,同时还定义了三个类型,用于标记三种删除类型:

#define ZRANGE_RANK 0#define ZRANGE_SCORE 1#define ZRANGE_LEX 2

Redis在实现zremrangeGenericCommand
时,会执行下面4步的逻辑:

  1. zremrangeGenericCommand
    会根据rangetype
    类型,校验客户端输入的区间参数的合法性。

  2. Redis的内存数据库中查找key
    所对应的有序集合;如果是按照排名进行删除,那么会结合有序集合的长度,对排名进行检查。

  3. 根据有序集合对应的编码类型以及删除类型,分别调用前面提到的有序集合批量删除接口,对数据进行删除。

  4. 最终将删除的结果返回给客户端调用者。

ZUNIONSTORE与ZINTERSTORE命令

针对有序集合,Redis提供了两个用于处理集合交集与并集的命令:

  1. ZUNIONSTORE,这个命令的格式为: ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]

  2. ZINTERSTORE,这个命令的格式为: ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]

上述两个命令的用途是计算给定的numkeys
个有序集合交集或者并集结果,并将结果存储在destination
对应的有序集合之中,可以选择性的给出WEIGHTS
参数,用于设定计算分值时所需要的用到的权重因子,如果没有指定这个参数,那么默认的权重因子便是1;通过可选参数AGGREGATE
,我们可以指定不同有序集合中的相同成员在存储到目标集合中时分值要采用何种聚合方式,默认是SUM
,如果使用参数MIN
或者MAX
,目标集合中成员分值就是所有集合中最小或者最大的分值。

这里需要注意的是destination
集合会在操作结束之后被覆盖。

void zunionInterGenericCommand(client *c, robj *dstkey, int op);void zunionstoreCommand(client *c);void zinterstoreCommand(client *c);

ZRANGE与ZREVRANGE命令

Redis为有序集合的区间操作提供了两个命令:

  1. ZRANGE,这个命令的格式为:

    ZRANGE key start stop [WITHSCORES]

    这个命令用于返回在有序集合中的指定排序范围的所有成员。如果携带了WITHSCORES
    参数,那么这个函数会在返回成员元素之外,还会返回成员元素的分值。返回成员元素的顺序是按照分值递增的顺序来排序的。

  2. ZREVRANGE,这个命令的格式为:

    ZREVRANGE key start stop [WITHSCORES]

    这个命令的功能与ZRANGE相似,区别在于所返回的元素的顺序是按照分值递减的顺序来排序的。

void zrangeGenericCommand(client *c, int reverse);void zrangeCommand(client *c);void zrevrangeCommand(client *c);

函数zrangeGenericCommand
函数是上面这两个命令的实现基础:

  1. 对于使用OBJ_ENCODING_ZIPLIST
    实现有序集合,首先会调用ziplistIndex
    函数在底层压缩链表中定位到第一个元素的位置,然后循环调用 zzlPrev
    或者zzlNext
    来收集数据。

  2. 对于使用OBJ_ENCODING_SKIPLIST
    实现的有序集合,那么会通过zslGetElementByRank
    函数在底层的跳跃表之中定义到起始元素的位置,最后根据跳跃表的的性质,对跳跃表进行遍历,完成数据的收集。

ZRANGEBYSCORE与ZREVRANGEBYSCORE命令

除了基于排序的区间成员的获取命令,Redis还提供了两个基于分值的区间成员获取的命令ZRANGEBYSCORE以及ZREVRANGEBYSCORE命令:

  1. ZRANGEBYSCORE命令的格式为:

    ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

  2. ZREVRANGEBYSCORE命令的格式为:

    ZREVRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

上述这两个命令都是用于从key
所指定的有序集合返回落在min
以及max
区间内的成员元素,这两个命令都可以携带两个可选参数:

  1. WITHSCORES
    ,如果命令的调用者给出了这个参数,那么成团的分值也会一并返回。

  2. LIMIT
    ,这个可选参数主要用来对返回结果的个数进行限制,其中offset
    用于表示结果的起始位置,而count
    则用来表示返回结果的个数。

void genericZrangebyscoreCommand(client *c, int reverse);void zrangebyscoreCommand(client *c);void zrevrangebyscoreCommand(client *c);

函数genericZrangebyscoreCommand
是用来实现上述命令的基础,这个函数首先会给定客户端输入的区间参数,初始化了zrangespec
数据,根据前面介绍的四个函数:

  1. zzlLastInRange

  2. zzlFirstInRange

  3. zslLastInRange

  4. zslFirstInRange

通过上面这个四个函数,Redis可以使用zrangespec
在有序集合中查找属于区间的起始节点,进而通过遍历底层数据结构,收集数据,并通过下面这两个函数来判断是否完成收集:

  1. zslValueGteMin

  2. zslValueLteMax

ZRANGEBYLEX与ZREVRANGEBYLEX命令

最后Redis还提供了两个基于字典顺顺序获取有序集合区间成员数据的命令,ZRANGEBYLEX以及ZREVRANGEBYLEX这两个命令,这两个命令所操作的有序集合,其元素分值必须都是一致的。

  1. ZRANGEBYLEX这个命令的格式为:

    ZRANGEBYLEX key min max [LIMIT offset count]

  2. ZREVRANGEBYLEX这个命令的格式为:

    ZREVRANGEBYLEX key min max [LIMIT offset count]

void genericZrangebylexCommand(client *c, int reverse);void zrangebylexCommand(client *c);void zrevrangebylexCommand(client *c);

ZRANGEBYLEXZREVRANGEBYLEX这两个命令,是使用genericZrangebylexCommand
作为基础实现函数的,这个函数的逻辑基础与genericZrangebyscoreCommand
类似,因此在这里不做过多的讲解。

ZCOUNT命令

Redis使用ZCOUNT命令来获取有序集合之中落于指定分值之间的成员的个数,这个命令的格式为:

ZCOUNT key min max

void zcountCommand(client *c);

zcountCommand
这个函数会根据有序集合底层编码的不用选用不同的策略:

  1. OBJ_ENCODING_ZIPLIST
    ,对于只用压缩链表实现有序集合,会首先调用zzlFirstInRange
    找到区间的起始成员,在按照遍历的方式,对落在范围区间内的成员进行计数。

  2. OBJ_ENCODING_SKIPLIST
    ,对于采用跳跃表实现的有序集合,会调用zslFirstInRange
    以及zslLastInRange
    找到区间的第一个以及最后一个成员,然后通过zslGetRank
    来获取成团的排名值,通过两个排名值来计算区间内成员的个数。

ZLEXCOUNT命令

对于字典区间的计数,Redis也提供了一个命令ZLEXCOUNT,用获取有序集合中指定成员之间的成员的个数,因为这是基于字典顺序的操作,因此需要有序集合之中的每个成员的分值要都相同,这个命令的格式为:

ZLEXCOUNT key min max

void zlexcountCommand(client *c);

zlexcountCommand
这个函数在实现逻辑上与zcountCommand
相似,不同之处在调用处理zlexrangespec
的相关函数。

ZCARD命令

ZCARD命令用于获取给定的key
所对应的成员个数,该命令的格式为:

ZCARD key

void zcardCommand(client *c);

zcardCommand
在实现ZCARD命令时,是通过调用zsetLength
接口来实现的。

ZSCORE命令

ZSCORE命令用于返回有序集合中某个指定成员的分值。该函数的格式为:

ZSCORE key member

如果key
对应的有序集合不存在,或者member
不存在于有序集合之中,则该命令返回nil
值,否则成功的情况下,则会返回对应的成员的分值。

void zscoreCommand(client *c);

zscoreCommand
函数则是通过调用zsetScore
函数来实现命令的功能的。

ZRANK与ZREVRANK命令

对于有序集合的排名操作,Redis给出了两个命令:

  1. ZRANK,这个命令用于获取获取元素按照分值递增顺序的排名,其格式为:

    ZRANK key member

    这个命令所返回的排名是以0开始的,也就是说如果函数返回0,那么说明这个成员在当前的有序集合之中是最小的;同时对于一个不存在的成员,命令会返回一个空值nil

  2. ZREVRANK,这个命令用于获取元素按照分值递减排序的排名,这个命令的格式为:

    ZREVRANK key member

    这个命令所返回的排名依然是以0开始的,如果函数返回0,那么说明这个成员在当前的有序集合之中是最大的。

void zrankGenericCommand(client *c, int reverse);void zrankCommand(client *c);void zrevrankCommand(client *c);

zrankGenericCommand
是实现上述两个命令的基础,其内部是通过调用zsetRank
这个接口来实现获取元素成员排名操作的。

ZPOPMIN与ZPOPMAX命令

Redis对于有序集合给出了两个用于弹出数据的命令:

  1. ZPOPMIN,这个命令从key
    所对应的有序集合之中弹出最小的若干个成员。这个命令的格式为:

    ZPOPMIN key [count]

    该命令如果不给出count
    ,则默认为1,也就是弹出有序集合中最小的那个成员。该命令执行成功后,会返回弹出的成员。

  2. ZPOPMAX,这个命令从key
    所对应的有序集合之中弹出最大的若干个成员。这个命令的格式为:

    ZPOPMAX key [count]

    这个命令与ZPOPMIN类似,成功之后都是会返回弹出的成员。

上述的两个命令中的count
参数可以大于有序集合的元素个数,这种情况下,命令不会返回错误,只会将有序集合之中的全部元素弹出。

void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey, robj *countarg);void zpopminCommand(client *c);void zpopmaxCommand(client *c);

用于实现上述两个命令的函数genericZpopCommand
,其内部逻辑是相对简单的,因为无论是使用压缩链表还是跳跃表实现的有序集合,其底层数据结构中成员数据的存储都是有序的,因此弹出操作只需要在有序集合的头部或者尾部按照个数弹出元素就可以。

BZPOPMIN与BZPOPMAX命令

对于上面的ZPOPMIN以及ZPOPMAX命令,Redis还提供了带有阻塞功能的版本:

  1. BZPOPMIN命令,其命令格式为:BZPOPMIN key [key ...] timeout

  2. BZPOPMAX命令,其命令格式为:BZPOPMAX key [key ...] timeout

这两个命令都可以处理多个有序集合对象,在参数中的所有有序集合都是空的情况下,会阻塞客户端的连接,timeout
会指定客户端被阻塞的最大秒数,0则便是永久阻塞。当所有的集合并非都是为空的情况下,会按照命令key
的输入顺序从第一个非空的集合之中弹出最大或者最小的成员。

void blockingGenericZpopCommand(client *c, int where);void bzpopminCommand(client *c);void bzpopmaxCommand(client *c);

用于实现上述两个命令的函数blockingGenericZpopCommand
在给定的有序集合非空时,会直接调用genericZpopCommand
来将结果返回给客户端,而当所有的有序集合都为空的时候,则会用blockForKeys
函数阻塞客户端的请求,直到其中一个有序集合非空或者超时为止。

文章转载自马基雅维利incoding,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论