莲花山公园-小平同志塑像
年,利用工作之余,我翻译了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没有在此文中探索,但是非常值得你