您的位置:首页 > 数据库 > Redis

Redis的数据类型和抽象概念的介绍

2015-05-01 13:36 357 查看
Redis不是一个简单的键值存储,事实上它是一个数据结构服务器,支持不同类型的值。这意味着在Redis中值并不限于一个简单的string,而是可以支持更复杂的数据结构,不像在传统的键值存储中,你只能关联一个String键到一个String值。下面是Redis支持的所有数据结构列表。这个文档会对这些数据结构分别进行讲解。

- Binary-safe strings。

- Lists: 按照插入顺序排序的String元素集合。底层是用链表实现。

- Sets: 不重复,不排序的String元素的集合。

- Sorted sets:和Set类似,但是每一个String元素关联一个浮点数值。这个数值被称为Score。元素总是通过他们的Score进行排序,所以不像Sets, 它可以获取一段范围的元素(例如, 你可以要求:给我前10个,或者后10个)。

- Hashes: Hash就是由关联值的字段构成的Map。字段和值都是string。这个与Ruby或Python的hash很类似。

- Bit arrays(或者简单称为Bitmap):像位数值一样通过特别的命令处理字符串:你可以设置和清除单独的bit, 统计所有bit集合中为1的数量,查找第一个设置或者没有设置的bit等等。

- HyperLogLogs: 这是一个概率统计的数据结构,可以被用来估计一个集合的基数。不要害怕,它比它看起来要简单的多。参考这个文档的HyperLogLog章节。

掌握这些数据类型的工作原理和通过阅读命令参考手册来解决问题的方法不是那么容易的,而这个文档是Redis数据类型和和大部分通用模式的一个速成课。

对于所有的例子,我们都使用redis-cli工具。这是一个简单但是非常有用的命令行工具,可以用来发送命令给Redis Server。

Redis keys

Redis Key是Binay safe。这意味着你可以使用任何二进制序列作为Key, 如从一个像"foo"的字符串到一个JPEG文件的内容。空字符串也是一个有效的Key。

关于Key的一些其它规则:

- 非常长的Key不是一个好主意,例如1024b的key是一个糟糕的主意,不仅从内存方面考虑, 而且在数据集中Key的查找可能需要和多个Key进行比较。即使当前的任务需要使用一个很大值,将它进行hash是一个更好的主意(例如,使用SHA1),尤其是从内存和带宽的角度考虑。

- 非常短的Key往往也不是一个好主意。如果你可以将Key写成"user:1000:followers",就不要使用"u1000flw"。前者更加具有可读性,而且增加的空间相比Key对象本身和值对象占用的空间是很小的。当然短Key显然会消耗更少的内存,你需要找到一个适当的平衡。

- 努力坚持使用模式。例如像"user:1000"这样的"object-type:id"模式是一个好主意。点和连接线通常被用在多个单词的字段,例如"comment:1234:reply.to"或者"comment:1234:reply-to"。

- 最大允许key的大小是512MB。

Redis Strings

Redis String类型是关联到Redis key最简单的值类型。它是Memcached中唯一数据类型,所以对于Redis新手,使用它也是非常自然的。

因为Redis Key是String, 所以当我们使用String类型作为value时, 其实就是将一个String映射到另一个String。String数据类型对于大量的用例是非常有用的,如缓存HTML片段或者页.

让我们一起使用redis-cli操作String类型,(在该文档中,所以的例子将通过redis-cli执行)

> set mykey somevalue

OK

> get mykey

"somevalue"

正如你看到的,使用SET和GET命令可以重置和获取String值。注意SET会替换已经存入到Key中的任何值,即使这个Key存在的不是String值。所以SET执行一次分配。

值可以是任何类型的String(包括binary data), 例如你可以存一个jpeg图片到一个Key中。值不能比512MB大。

SET命令有一些有趣的选项。这些选项可以通过额外的参数来设置。例如,我们可以要求在Key已经存在情况下,SET操作失败。或者相反,在这个key已经存在的情况下,操作成功。

> set mykey newval nx

(nil)

> set mykey newval xx

OK

String是Redis的基础值,你可以对它们进行一些有意思的操作。例如,进行原子递增:

> set counter 100

OK

> incr counter

(integer) 101

