# /

# 数据类型(data types)

Redis不是一个简单的键值存储,它实际上是一个数据结构服务器,支持不同类型的值。这意味着,在传统的键值存储中,您可以将字符串键与字符串值相关联,而在Redis中,该值不仅限于简单的字符串,还可以容纳更复杂的数据结构。以下是Redis支持的所有数据结构的列表,本教程将单独介绍这些数据结构:
Strings 二进制安全字符串。
Lists 列表:根据插入顺序排序的字符串元素的集合。它们基本上是链表。
Sets 集合:唯一的、未排序的字符串元素的集合。
Sorted sets 排序的集合,类似于集合,但其中每个字符串元素都与一个称为score的浮点数相关联。元素总是按分数排序,因此与Set不同,可以检索一系列元素(例如,您可能会问:给我前10个,或后10个)。
Hashes 哈希,它是由与值相关联的字段组成的映射。字段和值都是字符串。这与Ruby或Python哈希非常相似。
Bitmaps 位图:使用特殊命令,可以像位数组一样处理字符串值:您可以设置和清除单个位,计数所有设置为1的位,找到第一个设置或未设置的位,等等。
HyperLogLogs 超级日志:这是一种概率数据结构,用于估计集合的基数。别害怕,它比看起来更简单。
Streams 流:仅附加提供抽象日志数据类型的类似映射的条目的集合。

# Keys

Redis密钥是二进制安全的,这意味着你可以使用任何二进制序列作为密钥,从像“foo”这样的字符串到JPEG文件的内容。空字符串也是一个有效的键。关于密钥的其他一些规则:

# 不建议使用很长的key

很长的钥匙不是个好主意。例如,1024字节的密钥不仅在内存方面是个坏主意,而且因为在数据集中查找密钥可能需要进行几次代价高昂的密钥比较。即使手头的任务是匹配大值的存在,对其进行哈希(例如使用SHA1)也是一个更好的想法,尤其是从内存和带宽的角度来看。
允许的最大密钥大小为512 MB。

# 建议使用有规范的key

很短的钥匙通常不是一个好主意。如果你可以写“user:1000:followers”,那么写“u1000flw”作为密钥就没有什么意义了。There is little point in writing "u1000flw" as a key if you can instead write "user:1000:followers".后者更具可读性,与键对象本身和值对象使用的空间相比,添加的空间较小。虽然短键显然会消耗更少的内存,但你的工作是找到正确的平衡。
尽量使用模式schema。例如,“object-type:id”是一个好主意,如“user:1000”。点或破折号通常用于多单词字段,如“comment🔢reply.to”或“comment:134:reply-to”。

# Strings

Redis字符串是最基本的Redis数据类型,表示一个字节序列。
Redis字符串类型是可以与Redis键关联的最简单的值类型。它是Memcached中唯一的数据类型,因此新手在Redis中使用它也是非常自然的。

# 将StringType视为字符串

由于Redis键是字符串,所以当我们也使用字符串类型作为值时,我们将一个字符串映射到另一个字符串。字符串数据类型对于许多用例非常有用,比如缓存HTML片段或页面。

> set mykey somevalue
OK
> get mykey
"somevalue"
1
2
3
4

正如您所看到的,使用SET和GET命令是我们设置和检索字符串值的方式。请注意,在密钥已经存在的情况下,SET将替换已经存储在密钥中的任何现有值,即使该密钥与非字符串值相关联。所以SET执行赋值。

注意:值可以是各种字符串(包括二进制数据),例如,您可以在值中存储jpeg图像。值不能大于512 MB。

# SET参数

SET命令有一些有趣的选项,这些选项作为附加参数提供。例如,如果密钥已经存在,我可能会要求SET失败,或者相反,只有当密钥已经存在时,它才能成功:

> set mykey newval nx
(nil)
> set mykey newval xx
OK
1
2
3
4

