Redis数据类型简介
系列教程导航
- 如何在 Ubuntu 18.04 上安装和保护 Redis
- 如何连接到 Redis 数据库
- 如何管理 Redis 数据库和 Keys
- 如何在 Redis 中管理副本和客户端
- 如何在 Redis 中管理字符串
- 如何在 Redis 中管理 List
- 如何在 Redis 中管理 Hashes
- 如何在 Redis 中管理 Sets
- 如何在 Redis 中管理 Sorted Sets
- 如何在 Redis 中运行事务
- 如何使 Redis 中的 Key 失效
- 如何解决 Redis 中的故障
- 如何从命令行更改 Redis 的配置
- Redis 数据类型简介
Redis 数据类型简介
Redis 不是简单的键值存储,它实际上是一个数据结构服务器,支持不同类型的值。这意味着在传统键值存储中,您将字符串键与字符串值相关联,而在 Redis 中,该值不仅限于简单的字符串,还可以容纳更复杂的数据结构。以下是 Redis 支持的所有数据结构的列表,本教程将分别进行介绍:
- 二进制安全字符串。
- 列表(List):根据插入顺序排序的字符串元素的集合。它们基本上是链表。
- 集合(Set):唯一、未排序的字符串元素的集合。
- 有序集合(Sorted Set):类似于 Sets,但每个字符串元素都与一个称为 score 的浮点值相关联。元素总是按它们的分数排序,因此与 Sets 不同,可以检索一系列元素(例如,您可能会问:给我前 10 名或后 10 名)。
- 哈希(Hash):是由与值关联的字段组成的映射。字段和值都是字符串。这与 Ruby 或 Python 哈希非常相似。
- 位数组(Bitmaps):可以使用特殊命令像位数组一样处理字符串值:您可以设置和清除单个位,计数所有设置为 1 的位,找到第一个设置或未设置的位,等等。
- HyperLogLogs:这是一个概率数据结构,用于估计集合的基数。别害怕,它比看起来更简单...请参阅本教程的 HyperLogLog 部分。
- 流(Streams):提供抽象日志数据类型的类地图项的仅追加集合。在"Redis 流简介"中对它们进行了深入 介绍。
从 命令参考 中掌握这些数据类型的工作方式以及使用什么来解决给定问题并不总是那么容易,因此,本文档是有关 Redis 数据类型及其最常见模式的速成课程。
对于所有示例,我们将使用该 redis-cli 实用程序(一个简单但方便的命令行实用程序)对 Redis 服务器发出命令。
Redis 键 (Redis Keys)
Redis 键是二进制安全的,这意味着您可以使用任何二进制序列作为键,从"foo"之类的字符串到 JPEG 文件的内容。空字符串也是有效的键。
有关键的其他一些规则:
- 太长的键不是一个好主意。例如,1024 字节的键不仅是内存方面的问题,也是一个坏主意,因为在数据集中查找键可能需要进行一些代价高昂的键比较。即使手头的任务是匹配一个大值的存在,对它进行散列(例如使用 SHA1)也是一个更好的主意,尤其是从内存和带宽的角度来看。
- 非常短的键通常不是一个好主意。如果您可以改写
user:1000:followers,那么将u1000flw写为键毫无意义。与键对象本身和值对象使用的空间相比,后者更具可读性,并且添加的空间较小。虽然短键显然会消耗更少的内存,但您的工作是找到合适的平衡。 - 尝试坚持使用架构。例如,
object-type:id是一个好主意,例如user:1000。点或破折号通常用于多字字段,例如comment:1234:reply.to或comment:1234:reply-to中。 - 允许的最大键大小为 512 MB。
Redis 字符串 (Redis Strings)
Redis 字符串类型是您可以与 Redis 键关联的最简单的值类型。它是 Memcached 中唯一的数据类型,因此对于新手来说,在 Redis 中使用它也是很自然的。
由于 Redis 键是字符串,因此当我们也使用字符串类型作为值时,我们会将一个字符串映射到另一个字符串。字符串数据类型对于许多用例很有用,例如缓存 HTML 片段或页面。
让我们使用 redis-cli 来处理字符串类型(所有示例将在本教程中通过 redis-cli 来执行)。
> set mykey somevalue
OK
> get mykey
"somevalue"如您所见,使用 SET 和 GET 命令是我们设置和检索字符串值的方式。请注意,即使键已与非字符串值相关联,SET 仍将替换已存储在键中的任何现有值。因此 SET 执行分配。
值可以是每种类型的字符串(包括二进制数据),例如,您可以在值内存储 jpeg 图像。值不能大于 512 MB。
该 SET 命令有有趣的选项,这是作为附加参数。例如,如果密钥已经存在,我可能会要求 SET 失败,或者相反,只有密钥已经存在时,它才会成功:
> set mykey newval nx
(nil)
> set mykey newval xx
OK即使字符串是 Redis 的基本值,您也可以使用它们执行一些有趣的操作。例如,一个是原子增量:
> set counter 100
OK
> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152INCR 命令通过解析字符串值作为一个整数,对它增量,并最终将获得的值作为新的值。还有其他类似的命令,例如 INCRBY, DECR 和 DECRBY。在内部,它始终是相同的命令,其执行方式略有不同。
INCR 是原子的意味着什么?即使使用相同密钥发出 INCR 的多个客户也永远不会进入竞争状态。例如,客户端 1 不会同时读取"10",客户端 2 会同时读取"10",都递增为 11,并将新值设置为 11。最终值将始终为 12,而在所有其他客户端未同时执行命令时执行增量设置操作。
有许多用于操作字符串的命令。例如,GETSET 命令将键设置为新值,并返回旧值作为结果。例如,如果您的系统在每次网站接收新访客时使用 INCR 递增 Redis 密钥,则可以使用此命令。您可能希望每小时收集一次此信息,而又不会丢失任何增量。您可以 GETSET 键,为其 分配新值"0",然后回读旧值。
在单个命令中设置或检索多个键的值的功能对于减少延迟也很有用。因此,有 MSET 和 MGET 命令:
> mset a 10 b 20 c 30
OK
> mget a b c
1) "10"
2) "20"
3) "30"使用 MGET 时,Redis 返回一个值数组。
键空间操作 (Altering and querying the key space)
有些命令未在特定类型上定义,但是在与键的空间进行交互时很有用,因此可以与任何类型的键一起使用。
例如,EXISTS 命令返回 1 或 0 表示数据库中是否存在给定的键,而 DEL 命令则删除键和关联的值(无论该值是什么)。
> set mykey hello
OK
> exists mykey
(integer) 1
> del mykey
(integer) 1
> exists mykey
(integer) 0从示例中,您还可以看到 DEL 本身如何返回 1 或 0,具体取决于密钥是否已删除(存在)(不存在具有该名称的此类密钥)。
有许多与密钥空间相关的命令,但是以上两个命令与 TYPE 命令一起是必不可少的,TYPE 命令返回存储在指定密钥处的值的类型:
> set mykey x
OK
> type mykey
string
> del mykey
(integer) 1
> type mykey
noneRedis 过期键:具有有限生存时间的键 (Redis expires: keys with limited time to live)
在继续使用更复杂的数据结构之前,我们需要讨论另一个功能,该功能不管值类型如何都可以工作,并且称为 Redis expires。基本上,您可以为密钥设置一个超时时间,这是有限的生存时间。生存时间过去后,该密钥将自动销毁,就像用户使用该密钥调用 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)由于第二次呼叫延迟了 5 秒钟以上,因此在两次 GET 呼叫之间密钥消失了。在上面的示例中,我们使用 EXPIRE 来设置过期时间(也可以使用它来为已经具有密钥的密钥设置不同的过期时间,例如可以使用 PERSIST 来删除过期并使密钥永久持久化)。但是,我们也可以使用其他 Redis 命令来创建具有过期密钥。例如,使用 SET 选项:
> set key 100 ex 10
OK
> ttl key
(integer) 9上面的示例使用一个字符串值设置一个密钥,该值 100 的到期时间为十秒钟。稍后调用 TTL 命令以检查密钥的剩余生存时间。
为了设置和检查以毫秒为单位到期,检查 PEXPIRE 和 PTTL 的命令,以及完整列表 SET 选项。
Redis 列表 (Redis Lists)
为了解释 List 数据类型,最好从理论上入手,因为 List 一词经常被信息技术人员以不正当的方式使用。例如,"Python 列表”并不是名称(链接列表)所建议的,而是数组(在 Ruby 中,相同的数据类型实际上称为数组)。
从非常普遍的角度来看,列表只是一系列有序元素:10, 20, 1, 2, 3 是一个列表。但是,使用 Array 实现的 List 的属性与使用 Linked List 实现的 List 的属性非常不同。
Redis 列表是通过链表实现的。这意味着即使您在列表中有数百万个元素,在列表的开头或结尾添加新元素的操作也会 在固定时间内 执行。使用 LPUSH 命令将新元素添加到具有 10 个元素的列表的开头的速度与将元素添加到具有 1000 万个元素的列表的开头的速度相同。
缺点是什么?在使用 Array 实现的列表中,按索引 访问元素 的 速度非常快(恒定时间索引访问),而在通过链表实现的列表中访问速度不是那么快(其中操作需要的工作量与所访问元素的索引成比例)。
Redis 列表是通过链表实现的,因为对于数据库系统而言,至关重要的是能够以非常快的方式将元素添加到很长的列表中。稍后您将看到,另一个强大的优势是 Redis 列表可以在恒定的时间内以恒定的长度获取。
当快速访问大量元素的中间位置很重要时,可以使用另一种称为排序集的数据结构。排序的集将在本教程的后面部分介绍。
Redis 列表入门 (First steps with Redis Lists)
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"请注意,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"在 Redis 列表上定义的一项重要操作是 弹出元素 的能力。弹出元素是同时从列表中检索元素并将其从列表中删除的操作。您可以从左侧和右侧弹出元素,类似于在列表两边推送元素的方式:
> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"我们添加了三个元素并弹出了三个元素,因此在此命令序列的末尾,列表为空,没有其他要弹出的元素。如果我们尝试弹出另一个元素,则会得到以下结果:
> rpop mylist
(nil)Redis 返回 NULL 值,以指示列表中没有元素。
列表的常见用例 (Common use cases for lists)
列表对于许多任务很有用,以下是两个非常有代表性的用例:
- 记住用户发布到社交网络上的最新更新。
- 使用生产者将项目推送到列表中的消费者与生产者模式进行流程之间的通信,而消费者(通常是 worker)消耗这些项目和已执行的动作。Redis 具有特殊的列表命令,以使此用例更加可靠和高效。
例如,流行的 Ruby 库 resque 和 sidekiq 都在 后台 使用 Redis 列表,以实现后台作业。
流行的 Twitter 社交网络 将 用户发布 的最新推文 放入 Redis 列表中。
为了逐步描述一个常见的用例,假设您的主页显示了在照片共享社交网络中发布的最新照片,并且您想加快访问速度。
- 每次用户发布新照片时,我们都会使用 LPUSH 将其 ID 添加到列表中。
- 当用户访问主页时,我们
LRANGE 0 9为了获取最新发布的 10 个项目。
有限列表 (Capped lists)
在许多用例中,我们只想使用列表来存储 最新项目,无论它们是什么:社交网络更新,日志或其他任何内容。
Redis 允许我们使用列表作为上限集合,仅使用 LTRIM 命令记住最新的 N 个项目并丢弃所有最旧的项目。
LTRIM 命令类似于 LRANGE,但是 ,而不是显示元件的规定的范围内 将其设置在该范围作为新的列表值。给定范围之外的所有元素都将被删除。
一个例子将使其更加清楚:
> 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 仅从索引 0 到 2 列出列表元素,其他所有内容都将被丢弃。这允许一个非常简单但有用的模式:一起执行 List 推操作 + List 修剪操作,以便添加新元素并丢弃超出限制的元素:
LPUSH mylist <some element>
LTRIM mylist 0 999上面的组合添加了一个新元素,并且仅将 1000 个最新元素纳入列表。使用 LRANGE,您可以访问最重要的项目,而无需记住非常旧的数据。
注意:虽然 LRANGE 从技术上讲是 O(N)命令,但朝列表的开头或 结尾 访问较小范围是恒定时间操作。
列表上的阻塞操作 (Blocking operations on lists)
列表具有一项特殊功能,使其适合于实现队列,并且通常用作进程间通信系统的构建块:阻塞操作。
想象一下,您想通过一个流程将项目推入列表,然后使用不同的流程来对这些项目进行某种工作。这是通常的生产者/使用者设置,可以通过以下简单方式实现:
但是,有时列表可能为空,没有任何要处理的内容,因此 RPOP 仅返回 NULL。在这种情况下,消费者被迫等待一段时间,然后使用 RPOP 重试。这称为 轮询,在这种情况下不是一个好主意,因为它有几个缺点:
- 强制 Redis 和客户端处理无用的命令(列表为空时的所有请求将无法完成任何实际工作,它们只会返回 NULL)。
- 由于工作人员在收到 NULL 之后会等待一段时间,因此会增加项目处理的延迟。为了使延迟更小,我们可以在 两次 调用 RPOP 之间等待的时间更少,从而扩大了问题编号 1,即对 Redis 的调用更加无用。
所以,所谓的 Redis 命令工具 BRPOP 和 BLPOP 它们的版本 RPOP 和 LPOP 能够阻止如果列表是空的:他们将回到只有当新的元素添加到列表中的来电者,或在用户指定的超时到达。
这是我们可以在 worker 中使用的 BRPOP 调用的示例:
> brpop tasks 5
1) "tasks"
2) "do_something"这意味着:“等待列表中的元素 tasks,但如果 5 秒钟后没有可用元素,则返回”。
请注意,您可以将 0 用作超时来永远等待元素,还可以指定多个列表,而不仅仅是一个列表,以便同时等待多个列表,并在第一个列表收到一个元素时得到通知。
- 客户端以有序方式提供服务:第一个阻塞等待列表的客户端,在某个元素被其他客户端推送时首先提供服务,依此类推。
- 返回值与 RPOP 相比有所不同:它是一个包含两个元素的数组,因为它还包含键的名称,因为 BRPOP 和 BLPOP 能够阻止等待来自多个列表的元素。
- 如果达到超时,则返回 NULL。
关于列表和阻止操作,您应该了解更多信息。我们建议您阅读以下内容:
- 使用 RPOPLPUSH 可以构建更安全的队列或轮换队列。
- 该命令还有一个阻塞变体,称为 BRPOPLPUSH。
键的自动创建和删除 (Automatic creation and removal of keys)
到目前为止,在我们的示例中,我们无需在推入元素之前创建空列表,也无需在内部不再包含元素时删除空列表。Redis 的责任是在列表为空时删除键,或者在键不存在并且我们试图向其添加元素(例如,使用 LPUSH)时创建一个空列表。
这不是特定于列表的,它适用于由多个元素组成的所有 Redis 数据类型 - 流,集合,排序集合和哈希。
基本上,我们可以用三个规则来总结行为:
- 当我们将元素添加到聚合数据类型时,如果目标键不存在,则在添加元素之前会创建一个空的聚合数据类型。
- 当我们从聚合数据类型中删除元素时,如果该值保持为空,则键将自动销毁。流数据类型是此规则的唯一例外。
- 调用带有空键的只读命令(例如 LLEN(返回列表的长度))或写命令删除元素,总会产生与键保持空的聚合类型相同的结果。命令希望找到。
规则 1 的示例:
> del mylist
(integer) 1
> lpush mylist 1 2 3
(integer) 3但是,如果密钥存在,我们将无法对错误的类型执行操作:
> 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弹出所有元素后,键不再存在。
规则 3 的示例:
> del mylist
(integer) 0
> llen mylist
(integer) 0
> lpop mylist
(nil)Redis 哈希 (Redis Hashes)
Redis 散列与字段值对看起来完全一样,可能是人们期望的“散列”外观:
> 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"尽管哈希可以方便地表示 对象,但是实际上可以放入哈希中的字段数没有实际限制(可用内存除外),因此您可以在应用程序内部以多种不同方式使用哈希。
HMSET 命令设置哈希的多个字段,而 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值得注意的是,小哈希(即,一些具有较小值的元素)以特殊方式在内存中进行编码,从而使它们具有很高的内存效率。
Redis 集合 (Redis Sets)
Redis 集是字符串的无序集合。该 SADD 命令添加新的元素,一组。还可以对集合进行许多其他操作,例如测试给定元素是否已存在,执行多个集合之间的交集,并集或求差等等。
> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2在这里,我在集合中添加了三个元素,并告诉 Redis 返回所有元素。如您所见,它们没有排序 - Redis 可以在每次调用时随意以任何顺序返回元素,因为与用户之间没有关于元素顺序的约定。
Redis 具有用于测试成员资格的命令。例如,检查元素是否存在:
> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0"3"是集合的成员,而"30"不是集合的成员。
集合非常适合表示对象之间的关系。例如,我们可以轻松地使用集合来实现标签。
对这个问题进行建模的一种简单方法是为我们要标记的每个对象设置一个集合。该集合包含与对象关联的标签的 ID。
一个例证是标记新闻文章。如果商品 ID 1000 带有标签 1、2、5 和 77 进行标记,则集合可以将这些标签 ID 与新闻项相关联:
> sadd news:1000:tags 1 2 5 77
(integer) 4我们可能还需要逆关系:用给定标签标记的所有新闻的列表:
> 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要获取给定对象的所有标签很简单:
> smembers news:1000:tags
1. 5
2. 1
3. 77
4. 2注意:在示例中,我们假设您具有另一个数据结构,例如 Redis 哈希,它将标签 ID 映射到标签名称。
还有其他一些非常简单的操作,使用正确的 Redis 命令仍然很容易实现。例如,我们可能需要包含标签 1、2、10 和 27 的所有对象的列表。我们可以使用 SINTER 命令执行此操作,该命令执行不同集合之间的交集。我们可以用:
> sinter tag:1:news tag:2:news tag:10:news tag:27:news
... results here ...除了交集之外,您还可以执行并集,求差,提取随机元素等等。
提取元素的命令称为 SPOP,对于建模某些问题非常方便。例如,为了实现基于 Web 的扑克游戏,您可能需要用一组来代表您的套牌。假设我们对(C)lubs,(D)diamonds,(H)hearts,(S)spades 使用一个单字符前缀:
> 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 密钥中的集合复制到 game:1:deck 密钥中。
这可以使用 SUNIONSTORE 来完成,SUNIONSTORE 通常执行多个集合之间的联合,并将结果存储到另一个集合中。但是,由于单个集合的并集本身,我可以使用以下命令复制我的卡组:
> sunionstore game:1:deck deck
(integer) 52现在,我准备为第一位玩家提供五张牌:
> 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"一副千斤顶,不是很好...
现在是引入 set 命令的好时机,该命令提供集合中元素的数量。在集合理论的上下文中,这通常称为 集合 的 基数,因此 Redis 命令称为 SCARD。
> scard game:1:deck
(integer) 47数学原理:52 - 5 = 47。
当您只需要获取随机元素而不将其从集合中删除时,可以使用适合该任务的 SRANDMEMBER 命令。它还具有返回重复元素和非重复元素的功能。
Redis 有序集合 (Redis Sorted sets)
排序集是一种数据类型,类似于集合和哈希之间的混合。像集合一样,排序集合由唯一的,非重复的字符串元素组成,因此从某种意义上说,排序集合也是一个集合。
但是,虽然集内的元素没有排序,但排序后的集合中的每个元素都与一个称为 得分 的浮点值相关联(这就是为什么该类型也类似于哈希的原因,因为每个元素都映射到一个值)。
此外,已排序集合中的元素是按 顺序进行的(因此,它们不是应请求而排序的,顺序是用于表示已排序集合的数据结构的特殊性)。它们按照以下规则排序:
- 如果 A 和 B 是两个分数不同的元素,则如果 A.score 是 > B.score,则 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如您所见,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"注意: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"也可以使用以下 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)
排序集比这更强大。它们可以在范围内操作。让我们获取所有在 1950 年(含)之前出生的人。我们使用 ZRANGEBYSCORE 命令来做到这一点:
> zrangebyscore hackers -inf 1950
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"我们要求 Redis 返回分数在负无穷大和 1950 之间的所有元素(包括两个极端)。
也可以删除元素范围。让我们从排序集中删除所有 1940 年至 1960 年之间出生的黑客:
> zremrangebyscore hackers 1940 1960
(integer) 4ZREMRANGEBYSCORE 可能不是最好的命令名称,但是它可能非常有用,并返回已删除元素的数量。
为排序的集合元素定义的另一个极其有用的操作是 get-rank 操作。可以问一个元素在有序元素集合中的位置是什么。
> zrank hackers "Anita Borg"
(integer) 4该 ZREVRANK 命令也可以为了获得军衔,考虑的要素排序的下降方式。
字典序分数 (Lexicographical scores)
在最新版本的 Redis 2.8 中,引入了一项新功能,该功能允许按字典顺序获取范围,假设已排序集中的元素都以相同的相同分数插入(将元素与 C memcmp 函数进行比较,因此可以确保没有排序规则,并且每个 Redis 实例将以相同的输出进行回复)。
用于按字典顺序操作的主要命令是 ZRANGEBYLEX, ZREVRANGEBYLEX, ZREMRANGEBYLEX 和 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"由于排序集的排序规则,它们已经按字典顺序排序:
> 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"范围可以是包含(inclusive)或排除(exclusive)(取决于第一个字符),字符串无限和负无限分别用 + 和 - 字符串指定。有关更多信息,请参见文档。
此功能非常重要,因为它允许我们将排序后的集合用作通用索引。例如,如果要通过 128 位无符号整数参数索引元素,则只需将元素添加到具有相同分数(例如 0)但具有由 128 个字节组成的 16 字节前缀的排序集中 大尾数中的位数。由于 big endian 中的数字实际上按数字顺序也按字典顺序(以原始字节顺序)排序,因此您可以要求 128 位空间中的范围,并获得丢弃前缀的元素值。
如果要在更严重的演示环境中查看该功能,请检查 Redis 自动完成演示。
更新分数:排行榜 (Updating the score: leader boards)
在切换到下一个主题之前,请只对已排序集做最后的说明。排序集的分数可以随时更新。只需对已包含在排序集中的元素调用 ZADD,将以 O(log(N))时间复杂度更新其得分(和位置)。这样,当有大量更新时,排序集是合适的。
由于这种特性,常见的用例是排行榜。典型的应用是 Facebook 游戏,您可以将按高分对用户进行排序的能力与获得排名的操作结合起来,以显示前 N 名的用户以及排行榜中的用户排名(例如,“您是这里的#4932 最佳成绩”)。
位图 (Bitmaps)
位图不是实际的数据类型,而是在 String 类型上定义的一组面向位的操作。由于字符串是二进制安全 Blob,并且最大长度为 512 MB,因此它们适合设置多达 2^32 个不同的位。
位操作分为两类:固定时间的单个位操作(如将一个位设置为 1 或 0 或获取其值),以及对位组的操作,例如计算给定位范围内设置的位的数量(例如,人口计数)。
位图的最大优点之一是,它们在存储信息时通常可以节省大量空间。例如,在以增量用户 ID 表示不同用户的系统中,仅使用 512 MB 内存就可以记住 40 亿用户的一位信息(例如,知道用户是否要接收新闻通讯)。
> setbit key 10 1
(integer) 1
> getbit key 10
(integer) 1
> getbit key 11
(integer) 0SETBIT 命令采用作为第一个参数的比特数,和作为第二个参数的值以设置所述位,其为 1 或 0 的命令自动放大字符串,如果寻址位是当前字符串长度之外。
GETBIT 只是返回指定索引处的位的值。超出范围的位(寻址超出存储在目标键中的字符串长度的位)始终被视为零。
在位组上有三个命令:
- BITOP 在不同的字符串之间执行按位运算。提供的运算为 AND, OR, XOR 和 NOT。
- BITCOUNT 执行填充计数,报告设置为 1 的位数。
- BITPOS 查找指定值为 0 或 1 的第一位。
无论 BITPOS 和 BITCOUNT 能够与字符串的字节范围进行操作,而不是该字符串的整个长度运行。以下是 BITCOUNT 调用的一个简单示例:
> setbit key 0 1
(integer) 0
> setbit key 100 1
(integer) 0
> bitcount key
(integer) 2位图的常见用例是:
- 各种实时分析。
- 存储与对象 ID 相关的空间高效但高性能的布尔信息。
例如,假设您想知道网站用户每天访问量最长的时间。您从零开始计算天数,即从您公开网站的那一天开始,并在用户每次访问该网站时对 SETBIT 进行设置。作为位索引,您只需花费当前的 unix 时间,减去初始偏移量,然后除以一天中的秒数(通常为 3600 * 24)。
这样,对于每个用户,您都有一个小的字符串,其中包含每天的访问信息。使用 BITCOUNT,可以轻松获得给定用户访问网站的天数,而只需几个 BITPOS 调用,或者仅获取和分析客户端的位图,就可以轻松计算最长的连胜记录。
位图很容易分成多个键,例如,为了分片数据集,并且因为通常最好避免使用大键。要在不同的密钥上拆分位图,而不是将所有位都设置为密钥,一个简单的策略就是为每个密钥存储 M 位,并使用来获取密钥名称,使用来获取 bit-number/M 第 N 位 bit-number MOD M。
HyperLogLogs
HyperLogLog 是一种概率数据结构,用于对唯一事物进行计数(从技术上讲,这是指估计集合的基数)。通常,对唯一项目进行计数需要使用与要计数的项目数量成比例的内存量,因为您需要记住过去已经看到的元素,以避免多次对其进行计数。但是,有一组算法会以内存为代价来交换精度:您最终会得到带有标准误差的估计量度,在 Redis 实现的情况下,该误差小于 1%。这种算法的神奇之处在于,您不再需要使用与所计数项目数量成正比的内存量,而是可以使用恒定数量的内存!在最坏的情况下为 12k 字节,如果您的 HyperLogLog(从现在开始将它们称为 HLL)看到的元素很少,则少得多。
Redis 中的 HLL 尽管在技术上是不同的数据结构,但被编码为 Redis 字符串,因此您可以调用 GET 来序列化 HLL,然后调用 SET 来将其反序列化回服务器。
从概念上讲,HLL API 就像使用 Set 来执行相同的任务。你会 SADD 每个观测元素为一组,并且将使用 SCARD 检查组中的元素,这是唯一的数量自 SADD 不会再添加一个现有的元素。
尽管您并未真正 将项目添加 到 HLL 中,但由于数据结构仅包含不包含实际元素的状态,因此 API 相同:
- 每次看到新元素时,都可以使用 PFADD 将其添加到计数中。
到目前为止,每次您要检索 添加 到 PFADD 的唯一元素的当前近似值时,都可以使用 PFCOUNT。
> pfadd hll a b c d (integer) 1 > pfcount hll (integer) 4
该数据结构用例的一个例子是每天计算用户在搜索表单中执行的唯一查询。
Redis 也能够执行 HLL 的合并,请查看 完整的文档 以获取更多信息。
其他值得注意的功能 (Other notable features)
Redis API 中还有其他重要内容,在本文档的上下文中无法探讨,但值得您注意:
- 可以逐步 迭代大型集合的键空间。
- 可以在 服务器端 运行 Lua 脚本 以改善延迟和带宽。
- Redis 还是 Pub-Sub 服务器。
了解更多 (Learn more)
本教程绝不完整,仅涵盖了 API 的基础知识。阅读 命令参考 以发现更多内容。
感谢您的阅读,并祝您使用 Redis 玩得开心!
作者:分布式编程
出处:https://blog.jsdiff.com/
如果你喜欢本文,请长按二维码,关注 分布式编程

说明:本文部分内容基于 Redis 早期版本(如 2.8、3.x)编写,核心数据类型概念至今仍适用,但部分命令细节或默认配置可能在新版本中有所调整。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/redis-shu-ju-lei-xing-jian-jie.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。