> incr counter

(integer) 102

> incrby counter 50

(integer) 152

INCR命令将String值解析为Integer, 然后将它递增1,最后将新值作为返回值。这里也有一些类似的命令,如INCRBY, DECR和DCRBY。在内部它们是相同的命令,并且执行方式的差别非常微小。

INCR命令是原子操作意味着什么?这表示即使多个客户端对同一个Key发送INCR命令也不会导致竞争条件(Race Condition)问题。例如,下面是永远不会发生的: 客户端1读到"10", 客户端2也同时读到"10",两个都增加到11,并设置新值为11。而使用INCR命令最终的值将总是12,并且在read-increment-set操作执行时,其它所有的客户端不会同时执行这个命令。

操作String有很多的命令。例如GETSET命令将一个Key设置为新值,并将旧值作为返回值。例如,你有一个系统。每次你的网站接收到新的访问者,则使用INCR命令递增一个Redis Key。你就可以使用GETSET命令来实现。你可能想每隔一个小时收集一次信息,并且需要不丢失每一次的递增。这样你就可以GETSET这个key,将新值0赋给它,并将旧值读回。

在一条命令中设置或者获取多个Key的值的能力对减少延时是非常有用的。正是这个原因,我们有MSET和MGET命令。

> mset a 10 b 20 c 30

OK

> mget a b c

1) "10"

2) "20"

3) "30"

当使用MSET时,Redis返回一个值数组。

修改和查询Key空间

这里有一些命令没有定义在具体的类型上,但在与key空间交互时非常有用。这些命令可以用于任何类型的Key。

例如,EXISTS命令返回1或者0来标志一个给定的Key是否在数据库中存在。DEL命令用来删除一个Key和关联的值,而不管这个值是什么。

> set mykey hello

OK

> exists mykey

(integer) 1

> del mykey

(integer) 1

> exists mykey

(integer) 0

通过这个例子,你也可以看到DEL命令根据Key是否被删除返回1或者0。

这里有很多与Key空间相关的命令,但是上面两个命令以及TYPE命令是非常关键的命令。TYPE命令返回指定key中存放值的类型。

> set mykey x

OK

> type mykey

string

> del mykey

(integer) 1

> type mykey

none

Redis失效:具有有限生存时间的key

在继续讲述更加复杂的数据结构之前,我们需要讨论另一个特性。这个特性可以用在任何一种值类型中,并被称为Redis失效。主要你可以给一个Key设置一个超时时间,这个超时时间就是有限的生存时间。当这个生存时间过去,这个key会自动被销毁。

一些关于Redis失效的快速信息:

- 他们设置时,可以使用秒或者毫秒精度。

- 失效时间分辨率总是1ms。

- 失效信息会被复制,并持久化到磁盘中。当你的Redis server停止(这意味着Rediskey的失效时间),这个时间在无形中度过(这意味 Redis保存了一个Key的失效时间)。

设置失效时间是轻而易举的:

> set key some-value

OK

> expire key 5

(integer) 1

> get key (immediately)

"some-value"

> get key (after some time)

(nil)

这个key在两次GET调用之间消失了,因为第二次调用延时超过了5秒。在上面的例子中,我们使用EXPIRE命令来设置超时时间(它也可以用来给一个已经设置超时时间的key设置一个不同的值。PERSIST可以用来删除失效时间,并将Key永远持久化)。当然我们也可以使用其它的Redis命令来创建带失效时间的key。例如,使用SET选项:

> set key 100 ex 10

OK

> ttl key

(integer) 9

上面例子中设置Key值为String 100, 并带有10秒的超时时间。之后,使用TTL命令检测这个Key的剩余生存时间。

如果你想知道如何以毫秒级设置和检测超时时间,查看PEXPIRE和PTTL命令,和SET选项列表。

Redis Lists

解释List数据类型最好先从一些理论开始,因为术语List经常被信息技术的人们以不正确的方式使用。例如 "Python List"并不是这个名字所使人想起的数据类型(Linked List),其实它是一个数组(事实上,同样的数据类型在Ruby中称为Array)。

一个普遍的观点认为,List仅是有序元素序列:10,20,1,2,3是一个List。但是使用Array实现的List和使用Linked List实现的List,它们的特性有很大的不同。