SET命令支持一组修改其行为的选项:
EX 秒--设置指定的过期时间,以秒为单位。
PX 毫秒--设置指定的过期时间,以毫秒为单位。
EXAT 时间戳秒--设置密钥将过期的指定Unix时间(以秒为单位)。
PXAT 时间戳毫秒--设置密钥将过期的指定Unix时间(以毫秒为单位)。
NX--仅当密钥不存在时才设置该密钥。
XX--仅在密钥已存在的情况下设置密钥。
KEEPTTL--保留与密钥相关联的生存时间。
GET--返回存储在key处的旧字符串,如果key不存在,则返回nil。如果存储在键上的值不是字符串,则返回错误并中止SET。

# 将StringType视为整数

即使字符串是Redis的基本值,也可以使用它们执行一些有趣的操作。例如,一种是原子增量:

> set counter 100
OK
> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152
1
2
3
4
5
6
7
8

INCR命令将字符串值解析为整数,然后将其递增一,最后将获得的值设置为新值。还有其他类似的命令,如INCRBY、DECR和DECRBY。在内部,它总是相同的命令,以稍微不同的方式执行。
INCR是原子的,这意味着什么?即使多个客户端针对同一密钥发出INCR,也永远不会进入竞争状态。例如,永远不会发生客户端1读取“10”,客户端2同时读取“10“,两者都递增到11,并将新值设置为11。最终值将始终为12,并且在所有其他客户端不同时执行命令时执行读取增量设置操作。
有许多用于对字符串进行操作的命令。例如,GETSET命令将键设置为新值,并返回旧值作为结果。例如,如果您有一个系统,每当您的网站接收到新访问者时,都会使用INCR递增Redis密钥,则可以使用此命令。您可能希望每小时收集一次此信息,而不会丢失一个增量。您可以设置密钥,为其分配新值“0”并读取旧值。

# 批量操作

在单个命令中设置或检索多个键的值的能力对于减少延迟也很有用。由于这个原因,存在MSET和MGET命令:

> mset a 10 b 20 c 30
OK
> mget a b c
1) "10"
2) "20"
3) "30"
1
2
3
4
5
6

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

# 更改和查询密钥空间

有些命令没有在特定类型上定义,但对于与键空间交互很有用,因此可以与任何类型的键一起使用。
例如,EXISTS命令返回1或0,表示数据库中是否存在给定的键,而DEL命令删除键和相关值,无论值是多少。

> set mykey hello
OK
> exists mykey
(integer) 1
> del mykey
(integer) 1
> exists mykey
(integer) 0
1
2
3
4
5
6
7
8

从这些例子中,您还可以看到DEL本身是如何返回1或0的,这取决于该键是否被删除(它存在)(没有具有该名称的键)。
有许多与键空间相关的命令,但以上两个命令与TYPE命令一起是必不可少的命令,TYPE命令返回存储在指定键上的值类型:

> set mykey x
OK
> type mykey
string
> del mykey
(integer) 1
> type mykey
none
1
2
3
4
5
6
7
8
# 密钥生存时间

在继续处理更复杂的数据结构之前,我们需要讨论另一个功能,该功能无论值类型如何都有效,称为Redis过期。基本上,您可以为密钥设置超时,这是一个有限的生存时间。当生存时间过去时,密钥会自动销毁,就像用户用密钥调用DEL命令一样。
一些关于Redis过期的快速信息:

  • 它们可以使用秒或毫秒精度进行设置。
  • 但是,过期时间分辨率始终为1毫秒。
  • 有关过期的信息会被复制并持久化到磁盘上,当Redis服务器保持停止状态时,这段时间实际上已经过去了(这意味着Redis会保存密钥的过期日期)。

设置过期是很琐碎的:

> set key some-value
OK
> expire key 5
(integer) 1
> get key (immediately)
"some-value"
> get key (after some time)
(nil)
1
2
3
4
5
6
7
8

密钥在两次GET调用之间消失,因为第二次调用延迟了5秒以上。在上面的例子中,我们使用了EXPIRE来设置EXPIRE(它也可以用于为已经有一个密钥的密钥设置不同的EXPIRE,就像PERSIST可以用于删除EXPIRE并使密钥永久持久化一样)。但是,我们也可以使用其他Redis命令创建过期的密钥。例如,使用SET选项:

> set key 100 ex 10
OK
> ttl key
(integer) 9
1
2
3
4

上面的例子设置了一个字符串值为100的密钥,其过期时间为10秒。稍后调用TTL命令,以检查密钥的剩余生存时间。

# Lists

Redis列表是按插入顺序排序的字符串列表。
为了解释List数据类型,最好从一点理论开始,因为信息技术人员经常以不恰当的方式使用List一词。例如,“Python列表”并不是名称所暗示的(链表),而是数组(在Ruby中,相同的数据类型实际上被称为数组)。
从一个非常普遍的角度来看,List只是一个有序元素的序列:10,20,1,2,3是一个列表。但是,使用数组实现的列表的属性与使用链表实现的列表属性非常不同。
Redis列表是通过链表实现的。这意味着,即使列表中有数百万个元素,在列表的头部或尾部添加新元素的操作也会在恒定时间内执行。使用LPUSH命令将新元素添加到具有十个元素的列表list的头部的速度与将一个元素添加到具有1000万个元素的表list的顶部的速度相同。
缺点是什么?在使用数组实现的列表中,通过索引访问元素的速度非常快(恒定时间索引访问),而在由链表实现的列表则不那么快(其中操作需要与访问的元素的索引成比例的工作量)。
Redis列表是用链表实现的,因为对于数据库系统来说,能够以非常快的方式将元素添加到非常长的列表中是至关重要的。另一个强大的优势,正如您稍后将看到的,是Redis列表可以在恒定的时间内以恒定的长度获取。

注意:当快速访问大型元素集合的中间很重要时,可以使用一种不同的数据结构,称为排序集sorted sets。排序后的集合将在本教程后面介绍。

# Redis列表的第一步

LPUSH命令将一个新元素添加到左侧(在头部)的列表中,而RPUSH命令则将一个新元素添加到右侧(在尾部)的列表。最后,LRANGE命令从列表中提取元素的范围:

> 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"
1
2
3
4
5
6
7
8
9
10

请注意,LRANGE需要两个索引,即要返回的范围的第一个和最后一个元素。这两个索引都可以是负数,告诉Redis从末尾开始计数:所以-1是列表的最后一个元素,-2是列表的倒数第二个元素,依此类推。

正如您所看到的,RPUSH将元素附加在列表的右侧,而最终的LPUSH将该元素附加在左侧。
这两个命令都是可变命令,这意味着您可以在一次调用中将多个元素推送到列表中:

> 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"
1
2
3
4
5
6
7
8
9
10
11
12

Redis列表上定义的一个重要操作是弹出元素的能力。弹出元素是从列表中检索元素,同时从列表中删除元素的操作。您可以从左到右弹出元素,类似于在列表两侧推送元素的方式:

> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"
1
2
3
4
5
6
7
8

我们添加了三个元素并弹出了三个元件,所以在这个命令序列的末尾,列表是空的,没有更多的元件可以弹出。如果我们尝试弹出另一个元素,这就是我们得到的结果:

> rpop mylist
(nil)
1
2
# 列表的常见用例

列表对许多任务都很有用,以下是两个非常有代表性的用例:

  • 记住用户在社交网络中发布的最新更新。
  • 进程之间的通信,使用消费者-生产者模式,生产者将项目推入列表,消费者(通常是工作人员)使用这些项目并执行操作。Redis有特殊的列表命令,使这个用例更加可靠和高效。

例如,流行的Ruby库resque和sidekiq都在后台使用Redis列表来实现后台作业。
流行的推特社交网络将用户发布的最新推文纳入Redis列表。
为了逐步描述一个常见的用例,想象一下你的主页显示了照片共享社交网络中发布的最新照片,你想加快访问速度。

  • 每次用户发布新照片时,我们都会使用LPUSH将其ID添加到列表中。
  • 当用户访问主页时,我们使用LRANGE 0 9来获取最新发布的10个项目。
