数据结构论坛

注册

 

发新话题 回复该主题

阿里资深专家聊Redis6系列05 [复制链接]

1#

莲花山公园-小平同志塑像

年,利用工作之余,我翻译了Redis3.0非稳定版的官方文档,在网络上被大量转载、推荐和盗链。6年时光白驹过隙,Redis6稳定版已经发布,增加了很多新特性,鉴于各种资料参差不齐,或陈旧或残缺或错误,于是抽空再倒腾下。

Redis有序集合(Sortedsets)

有序集合是类似于集合与哈希的混合体的一种数据类型。像集合一样,有序集合由唯一的、不重复的字符串元素组成,在某种意义上,有序集合也就是集合。

集合中的每个元素是无序的,但有序集合中的每个元素都关联了一个浮点值,称为分数(score,这就是为什么该类型也类似于哈希,因为每一个元素都映射到一个值)。

此外,有序集合中的元素是按序存储的(不是请求时才排序的,顺序是依赖于表示有序集合的数据结构)。它们按照如下规则排序:

如果A和B是拥有不同分数的元素,A.scoreB.score,则AB。

如果A和B是有相同的分数的元素,如果按字典顺序A大于B,则AB。A和B不能相同,因为有序集合只能有唯一元素。

让我们开始一个简单的例子,添加一些黑客的名字作为有序集合的元素,以他们的出生年份为分数。

zaddhackers"AlanKay"

(integer)1

zaddhackers"SophieWilson"

(integer)1

zaddhackers"RichardStallman"

(integer)1

zaddhackers"AnitaBorg"

(integer)1

zaddhackers"YukihiroMatsumoto"

(integer)1

zaddhackers"HedyLamarr"

(integer)1

zaddhackers"ClaudeShannon"

(integer)1

zaddhackers"LinusTorvalds"

(integer)1

zaddhackers"AlanTuring"

(integer)1

如你所见,ZADD命令类似于SADD,但是多了一个参数(位于添加的元素之前),即分数。ZADD命令也是可变参数的,所以你可以自由的指定多个分数-值对(score-valuepairs),尽管上面的例子中并没有使用到。

使用有序集合可以很容易地返回按照出生年份排序的黑客列表,因为他们已经是排序好的。

实现注意事项:有序集合是通过双端(dual-ported)数据结构实现的,包括跳跃表(skiplist,后续文章会详细介绍,作者注)和哈希表(hashtable),所以我们每次添加元素时Redis执行了一个O(log(N))时间复杂度的操作。这还好,但是当我们请求有序元素时,Redis根本不需要做什么工作,因为已经是全部有序的了:

zrangehackers0-1

1)"AlanTuring"

2)"HedyLamarr"

3)"ClaudeShannon"

4)"AlanKay"

5)"AnitaBorg"

6)"RichardStallman"

7)"SophieWilson"

8)"YukihiroMatsumoto"

9)"LinusTorvalds"

注意:0和-1表示从索引为0的元素到最后一个元素(-1像LRANGE命令中一样工作)。

如果我想按照相反的顺序排序,从最年轻到最年长呢?使用ZREVRANGE代替ZRANGE:

zrevrangehackers0-1

1)"LinusTorvalds"

2)"YukihiroMatsumoto"

3)"SophieWilson"

4)"RichardStallman"

5)"AnitaBorg"

6)"AlanKay"

7)"ClaudeShannon"

8)"HedyLamarr"

9)"AlanTuring"

也可以同时返回分数,使用WITHSCORES参数:

zrangehackers0-1withscores

1)"AlanTuring"

2)""

3)"HedyLamarr"

4)""

5)"ClaudeShannon"

6)""

7)"AlanKay"

8)""

9)"AnitaBorg"

10)""

11)"RichardStallman"

12)""

13)"SophieWilson"

14)""

15)"YukihiroMatsumoto"

16)""

17)"LinusTorvalds"

18)""

范围操作

有序集合远比这些要强大。它们可以在范围上操作。让我们获取年前出生的所有人。我们使用ZRANGEBYSCORE命令来办到:

zrangebyscorehackers-inf

1)"AlanTuring"

2)"HedyLamarr"

3)"ClaudeShannon"

4)"AlanKay"

5)"AnitaBorg"

我们要求Redis返回分数在负无穷到之间的所有元素(包括两个极端)。

也可以删除某个范围的元素。让我们从有序集合中删除出生于年到年之间的黑客:

zremrangebyscorehackers

(integer)4

ZREMRANGEBYSCORE也许不是最合适的命令名,但是非常有用,返回删除的元素数目。

另一个非常有用的操作是用来获取有序集合中元素排名的操作。也就是可以查询集合中元素所处的位置。

zrankhackers"AnitaBorg"

(integer)4

ZREVRANK命令用来按照降序返回元素的排名。

字典分数(Lexicographicalscores)

最近的Redis2.8版本中引入了一个新的特性,假定集合中的元素都具有相同的分数,则允许按字典顺序获取范围(元素按照C语言中的memcmp函数进行比较,因此可以保证没有排序规则,每个Redis实例都会有相同的输出)。

操作字典顺序范围的主要命令是ZRANGEBYLEX,ZREVRANGEBYLEX,ZREMRANGEBYLEX和ZLEXCOUNT。例如,我们再次添加我们的著名黑客清单。但是这次为每个元素使用0作为分数:

zaddhackers0"AlanKay"0"SophieWilson"0"RichardStallman"0

"AnitaBorg"0"YukihiroMatsumoto"0"HedyLamarr"0"ClaudeShannon"

0"LinusTorvalds"0"AlanTuring"

根据有序集合的排序规则,它们已经按照字典顺序排好了:

zrangehackers0-1

1)"AlanKay"

2)"AlanTuring"

3)"AnitaBorg"

4)"ClaudeShannon"

5)"HedyLamarr"

6)"LinusTorvalds"

7)"RichardStallman"

8)"SophieWilson"

9)"YukihiroMatsumoto"

使用ZRANGEBYLEX我们可以查询字典顺序范围:

zrangebylexhackers[B[P

1)"ClaudeShannon"

2)"HedyLamarr"

3)"LinusTorvalds"

范围可以是包含性的或者排除性的(取决于第一个字符,即开闭区间,作者注),+和-分别表示正无穷和负无穷。请查看该命令的文档以获取更详细信息(后续文章将详细介绍,作者注)。

这个特性非常重要,因为这允许有序集合作为通用索引。例如,如果你想用一个位无符号整数参数来索引元素,你需要做的就是以相同的分数(例如0)将元素添加到有序集合中,元素前缀为组成大端(bigendian)位数字的16个字节。由于数字是按照大端编码,按字典排序(原始raw字节顺序)其实就是按数字排序,你可以在位空间中查询范围,并通过丢弃前缀来获取元素的值。

如果你想在一个更正式的范例中了解该特性,可以看看Redis自动补全的范例(后续献上,作者注)。

更新分数:排行榜(leaderboards)

这一部分是开始新的主题前的最后一个关于有序集合的内容。有序集合的分数可以随时更新。对一个存在于有序集合中的元素再次调用ZADD,将会在O(log(N))时间复杂度内更新其分数(和位置),所以有序集合适合于经常更新的场合。

由于这个特性,通常的一个使用场景就是排行榜。最典型的应用就是facebook游戏,你可以组合使用按分数高低存储用户,以及获取排名的操作,来展示前N名的用户以及用户在排行榜上的排名(例如,你是第名最佳分数)。

位图(Bitmaps)

位图不是一个真实的数据类型,而是定义在字符串类型上的面向位的操作的集合。由于字符串类型是二进制安全的二进制大对象(blobs),并且最大长度是MB,适合于设置最多个不同的位。

位操作分为两组:常量时间单个位的操作,像设置一个位为1或者0,或者获取该位的值。对一组位的操作,例如计算指定范围位的置位数量。