Redis List是通过Linked List实现。这意味着即使成千上万的元素在一个列表中,在列表头和尾增加一个元素的操作是在一个常量时间完成。使用LPUSH命令增加一个新元素到一个具有10个元素的列表头的速度和增加一个元素到有千万元素的列表头是一样的。

这样做的负面影响是什么?在使用数组实现的列表中,使用Index访问一个元素是非常快的(常量时间Index访问),而在使用Linked List实现的List中不是那么快的(这个操作需要的工作量和被访问元素的Index成正比)

Redis列表使用Linked List实现,因为对于数据库系统,能够快速增加一个元素到一个非常长的列表中是非常关键的。

你过一会儿将会看到,Redis Lists的另一个重要优势是可以在常量时间内获取一个固定长度子List。

当快速访问一个庞大元素集合的中间值是非常重要时,可以使用另一个数据结构。它称为Sorted Set。 Sorted Set将在本文的后续章节讲到。

使用Redis List的第一步

LPUSH命令将一个新元素从左边加入到列表中,而RPUSH命令将一个新元素从右边加入到列表中。

> rpush mylist A

(integer) 1

> rpush mylist B

(integer) 2

> lpush mylist first

(integer) 3

> lrange mylist 0 -1

1) "first"

2) "A"

3) "B"

注意LRANGE带有两个Index, 分别是返回范围的开始和结束。这两个Index都可以是负数,告诉Redis从后边开始计数:-1表示最后一个元素,-2表示倒数第二个元素,以此类推。

正如你看到的, RPUSH将元素附加到列表右边,而LPUSH将元素附加到左边。

两个命令都是可变长参数列表(Variadic)命令。这意味着你可以在一次调用中将多个元素插入到列表中。

> rpush mylist 1 2 3 4 5 "foo bar"

(integer) 9

> lrange mylist 0 -1

1) "first"

2) "A"

3) "B"

4) "1"

5) "2"

6) "3"

7) "4"

8) "5"

9) "foo bar"

Redis List的一个重要操作是pop元素的能力。pop元素是指从List中取出元素,并同时将它从List中删除的操作。你可以从左边和右边pop元素。这和你从List的两侧Push元素是类似的。

> rpush mylist a b c

(integer) 3

> rpop mylist

"c"

> rpop mylist

"b"

> rpop mylist

"a"

我们加入了三个元素,并Pop了三个元素,所以在这些命令执行完后,这个列表是空的,并且没有更多的元素可以Pop。如果我尝试再Pop一个元素,下面就是我们获得的结果:

> rpop mylist

(nil)

Redis返回NULL值来表示已经没有元素在List中。

List常见的用例

List对于一些任务是非常有用的。两个非常典型用例如下:

- 记录用户Post到社区网络的最新更新。

- 使用消费者-生产者模式进行进程间通信。生产者推送item到List中,消费者消费这些item并执行操作。Redis有专门的List命令使这个用例更加可靠和高效。

例如热门的Ruby库resque和sidekiq在底层就是使用Redis列表实现后台Job.

热门的Twitter社交网络将用户最新Post的tweet放入到Redis List中。

为了一步步描述一个常见的用例, 设想你的主页展示一个图片共享社交网站上发布的最新图片,并且你想提高访问速度。

- 每次一个用户Post一张新的图片,我们使用LPUSH将它的ID加入到一个List中。

- 当用户访问这个home page时, 我们使用LRANGE 0 9来获得最近上传的10个Item。

Capped lists

在很多的用例中,我们仅需要使用List保存最近的元素,并且不管他们是什么:社交网络的更新,日志,或者其他任何事。

Redis允许我们使用List作为capped集合, 使用LTRIM命令来仅记住最近N个元素并丢失所有最久的Item。

LTRIM命令和LRANGE类似,但它设置这个范围作为新的List值,而不是展示指定范围的元素。所有在给定范围之外的元素会被删除。

通过下面的例子,我们可以使它更清晰:

> rpush mylist 1 2 3 4 5

(integer) 5

> ltrim mylist 0 2

OK

> lrange mylist 0 -1

1) "1"

2) "2"

3) "3"