# 有上限的列表

在许多用例中,我们只想使用列表来存储最新的项目,无论它们是什么:社交网络更新、日志或其他任何东西。
Redis允许我们使用列表作为一个有上限的集合,只记住最新的N个项目,并使用LTRIM命令丢弃所有最旧的项目。
LTRIM命令类似于LRANGE,但它没有显示指定的元素范围,而是将该范围设置为新的列表值。所有超出给定范围的元素都将被删除。
举个例子可以更清楚地说明:

> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2 //这里表示保留[0,2]区间的元素,删除其它元素
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
1
2
3
4
5
6
7
8

上面的LTRIM命令告诉Redis只获取索引0到2的列表元素,其他所有内容都将被丢弃。这允许一个非常简单但有用的模式:一起执行列表推送操作和列表修剪操作,以添加新元素并丢弃超过限制的元素:

LPUSH mylist <some element>
LTRIM mylist 0 999
1
2

上面的组合添加了一个新元素,并且只将1000个最新的元素放入列表中。使用LRANGE,您可以访问最热门的项目,而无需记住非常旧的数据。

注意:虽然LRANGE在技术上是一个O(N)命令,但访问列表开头或结尾的小范围是一个恒定时间O(1)操作。

# 阻塞队列(阻止Blocking列表上的操作)

列表有一个特殊的功能,使其适合于实现队列,并且通常作为进程间通信系统的构建块:阻塞操作。
想象一下,你想用一个流程将项目推送到一个列表中,然后使用不同的流程来实际处理这些项目。这是通常的生产者/消费者设置,可以通过以下简单方式实现:

  • 为了将项目推送到列表中,生产商调用LPUSH。
  • 要从列表中提取/处理项目,消费者调用RPOP。

然而,有时列表可能是空的,并且没有什么要处理的,所以RPOP只返回NULL。在这种情况下,使用者被迫等待一段时间,然后使用RPOP重试。这被称为轮询,在这种情况下不是一个好主意,因为它有几个缺点:

  1. 强制Redis和客户端处理无用的命令(当列表为空时,所有请求都不会完成实际工作,它们只会返回NULL)。
  2. 给项的处理增加了延迟,因为工作程序收到NULL后会等待一段时间。为了使延迟更小,我们可以减少对RPOP的调用之间的等待时间,从而放大问题1,即对Redis的更多无用调用。

因此,Redis实现了名为BRPOP和BLPOP的命令,这两个命令是RPOP和LPOP的版本,如果列表为空,它们可以阻止:只有当向列表中添加新元素时,或者达到用户指定的超时时,它们才会返回给调用方。
这是我们可以在工作者中使用的BRPOP调用的示例:

> brpop tasks 5
1) "tasks"
2) "do_something"
1
2
3

它的意思是:“等待列表任务中的元素,但如果5秒钟后没有可用的元素,则返回”。

请注意,您可以使用0作为超时值来永远等待元素,也可以指定多个列表而不仅仅是一个列表,以便同时等待多个列表,并在第一个列表接收到元素时得到通知。

关于BRPOP需要注意的几点:

  1. 以有序的方式为客户端提供服务:阻止等待列表的第一个客户端,在其他客户端推送元素时首先提供服务,以此类推。
  2. 返回值与RPOP不同:它是一个双元素数组,因为它还包括密钥的名称,因为BRPOP和BLPOP能够阻止等待多个列表中的元素。
  3. 如果达到超时,则返回NULL。

关于列表和阻塞操作,您应该了解更多信息。我们建议您更多地阅读以下内容:

  • 使用LMOVE可以构建更安全的队列或轮换队列。
  • 该命令还有一个阻塞变体,称为BLMOVE。
# 自动创建和删除密钥