位图的最大优势是,有时是一种非常显著的节省空间来存储信息的方式。例如,在一个系统中,不同用户由递增的用户ID来表示,可以使用MB的内存来表示万用户的单个位信息(例如他们是否需要接收信件)。

使用SETBIT和GETBIT命令来设置和检索位:

setbitkey

(integer)1

getbitkey10

(integer)1

getbitkey11

(integer)0

SETBIT命令把第一个参数作为位数,第二个参数作为要给位设置的值,0或者1。如果位的位置超过了当前字符串的长度,这个命令或自动扩充这个字符串。

GETBIT命令只是返回指定下标处的位的值。超出范围的位(指定的位超出了该键下字符串的长度)被认为是0。

有3个命令用来操作一组位:

1.BITOP命令对不同字符串执行逐位操作。提供的操作包括与、或、异或和非。

2.BITCOUNT命令执行计数操作,返回被置位为1的位的数量。

3.BITPOS命令找到第一个值为指定值(0或者1)的位。

BITPOS和BITCOUNT命令都可以操作字符串的字节范围,而不仅仅是运行于整个字符串长度。下面是BITCOUNT调用的一个简单例子:

setbitkey01

(integer)0

setbitkey

(integer)0

bitcountkey

(integer)2

位图的常用场景:

各种实时分析。

需要高性能和高效率的空间利用来存储与对象ID关联的布尔信息。

例如,假设你想知道你的网站的用户的最长日访问曲线。你从0开始计算天数,也就是你的网站可访问的那天,并且每当用户访问你的网站的时候,就用SETBIT命令设置一个位。你可以使用当前unix时间减去初始位移,然后除以*24,作为位的下标。

这种方式下每个用户都有一个记录每天访问信息的一个小字符串。使用BITCOUNT命令以及几次BITPOS调用,可以很容易获得指定用户访问网站的天数,或者只是获取并在客户端分析位图,也很容易计算出最长曲线。

位图可以很容易的拆分为多个键,例如,为了数据集分片,因为通常要避免使用很大的键。为了将位图拆分为不同的键,而不是将所有的位设置到一个键,一个简单的策略就是每个键只存储M位,并且使用位数除以M作为键名,在键中使用位数模M来定位第N位。

超重对数(HyperLogLogs)

超重对数是用于计算唯一事物数量的概率性数据结构(学术上指的是估算集合的基数)。通常,计算唯一项数量需要使用和你想计算的项成正比的大量内存,因为你需要记住你已经看到的元素,以避免被多次计算。然而,有一组用内存换精度的算法:你会得到一个估算的测量,伴随一个标准错误,在Redis的实现中误差低于1%,但是这些算法的魔力在于,你不再需要使用和你要计算的量成比例的大量内存,你只需要使用常量内存!最坏情况下12K字节,或者当你的超重对数(后续称它们为HLL)只发现了少量元素时更是省内存。

Redis中的超重对数,虽然技术上是一个不同的数据结构,但被编码为Redis字符串,所以你可以调用GET来序列化超重对数,使用SET反序列化回服务器。

从概念上讲,超重对数的API像是使用集合来做同样的事情。你会SADD每一个待观察的元素到集合,然后使用SCARD来检查集合中元素数量,这些元素都是唯一的,因为SCARD不会重复添加已经添加的元素。

你并没有真正添加项到超重对数中,因为这种数据结构只是包含了状态而没有包含真正的元素,其API也是一样:

每次你看到一个新元素,你就使用PFADD命令添加。

每次你想检索到目前为止当前近似的已添加进去的唯一元素数,你就使用PFCOUNT命令。

redisPFADDhllabcd

(integer)1

redisPFCOUNThll

(integer)4

这种数据结构的使用场景的一个例子是,计算每天搜索的唯一请求数。

Redis也可以执行超重对数的并集,更多信息请查看命令介绍页(将逐一揭晓,作者注)。

其他值得注意的特性

还有一些重要的RedisAPI没有在此文中探索,但是非常值得你

分享 转发
TOP
发新话题 回复该主题