上面的LTRIM命令告诉Redis仅取从index 0到2的列表元素,其它的会被丢弃。这允许一个非常简单但又很有用的模式:执行一个List推送操作 + 一个List截断操作,来增加一个新元素,并丢弃超过限制的元素。

LPUSH mylist <some element>

LTRIM mylist 0 999

上面的组合增加一个新元素,并取1000个最新的元素放入这个List。通过LRANGE命令,你可以访问最前面的Item,而不需要记住非常旧的数据。

注意:LRANGE是一个O(N)的命令,访问向列表头或者尾的小范围元素是一个常量时间操作。

List上的阻塞操作

List有一个特别的特性, 非常适合用来实现队列,并且它也是内部进程通信系统的一个基本构件:阻塞操作。

设想你想使用一个进程将Item推送到一个列表中,并且使用一个不同的进程在这些Item上做一些工作。这是一个普通的生成者/消费者组合,并且可以通过下面简单的方法来实现。

- 为了推送Item到这个List中,生产者调用LPUSH命令。

- 为了从列表中提取/处理Item, 消费者调用RPOP命令。

然而,有时这个列表有可能是空的,没有什么需要处理,所以RPOP仅返回NULL。在这种情况下,消费者强制等待一些时间,并使用RPOP进行重试。这种方式称为拉(polling), 并且在这个场景下这不是一个好主意,因为它有下面几个缺点:

- 迫使Redis和客户端处理无用的命令(所有的请求在List为空的时候没有实际工作要做,他们仅是返回NULL)

- 增加了Item处理延时,因为在一个工作者接收到NULL之后,它等待了一段时间。为了使延时更小,我们可以在调用RPOP之间等待更短时间,但负面影响是放大了问题1,例如,更多无用的Redis调用。

所以Redis实现了BRPOP和BLPOP命令。他们是RPOP和LPOP的另一个版本,可以在List为空的时候进行阻塞:仅当一个新的元素加入到列表中,或者用户指定的超时时间到达时,他们才会返回给调用者。

这是一个我们可以在工作者中调用BRPOP的例子:

> brpop tasks 5

1) "tasks"

2) "do_something"

它的意思是:“等待在列表tasks中的元素, 但是如果5秒钟过后,还没有元素有效则返回”。

注意你可以使用0作为超时时间表示永久等待元素。并且你也可以指定多个List而不仅是一个。这样就可以在同一时间等待多个List,并且当第一个List接收到元素时获得通知。

关于BRPOP的一些注意事项:

- 客户端按照顺序被服务:当某个客户端推送一个元素时,第一个阻塞等待List的客户端会被最先服务。

- BRPOP的返回值是和RPOP不同的:它是两个元素的数组。数组中除了新加入的元素,还有Key的名称,这是因为BRPOP和BLPOP能够等待多个队列中的元素。

- 如果超时时间到达,则返回NULL。

关于List和阻塞操作,你需要知道更多的事情。我们建议你阅读下面的更多信息。

- 可以使用RPOPLPUSH构建安全队列或者循环队列。

- 还有一个这个命令的阻塞版本,称为BRPOPLPUSH。

Key的自动创建和删除

到现在,在我们的例子中,我们从没有在推送元素之前必须创建空列表,或者当他们不再有元素在里面的时候删除空列表。当List为空时,Redis会负责删除这个Key;当Key不存在时,而我们正在尝试增加一个新元素给它,Redis会负责创建一个空列表。

这不是List特有的,它对所有包含多个元素的Redis数据类型都有效--Sets, Sorted Sets和Hashes。

基础上我们可以用下面三个规则来总结这个行为:

- 当我们增加一个元素到一个聚合数据类型,如果目标Key不存在,那么在增加元素之前被自动创建一个空的聚合数据类型。

- 当我们从一个聚合数据类型中删除元素,如果值为空,那么这个Key会自动销毁。

- 对于一个空key,调用一个只读命令,如LLEN(返回列表的长度),或者调用一个删除元素的写命令,总是和持有空聚合类型的Key产生一样的结果。

规则1的例子

> del mylist

(integer) 1

> lpush mylist 1 2 3

(integer) 3

然而,我们不能对Key中存在的错误类型执行操作:

> set foo bar

OK

> lpush foo 1 2 3

(error) WRONGTYPE Operation against a key holding the wrong kind of value

> type foo

string

规则2的例子:

> lpush mylist 1 2 3

(integer) 3

> exists mylist

(integer) 1

> lpop mylist

"3"

> lpop mylist

"2"

> lpop mylist

"1"

> exists mylist

(integer) 0

在所有的元素pop之后,这个key不再存在。

规则3的例子:

> del mylist

(integer) 0

> llen mylist

(integer) 0

> lpop mylist

(nil)

Redis Hashes

Redis hash正是我们期望的那样,具有field-value对:

> hmset user:1000 username antirez birthyear 1977 verified 1

OK

> hget user:1000 username

"antirez"

> hget user:1000 birthyear

"1977"

> hgetall user:1000

1) "username"

2) "antirez"

3) "birthyear"

4) "1977"

5) "verified"

6) "1"

Hash可以非常方便地表示对象,事实上你能放入到Hash中字段的数量是没有限制的(除了可用内存),所以你可以在你的应用中以不同的方式使用Hash。

命令HMSET可以用来设置Hash的多个字段,HGET获取单个字段。HMGET和HGET类似,但是返回一个值数组:

> hmget user:1000 username birthyear no-such-field

1) "antirez"

2) "1977"

3) (nil)

这里也有命令能够在单个字段上执行操作,如HINCRBY:

> hincrby user:1000 birthyear 10

(integer) 1987

> hincrby user:1000 birthyear 10

(integer) 1997

你可以在文档中找到Hash命令的完整列表。

值得注意的是小Hash(例如,一些带有小值的元素)在内存中使用特殊方式编码,这样使他们内存使用非常高效。

Redis Sets

Redis Set是String的无序集合。SADD命令增加新元素到一个Set中。还有一些针对Set的其它操作,如检查一个元素是否已经存在,在多个Set上执行交、并或者差运算等等。

> sadd myset 1 2 3

(integer) 3

> smembers myset

1. 3

2. 1

3. 2

这里,我们加入三个元素到我的Set中,并且告诉Rediis返回所有元素。正如你看到的,他们是无序的---在每次调用时,Redis以任意顺序返回元素,没有和用户有任何关于元素顺序的约定。

Redis有用来测试成员关系的命令。一个给定的元素是否存在?

> sismember myset 3

(integer) 1

> sismember myset 30

(integer) 0

"3"是Set的元素,而"30"不是。

Set善于用来表达对象间关系。例如我们可以很容易用Set来实现打标。

建模这个问题的一个简单方法是为我们想打标的每一个对象构建一个Set。这个Set包含关联这个对象的Tag ID。

设想我们想打标News。如果我们的news ID使用1, 2, 5和77进行打标,我们就可以使用一个Set关联我们的Tag ID和News Item。

> sadd news:1000:tags 1 2 5 77

(integer) 4

可是有时我可能也想有反向关系: 所有News的列表使用一个给定的Tag来打标。

> sadd tag:1:news 1000

(integer) 1

> sadd tag:2:news 1000

(integer) 1

> sadd tag:5:news 1000

(integer) 1

> sadd tag:77:news 1000

(integer) 1

获得一个给定对象的所有Tag是非常简单的:

> smembers news:1000:tags

1. 5

2. 1

3. 77

4. 2

注意:在这个例子中,我们设想你有另一个数据结构,例如一个Redis Hash, 这个Hash将Tag ID映射到Tag名称上。

这里也有其它一些比较复杂的操作,仍可以很简单的使用恰当的Redis命令来实现。例如,我们可能想得到所有带有Tag 1, 2, 10和27的对象列表。我们可以使用SINTER命令来做到这一点,这个命令可以在不同的Set上做交集。我可以使用:

> sinter tag:1:news tag:2:news tag:10:news tag:27:news

... results here ...

交集操作不是唯一可以执行的操作,你也可以执行并,差和提出一个随机元素等操作。

提取元素的命令是SPOP, 并且对于建模这种问题是非常容易的。例如,为了实现一个Web的游戏,你可能想使用Set来表示。设想我们使用一个字母前缀表示(C)lubs, (D)iamonds, (H)earts, (S)pades:

> sadd deck C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 CJ CQ CK

D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 DJ DQ DK H1 H2 H3

H4 H5 H6 H7 H8 H9 H10 HJ HQ HK S1 S2 S3 S4 S5 S6

S7 S8 S9 S10 SJ SQ SK

(integer) 52

现在我们想提供给每一个玩家5张牌。SPOP命令删除一个随机元素,并将它返回给客户端,所以在这种情况下,这是完美的操作。

然而如果我们对我们的牌直接调用它, 在这个游戏的下一次玩的时候, 我们将需要再次获得一副牌。这可能就不是完美的。所以在开始的时候,我们可以拷贝deck Key中的Set到game:1:deck Key中。

这可以使用SUNIONSTORE完成。这个命令是执行多个Set的并操作,并保存结果到另一个Set中。然而,一个单独Set的并操作是它自己,我们可以通过下面操作拷贝我的deck:

> sunionstore game:1:deck deck

(integer) 52

现在,我们已经可以提供给第一个玩家5张牌:

> spop game:1:deck

"C6"

> spop game:1:deck

"CQ"

> spop game:1:deck

"D1"

> spop game:1:deck

"CJ"

> spop game:1:deck

"SJ"

一对J, 不大

现在我们可以介绍获取Set内元素数量命令。这个通常被称为一个Set的基数,在Set理论上下文中,对应的Redis命令称为SCARD。

> scard game:1:deck

(integer) 47

数学计算: 52 - 5 = 47.

当你仅需要获取随机元素,而不需要从Set中删除它们,可以使用SRANDMEMBER命令。它也提供返回repeating和non-repeating元素的能力。

Redis Sorted sets

Sorted Set是和Set和Hash的混合体很类似的一种数据类型。和Set一样, Sorted Set由唯一、不重复的String元素组成。所以在某种意义上, Sorted Set也是Set。

然而,Set中的元素是无序的,而Sorted Set中每一个元素关联一个称为分数(Score)的浮点值(这就是为什么这个类型也和Hash类似,因为每一个元素都映射到一个值)。

此外, 可以有序获取Sorted Set中元素(所以你不需要明确要求Sorted Set进行排序,排序是Sorted Set的特性)。他们根据下面的规则排序:

- 如果A和B是具有不同分数的元素,那么如果A.score > B.score, 则A>B。

- 如果A和B确实有相同的分数, 那么如果A String比B的字典顺序大,则A>B。 A和B String是不可能相等的,因为Sorted Sets不会包含重复元素。

让我们从一个简单的例子开始,将一些选定的黑客名称作为元素增加到Sorted Set中,他们的生日年份作为Score。

> zadd hackers 1940 "Alan Kay"

(integer) 1

> zadd hackers 1957 "Sophie Wilson"

(integer 1)

> zadd hackers 1953 "Richard Stallman"

(integer) 1

> zadd hackers 1949 "Anita Borg"

(integer) 1

> zadd hackers 1965 "Yukihiro Matsumoto"

(integer) 1

> zadd hackers 1914 "Hedy Lamarr"

(integer) 1

> zadd hackers 1916 "Claude Shannon"

(integer) 1

> zadd hackers 1969 "Linus Torvalds"

(integer) 1

> zadd hackers 1912 "Alan Turing"

(integer) 1

正如你看到的,ZADD和SADD类似,但是带有一个额外的参数(放置在被增加元素的前面),这就是Score。ZADD也是可变长列表的(Variadic), 所以你可以随意指定多个score-value对,即使在上面的例子中没有使用。

返回以黑客生日年份排序的黑客列表是非常容易的,因为事实上他们已经排序。

实现注意事项:Sorted Set通过一个dual-ported数据结构,包含一个skip列表和一个hash表,所以每次我们增加一个元素,Redis执行一个O(log(N))的操作。这是非常好的实践,因为当我们请求排序的元素,Redis完全不需要做任何工作,所有的都已经排序。

> zrange hackers 0 -1

1) "Alan Turing"

2) "Hedy Lamarr"

3) "Claude Shannon"

4) "Alan Kay"

5) "Anita Borg"

6) "Richard Stallman"

7) "Sophie Wilson"