到目前为止,在我们的示例中,我们从未在推送元素之前创建过空列表,或者在空列表中不再包含元素时删除空列表。当列表为空时,Redis有责任删除密钥,或者如果密钥不存在,并且我们试图向其中添加元素,例如使用LPUSH,则创建一个空列表。
这并不特定于列表,它适用于由多个元素组成的所有Redis数据类型——流、集、排序集和哈希。
基本上,我们可以用三条规则来概括行为:

  1. 当我们将元素添加到聚合数据类型时,如果目标键不存在,则在添加元素之前会创建一个空的聚合数据类型。
  2. 当我们从聚合数据类型中删除元素时,如果值保持为空,则键将自动销毁。流数据类型是此规则的唯一例外。
  3. 调用一个只读命令,如LLEN(返回列表的长度),或用一个空键删除元素的写命令,总是会产生与该键所包含的命令期望查找的类型的空聚合类型相同的结果。

规则1的示例:

> del mylist
(integer) 1
> lpush mylist 1 2 3
(integer) 3
1
2
3
4

但是,如果密钥存在,我们不能对错误的类型执行操作:

> set foo bar
OK
> lpush foo 1 2 3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> type foo
string
1
2
3
4
5
6

规则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
1
2
3
4
5
6
7
8
9
10
11
12

在弹出所有元素后,密钥不再存在。
规则3示例:

> del mylist
(integer) 0
> llen mylist
(integer) 0
> lpop mylist
(nil)
1
2
3
4
5
6

# Hashes

Redis散列是被建模为字段值对集合的记录类型。因此,Redis散列类似于Python字典、Java散列映射和Ruby散列。

# 批量操作

Redis散列看起来正是人们所期望的“散列”的样子,具有字段值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"
1
2
3
4
5
6
7
8
9
10
11
12
13

虽然散列可以方便地表示对象,但实际上,散列中可以放入的字段数量没有实际限制(除了可用内存),因此您可以在应用程序中以多种不同的方式使用散列。
命令HMSET设置散列的多个字段,而HGET检索单个字段。HMGET类似于HGET,但返回一组值:

> hmget user:1000 username birthyear no-such-field
1) "antirez"
2) "1977"
3) (nil)
1
2
3
4

有些命令也可以对单个字段执行操作,如HINCRBY:

> hincrby user:1000 birthyear 10
(integer) 1987
> hincrby user:1000 birthyear 10
(integer) 1997
1
2
3
4

您可以在文档中找到“哈希命令的完整列表”。

值得注意的是,小散列(即一些具有小值的元素)在内存中以特殊的方式进行编码,这使它们具有非常高的内存效率。

# Sets

Redis集是唯一字符串的无序集合,其作用类似于您喜爱的编程语言中的集(例如,Java哈希集、Python集等)。使用Redis集合,您可以在O(1)时间内添加、删除和测试是否存在(换句话说,无论集合元素的数量如何)。

# 添加元素

Redis集合是无序的字符串集合。SADD命令将新元素添加到集合中。还可以对集合执行许多其他操作,如测试给定元素是否已经存在,执行多个集合之间的交集、并集或差集,等等。

> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2
1
2
3
4
5
6

在这里,我向我的集合添加了三个元素,并告诉Redis返回所有元素。正如您所看到的,它们没有排序——Redis可以在每次调用时自由地以任何顺序返回元素,因为与用户没有关于元素排序的约定。

# 检查元素是否存在

Redis有测试成员身份的命令。例如,检查元素是否存在:

> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0
1
2
3
4

“3”是集合的成员,而“30”不是。

# 批量操作

集合有利于表达对象之间的关系。例如,我们可以很容易地使用集合来实现标记。
为这个问题建模的一个简单方法是为我们想要标记的每个对象都设置一个集合。该集合包含与对象关联的标记的ID。
一个例子是给新闻文章加标签。如果文章ID 1000被标记有标签1、2、5和77,则一组可以将这些标签ID与新闻项目相关联:

> sadd news:1000:tags 1 2 5 77
(integer) 4
1
2

获取给定对象的所有标记是很简单的:

> smembers news:1000:tags
1. 5
2. 1
3. 77
4. 2
1
2
3
4
5
# 集合交集

我们也可能希望有相反的关系:用给定标签标记的所有新闻的列表:

> 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
1
2
3
4
5
6
7
8

注意:在这个例子中,我们假设您有另一个数据结构,例如Redis散列,它将标签ID映射到标签名称。

使用正确的Redis命令仍然可以轻松实现其他一些非琐碎的操作。例如,我们可能想要一个包含标签1、2、5的所有对象的列表。我们可以使用SINTER命令来执行此操作,该命令执行不同集合之间的交集。我们可以使用:

> sinter tag:1:news tag:2:news tag:5:news
1) "1000"
1
2

除了交集,您还可以执行并集、差分、提取随机元素等等。

# 集合复制

提取元素的命令称为SPOP,用于对某些问题进行建模非常方便。例如,为了实现基于网络的扑克游戏,您可能需要用一组来表示您的牌组。假设我们为(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
1
2
3
4
5

现在我们想为每个玩家提供5张牌。SPOP命令删除一个随机元素,并将其返回给客户端,因此在这种情况下,这是一个完美的操作。
然而,如果我们直接对我们的牌组进行调用,在下一次游戏中,我们需要再次填充牌组,这可能并不理想。因此,首先,我们可以将存储在套牌密钥中的副本复制到game:1:deck key。
这是使用SUNIONSTORE完成的,它通常在多个集合之间执行并集,并将结果存储到另一个集合中。然而,由于单集的并集本身就是并集,我可以用以下内容复制我的套牌:

> sunionstore game:1:deck deck
(integer) 52
1
2
# 集合基数

现在我准备为第一个玩家提供provide五张牌:

> 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"
1
2
3
4
5
6
7
8
9
10

一对jacks,不太好。。。
现在是引入set命令的好时机,该命令提供集合中元素的数量。在集合论的上下文中,这通常被称为集合的基数cardinality,因此Redis命令被称为SCARD。

> scard game:1:deck
(integer) 47
1
2

数学运算:52-5=47。

# 随机获取元素

当您只需要获取随机元素而不从集合中删除它们时,有适合该任务的SRANDMEMBER命令。它还具有返回重复元素和非重复元素的功能。

# Sorted sets

Redis排序集是唯一字符串的集合,它们根据每个字符串的相关分数来维持顺序。
排序集是一种数据类型,类似于集和哈希之间的混合。与集合一样,排序集合由唯一的、不重复的字符串元素组成,因此在某种意义上,排序集合也是集合。
然而,虽然集合内的元素没有排序,但排序集合中的每个元素都与一个称为分数的浮点值相关联(这就是为什么该类型也类似于哈希,因为每个元素都映射到一个值)。
此外,排序集合中的元素是按顺序排列的(因此它们不是根据请求排列的,顺序是用于表示排序集合的数据结构的一个特性)。它们是根据以下规则订购的:

  • 如果A和B是具有不同分数的两个元素,如果A.分数>B.分数,则A>B。
  • 如果A和B的分数完全相同,那么如果A字符串在字典上大于B字符串,则A>B。A和B字符串不能相等,因为排序的集合只有唯一的元素。
# 添加元素

让我们从一个简单的例子开始,添加一些选定的黑客名称作为排序的集合元素,将他们的出生年份作为“分数”。

> 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

正如您所看到的,ZADD类似于SADD,但需要一个额外的参数(放置在要添加的元素之前),即分数。ZADD也是可变的,所以您可以自由指定多个得分值对,即使在上面的例子中没有使用。

# 读取集合

对于已排序的集合,返回按出生年份排序的黑客列表是微不足道的,因为实际上他们已经被排序了。

实现说明:排序集是通过包含跳过列表和哈希表的双端口数据结构实现的,因此每次我们添加元素时,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"
1
2
3
4
5
6
7
8
9
10

注意:0和-1表示从元素索引0到最后一个元素(-1在这里的作用与在LRANGE命令中的作用相同)。

如果我想按相反的方式订购,从最小到最大怎么办?使用ZREVRANGE而不是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"
1
2
3
4
5
6
7
8
9
10

也可以使用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"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 在范围内操作

排序集比这更强大。它们可以在靶场进行操作。让我们把1950年之前出生的所有人都包括在内。我们使用ZRANGEBYSCORE命令来执行此操作:

> zrangebyscore hackers -inf 1950
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
1
2
3
4
5
6

我们要求Redis返回所有得分在负无穷大-inf和1950之间的元素(包括两个极端)。
还可以删除元素的范围。让我们从排序的集合中删除1940年至1960年间出生的所有黑客:

> zremrangebyscore hackers 1940 1960
(integer) 4
1
2

ZREMRANGEBYSCORE可能不是最好的命令名,但它可能非常有用,并返回已删除元素的数量。
为排序的集合元素定义的另一个非常有用的操作是get-rank操作。可以问一个元素在有序元素集合中的位置是什么。

> zrank hackers "Anita Borg"
(integer) 4
1
2

ZREVRANK命令也可用于获取秩rank,考虑到元素按降序排序。

# 词典分数

在最新版本的Redis 2.8中,引入了一项新功能,允许以字典方式获取范围,假设已排序集合中的元素都以相同的分数插入(元素与C memcmp函数进行比较,因此可以保证不进行排序,并且每个Redis实例都将以相同的输出进行回复)。
使用字典范围操作的主要命令有ZRANGEBYLEX、ZREVRANGEBYLEX、ZrEMRANGEBYELEX和ZLEXCOUNT。
例如,让我们再次添加我们的著名黑客列表,但这次对所有元素使用零分:

> 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"
1
2
3

由于排序集的排序规则,它们已经按照字典顺序lexicographically进行了排序:

> 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"
1
2
3
4
5
6
7
8
9
10

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

> zrangebylex hackers [B [P
1) "Claude Shannon"
2) "Hedy Lamarr"
3) "Linus Torvalds"
1
2
3
4