8) "Yukihiro Matsumoto"

9) "Linus Torvalds"

注意:0和-1表示元素index 0到最后一个元素(-1在这里和LRANGE命令中的是一样的)

如果我想反向排序他们,最年轻的到最老的?使用ZREVRAGE代替ZRANGE:

> zrevrange hackers 0 -1

1) "Linus Torvalds"

2) "Yukihiro Matsumoto"

3) "Sophie Wilson"

4) "Richard Stallman"

5) "Anita Borg"

6) "Alan Kay"

7) "Claude Shannon"

8) "Hedy Lamarr"

9) "Alan Turing"

我们也可以返回Score, 使用WITHSCORES参数:

> zrange hackers 0 -1 withscores

1) "Alan Turing"

2) "1912"

3) "Hedy Lamarr"

4) "1914"

5) "Claude Shannon"

6) "1916"

7) "Alan Kay"

8) "1940"

9) "Anita Borg"

10) "1949"

11) "Richard Stallman"

12) "1953"

13) "Sophie Wilson"

14) "1957"

15) "Yukihiro Matsumoto"

16) "1965"

17) "Linus Torvalds"

18) "1969"

Operating on ranges

Sorted Set比上面提到的更强大。他们可以对一个范围进行操作。让我们获得所有1950年和之前出生的人。我们可以使用ZRANGEBYSCORE命令做到这点:

> zrangebyscore hackers -inf 1950

1) "Alan Turing"

2) "Hedy Lamarr"

3) "Claude Shannon"

4) "Alan Kay"

5) "Anita Borg"

我们要求Redis返回所有从负无穷到1950之间Score的所有元素(两个极端都包含)。

我们也可以删除一个区域的所有元素。让我们从Sorted Set中删除在1940到1960之间出生的所有黑客:

> zremrangebyscore hackers 1940 1960

(integer) 4

ZREMRANGEBYSCORE可能不是最好的命令名称,但是它非常有用。它的返回值是删除元素的数量。

Sorted Set的另一个非常有用的命令是获取位置操作。可能用来查询一个元素在Sorted Set中的排序位置。

> zrank hackers "Anita Borg"

(integer) 4

ZREVRANK命令也是可以获得元素位置,但是元素反序排列的位置。

Lexicographical scores

在最近Redis 2.8版本中引入了一个新的特性,获取字典顺序(Lexicographical)范围。 可以设想这个特性就是在Sorted Set中所有元素插入时带有相同的Score(元素使用C memcmp函数比较,所以保证没有冲突,并且每一个Redis实例回复相同的输出)

字典顺序范围操作的主要命令有ZRANGEBYLEX, ZREVRANGEBYLEX, ZREMRANGEBYLEX和ZLEXCOUNT.

例如, 让我们再次加入著名黑客列表,但是这次对所有元素使用Score 0。

> zadd hackers 0 "Alan Kay" 0 "Sophie Wilson" 0 "Richard Stallman" 0

"Anita Borg" 0 "Yukihiro Matsumoto" 0 "Hedy Lamarr" 0 "Claude Shannon"

0 "Linus Torvalds" 0 "Alan Turing"

因为这个Sorted Set的排序规则,他们已经以字典顺序排序:

> zrange hackers 0 -1

1) "Alan Kay"

2) "Alan Turing"

3) "Anita Borg"

4) "Claude Shannon"

5) "Hedy Lamarr"

6) "Linus Torvalds"

7) "Richard Stallman"

8) "Sophie Wilson"

9) "Yukihiro Matsumoto"

通过使用ZRANGEBYLEX,我们可以得到字典顺序范围:

> zrangebylex hackers [B [P

1) "Claude Shannon"

2) "Hedy Lamarr"

3) "Linus Torvalds"

Ranges可以设置包含和排除规则(根据第一个character)。string infinite和minus infinite分别使用+和-来指定。更多信息参考文档。

这个特性很重要,因为它允许我们将Sorted set用着通用的index。例如,如果你想通过一个128-bit无符号整形参数来Index一个元素,你可以将Sorted Set中元素设置成相同的Score(例如0), 但在元素前面加上一个16byte前缀。这16 byte由128 bit数字以大端字节序组成。 因为数字是大端字节序, 当按照字典顺序排序事实上也是按照数字排序。你可以获取在128bit空间中的任何范围,并且获得元素的值,然后抛弃这个前缀。