范围可以是包含的,也可以是互斥的(取决于第一个字符),字符串infinite和减号infinite分别用和-string指定。有关详细信息,请参阅文档。
这个功能很重要,因为它允许我们使用排序集作为通用索引。例如,如果您想通过128位无符号整数参数对元素进行索引,您所需要做的就是将元素添加到一个具有相同分数(例如0)但具有16字节前缀的排序集中,该前缀由128位big-endian数字组成。由于big-endian中的数字在按字典顺序(按原始字节顺序)排序时实际上也是按数字顺序排列的,因此您可以询问128位空间中的范围,并获得元素的值,而不使用前缀。

# 更新分数:排行榜leader boards

排序集的分数可以随时更新。仅对已排序集合中已包含的元素调用ZADD,就会使用O(log(N))时间复杂度更新其分数(和位置)。因此,当有大量更新时,排序集是合适的。
由于这个特性,一个常见的用例是引线板leader boards。典型的应用程序是一款Facebook游戏,你可以将根据高分对用户进行排序的能力与获取排名操作相结合,以显示前N名用户和用户在排行榜上的排名(例如,“你是#4932最佳得分者”)。

# Bitmaps

Redis位图允许您对字符串执行逐位操作。
位图不是实际的数据类型,而是在String类型上定义的一组面向位的操作。由于字符串是二进制安全Blob,其最大长度为512MB,因此适合设置多达232个不同的位。
位操作分为两组:恒定时间单比特操作,如将位设置为1或0,或获取其值,以及对位组的操作,如计数给定位范围内的设置位的数量(如总体计数)。
位图最大的优点之一是,在存储信息时,它们通常可以极大地节省空间。例如,在不同用户由增量用户ID表示的系统中,仅使用512MB的内存就可以记住40亿用户的单比特信息(例如,知道用户是否想接收时事通讯)。

# 设置和检索位

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

> setbit key 10 1
(integer) 1
> getbit key 10
(integer) 1
> getbit key 11
(integer) 0
1
2
3
4
5
6

SETBIT命令的第一个自变量是比特数,第二个自变量是要将比特设置为的值,即1或0。如果寻址位在当前字符串长度之外,则该命令会自动放大字符串。

# 位图统计

GETBIT只返回指定索引处的位的值。超出范围的位(寻址存储在目标密钥中的字符串长度之外的位)总是被认为是零。
有三个命令对一组位进行操作:

  1. BITOP在不同的字符串之间执行逐位操作。提供的操作有AND、OR、XOR和NOT。
  2. BITCOUNT执行总体计数,报告设置为1的位数。
  3. BITPOS找到具有指定值0或1的第一个比特。

BITPOS和BITCOUNT都能够对字符串的字节范围进行操作,而不是对字符串的整个长度进行操作。以下是BITCOUNT调用的一个琐碎示例:

> setbit key 0 1
(integer) 0
> setbit key 100 1
(integer) 0
> bitcount key
(integer) 2
1
2
3
4
5
6
# 位图的常见用例有:
  • 各种实时分析。
  • 存储与对象ID相关联的节省空间但高性能的布尔信息。

例如,想象一下你想知道你的网站用户每天访问次数最长的一次。你从零开始计算天数,也就是你公开网站的那一天,并在每次用户访问网站时使用SETBIT设置一点。作为位索引,您只需取当前unix时间,减去初始偏移量,然后除以一天中的秒数(通常为3600*24)。
这样,对于每个用户,您都有一个小字符串,其中包含每天的访问信息。有了BITCOUNT,就可以轻松地获得给定用户访问网站的天数,而通过一些BITPOS调用,或者简单地获取和分析位图客户端,就可以容易地计算最长的连续时间。
位图拆分为多个键是微不足道的,例如,为了分割数据集,而且通常最好避免使用巨大的键。要在不同的密钥之间分割位图,而不是将所有比特设置为一个密钥,一个简单的策略就是每个密钥存储M个比特,并获得比特号为/M的密钥名称和比特号为MOD M的密钥内要寻址的第N个比特。

# HyperLogLog

Redis Hyper-Log-Log数据结构提供了对大集合的基数(即元素数量)的概率估计。
超日志日志是一种概率数据结构,用于统计唯一的事物(从技术上讲,这是指估计集合的基数)。通常,计数唯一的项目需要使用与您想要计数的项目数量成比例的内存,因为您需要记住过去已经看到的元素,以避免多次计数。然而,有一组算法是用内存换取精度的:最终得出一个具有标准误差的估计度量,在Redis实现的情况下,该误差小于1%。该算法的神奇之处在于,您不再需要使用与计数项目数成比例的内存量,而是可以使用恒定的内存量!在最坏的情况下是12k字节,如果您的Hyper Log Log(从现在起我们只称之为HLL)只看到很少的元素,则会少得多。
Redis中的HLL在技术上是不同的数据结构,但编码为Redis字符串,因此您可以调用GET来序列化HLL,并调用SET将其反序列化回服务器。
从概念上讲,HLL API类似于使用Sets来完成相同的任务。您将把每个观察到的元素SADD到一个集合中,并使用SCARD来检查集合内的元素数量,这些元素是唯一的,因为SADD不会重新添加现有元素。
虽然您并没有真正将项添加到HLL中,但由于数据结构只包含不包含实际元素的状态,因此API是相同的:

  • 每次您看到一个新元素时,都会使用PFADD将其添加到计数中。
  • 每次要检索到目前为止使用PFADD添加的唯一元素的当前近似值时,都要使用PFCOUNT。
> pfadd hll a b c d
(integer) 1
> pfcount hll
(integer) 4
1
2
3
4

此数据结构的用例示例是计算用户每天在搜索表单中执行的唯一查询。
Redis还可以执行HLL的并集,请查看完整的文档以了解更多信息。

# Streams

Redis流是一种数据结构,其作用类似于仅追加日志。流帮助按事件发生的顺序记录事件,然后将它们联合起来进行处理。

# Geospatial indexes(地理空间索引)

Redis地理空间索引可用于查找给定地理半径或边界框内的位置。

# Bitfields

Redis位字段有效地将多个计数器编码为字符串值。位字段提供原子获取、设置和增量操作,并支持不同的溢出策略。