如果你想在一个更加正式的demo中查看这个特性,参考Redis autocomplete demo。

更新Score: 积分榜

在到下一个话题之前,这是最后一个关于Sorted Set的注意事项。Sorted Set的分数可以在任何时候都可以被修改。仅需要在Sorted Set中包含的元素上调用ZADD命令,就可以更新它的值(和位置)。其时间复杂度是O(log(N))。正是这样,Sorted Set也适用在有大量更新的场景。

正因这个特性,一个常见的用例是积分榜。 Facebook游戏就是一个经典的应用。你结合按最高分排序用户的能力,和获取位置的操作,来显示top-N用户以及在积分榜上的用户位置(例如,你是这里#4932最高分数)

Bitmaps

Bitmap不是一个真实数据类型,而是定义在String类型上面向Bit操作的集合。因为String是binary safe blobs,最大长度是512MB。他们适合设置232个不同的Bit。

Bit操作被分成两个组:

常量时间单Bit操作,如设置一个Bit为1或者,又或者获得它的值;

在Bit组上的操作,例如在一个给定Bit范围的Bit计数(例如,人口统计)。

Bitmap最大优点之一是在存储信息时,他们经常能节约大量空间。例如在一个系统中,不同用户通过递增用户ID来表示。可以记录一个单个Bit信息(例如,仅需要512MB内存就可以记录40亿用户中每一个用户是否接受一个刊物)。

使用SETBIT和GETBIT命令设置或获取Bit

> setbit key 10 1

(integer) 1

> getbit key 10

(integer) 1

> getbit key 11

(integer) 0

SETBIT命令将Bit序号作为它的第一个参数,需要设置的值作为它的第二个参数。

GETBIG仅返回在指定位置上Bit值。在范围以外的Bit(定位一个在目标Key保存的string的长度之外的Bit)总是被认为是0。

在Bit组上的三个命令操作。

- BITOP在不同的string按位操作。提供的操作有AND, OR,XOR和NOT。

- BITCOUNT获取设置成1的bit数量

- BITPOS查找指定0或者1值的第一个bit。

BITOPS和BITCOUNT能够在String的Byte范围上操作,而不是在整个string长度上运行。下面是一个BITCOUNT调用的简单例子。

> setbit key 0 1

(integer) 0

> setbit key 100 1

(integer) 0

> bitcount key

(integer) 2

Bitmap的常见用例:

- 所有类型的实时分析

- 高效的存储空间,同时具有高性能的关联对象ID的布尔信息

例如,你想知道你的网站用户日访问最长的streak。从你的网站开放开始自0开始计天数,每次有用户访问网站就使用SETBIT设置一个bit。使用当前unix时间作为bit index, 减去初始偏移量,并除以3600*24。

对于每一个用户,你使用一个小String包含每天的访问信息。使用BITCOUNT可以很容易得到一个用户访问网站的天数。使用一些BITPOS调用简单的获取和分析bitmap客户端,就可以很容易计算出最长的Streak。

Bitmap可以很容易的分裂成多个Key。因为需要将数据集合分片,和避免使用巨大的key上,我们将一个Bitmap分裂成不同的key,而不是将所有的bit设置到一个key中。实现这个的一个简单策略是每个key仅保存M个bit,并使用bit-number/M获得key的名称,第N个big通过bit-number MOD M来在Key中定位。

HyperLogLogs

HyperLogLog是带有统计功能的数据结构,用来统计唯一的事情。请参考完整文档获得更多信息。

其它值得注意的特性

还有其它关于Redis API重要的特性,在这个文档没有讲到,但是是你值得关注的:

- It is possible to iterate the key space of a large collection incrementally.

- It is possible to run Lua scripts server side to win latency and bandwidth.

- Redis is also a Pub-Sub server.

Learn more

这个学习指南没有完全讲解API,只是覆盖了基本的API。阅读命令参考文档来了解更多信息。

参考文档

An introduction to Redis data types and abstractions

转载请附上原博客地址:/article/7785803.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: