Redis【有与无】【T1】Redis数据类型和抽象简介

本文基于Redis 6.0.9版本,前提至少 Redis 3.0或更高版本。

目录

1.Redis数据类型和抽象简介

1.1.Redis keys

1.2.Redis Strings

1.3.更改和查询键空间

1.4.Redis到期:键有限的生存时间

1.5.Redis Lists

1.6.Redis Lists的第一步

1.7.列表的常见用例

1.8.封顶列表

1.9.阻止列表上的操作

1.10.自动创建和删除键

规则1的示例:

规则2的示例:

规则3的示例:

1.11.Redis Hashes

1.12.Redis Sets

1.13.Redis Sorted sets

1.14.在范围内操作

1.15.Lexicographical分数

1.16.更新分数:排行榜

1.17.Bitmaps

1.18.HyperLogLogs

1.19.其他显着特征

1.20.学到更多


1.Redis数据类型和抽象简介

Redis不是简单的key-value存储,它实际上是一个数据结构服务器,支持各种类型的值。 这意味着,尽管在传统的key-value存储中,你将string key与string value相关联,但是在Redis中,该值不仅限于简单的string,还可以容纳更复杂的数据结构。

  • 二进制安全字符串
  • Lists: 根据插入顺序排序的字符串元素集合。 它们基本上是链接列表。
  • Sets: 唯一、未排序的字符串元素的集合。
  • Sorted sets: 与Sets类似,但每个字符串元素都与一个称为分数的浮点值相关联。 元素总是按它们的分数排序,因此与Sets不同,可以检索一系列元素(例如,查看前10名或后10名)。
  • Hashes: 由与值相关联的字段组成的map。 字段和值都是字符串。 这与Ruby或Python哈希非常相似。
  • Bit arrays (or simply bitmaps): 可以使用特殊命令像位数组一样处理字符串值:你可以设置和清除单个bits,计数所有设置为1的bits,找到第一个设置或未设置的位,依此类推。
  • HyperLogLogs:这是一个概率数据结构,用于估计集合的基数。
  • Streams: 提供抽象日志数据类型的map-like entries 的仅追加集合。

从命令参考中掌握这些数据类型的工作方式以及使用什么来解决给定问题并不总是那么容易,因此,本文档是有关Redis数据类型及其最常见模式的速成教程。

对于所有示例,我们将使用redis-cli实用程序(一个简单但方便的命令行实用程序)对Redis服务器发出命令。

1.1.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。

1.2.Redis Strings

Redis字符串类型是你可以与Redis键关联的最简单的值类型。 它是Memcached(分布式的高速缓存)中唯一的数据类型,因此对于新手来说,在Redis中使用它也是很自然的。

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

让我们使用redis-cli来研究字符串类型(在本文中,所有示例都将通过redis-cli执行)。

> set mykey somevalue
OK
> get mykey
"somevalue"

如你所见,使用SET和GET命令是设置和检索字符串值的方式。 请注意,即使键已与非字符串值相关联,SET仍将替换已存储在键中的任何现有值。

值可以是每种类型的字符串(包括二进制数据),例如,你可以在值内存储jpeg图片。 值不能大于512 MB。

SET命令具有有趣的选项,这些选项作为附加参数提供。 例如,如果键已经存在,我可能会要求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) 152

INCR 命令将字符串值解析为整数,将其递增1,最后将获得的值设置为新值。 还有其他类似的命令,例如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返回一个值数组。

1.3.更改和查询键空间

有些命令没有在特定类型上定义,但是在与键空间交互时很有用,因此可以与任何类型的键一起使用。

例如,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
none

1.4.Redis到期:键有限的生存时间

在继续使用更复杂的数据结构之前,我们需要讨论另一个功能,该功能无论值类型如何都可以工作,并且称为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来设置过期时间(也可以用于为已经存在的键设置不同的过期时间,例如可以使用EXPIRE来删除过期并使键永久持久化 )。但是,我们也可以使用其他Redis命令来创建具有到期时间的键。 例如,使用SET选项:

> set key 100 ex 10
OK
> ttl key
(integer) 9

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

为了设置并检查以毫秒为单位的到期时间,请检查PEXPIRE和PTTL命令以及SET选项的完整列表。

1.5.Redis Lists

为了解释List数据类型,最好从理论上入手,因为List一词经常被信息技术人员以不正当的方式使用。 例如,“Python Lists”并不是名称(Linked Lists)的名字,而是数组(在Ruby中,相同的数据类型实际上称为数组)。

从非常普遍的角度来看,列表只是一系列有序元素:10,20,1,2,3是一个列表。 但是,使用数组实现的列表的属性与使用Linked List实现的列表的属性非常不同。

Redis列表是通过链接列表(Linked Lists)实现的。 这意味着即使你在列表中有数百万个元素,在列表的开头或结尾添加新元素的操作也会在恒定时间内执行。 使用LPUSH命令将新元素添加到具有10个元素的列表的开头的速度与将元素添加到具有1000万个元素的列表的开头的速度相同。

有什么缺点? 在使用Array实现的列表中,按索引访问元素的速度非常快(恒定时间索引访问),而在通过链接列表实现的列表中访问速度不是那么快(其中操作需要的工作量与所访问元素的索引成比例)。

Redis列表是使用链接列表实现的,因为对于数据库系统而言,至关重要的是能够以非常快的方式将元素添加到很长的列表中。 稍后你将看到,另一个强大的优势是Redis列表可以在恒定的时间内以恒定的长度获取。

当快速访问大量元素的中间很重要时,可以使用另一种称为排序集的数据结构。 排序的集将在本教程的后面部分介绍。

1.6.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值以表示列表中没有元素。

1.7.列表的常见用例

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

  • 查看用户发布到社交网络上的最近的更新。
  • 使用生产者将项目推送到列表中的消费者与生产者模式进行流程之间的通信,而消费者(通常是worker)则消耗这些项目和已执行的动作。 Redis具有特殊的列表命令,以使此用例更加可靠和高效。

例如,流行的Ruby库resque 和sidekiq 都在后台使用Redis列表,以实现后台作业。

流行的Twitter社交网络将用户发布的最新推文放入Redis列表中。

为了逐步描述一个常见的用例,假设你的主页显示了在照片共享社交网络中发布的最新照片,并且你想加快访问速度。

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

1.8.封顶列表

在许多用例中,我们只想使用列表来存储最新项目,无论它们是什么:社交网络更新,日志或其他任何内容。

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列出列表元素,其他所有内容都将被丢弃。 这允许一个非常简单但有用的模式:一起执行列表推送操作和列表修剪操作,以添加新元素并丢弃超出限制的元素:

LPUSH mylist 
LTRIM mylist 0 999

上面的组合添加了一个新元素,并且仅将1000个最新元素纳入列表。 使用LRANGE,你可以访问最重要的项目,而无需记住非常旧的数据。

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

1.9.阻止列表上的操作

列表具有一项特殊功能,使其适合于实现队列,并且通常用作进程间通信系统的构建块:阻止操作(blocking operations)。

想象一下,你想通过一个流程将项目推入列表,然后使用不同的流程来对这些项目进行某种工作。 这是通常的生产者/使用者设置,可以通过以下简单方式实现:

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

但是,有时列表可能为空,没有任何要处理的内容,因此RPOP仅返回NULL。 在这种情况下,消费者被迫等待一段时间,然后使用RPOP重试。这被称为轮询(polling),在这种情况下不是一个好主意,因为它有几个缺点:

  • 强制Redis和客户端处理无用的命令(列表为空时的所有请求将无法完成任何实际工作,它们只会返回NULL)。
  • 由于工人(worker)在收到NULL之后会等待一段时间,因此会增加项目处理的延迟。 为了减小延迟,我们可以在两次调用RPOP之间等待更少的时间,从而扩大了问题编号1,即对Redis的调用更多了。

因此,Redis实现了称为BRPOP和BLPOP的命令,它们是RPOP和LPOP的版本,如果列表为空,它们可以阻止:仅当将新元素添加到列表中或达到用户指定的超时时,它们才会返回到调用方。

这是我们可以在worker中使用的BRPOP调用的示例:

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

这意味着:“等待列表tasks中的元素,但是如果5秒钟后没有可用元素,则返回”。

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

有关BRPOP的几点注意事项:

  • 客户端以有序的方式服务:第一个阻塞等待列表的客户端,当元素被其他客户端推送时首先服务,依此类推。
  • 返回值与RPOP相比有所不同:它是一个包含两个元素的数组,因为它还包含键的名称,因为BRPOP和BLPOP能够阻止等待来自多个列表的元素。
  • 如果达到超时,则返回NULL。

你还应该了解有关列表和阻止操作的更多信息。 我们建议你阅读以下内容:

  • 使用LMOVE可以建立更安全的队列或轮询队列。
  • 该命令还有一个阻塞变体,称为BLMOVE。

1.10.自动创建和删除键

到目前为止,在我们的示例中,我们无需在推送元素之前创建空列表,也无需在内部不再包含元素时删除空列表。 Redis的责任是在列表为空时删除键,或者在键不存在的情况下创建一个空列表,并且我们正在尝试向其中添加元素,例如使用LPUSH。

这不是特定于列表的,它适用于由多个元素组成的所有Redis数据类型-Streams, Sets, Sorted Sets, Hashes。

基本上,我们可以用三个规则来总结行为:

  1. 当我们将元素添加到聚合数据类型时,如果目标键不存在,则会在添加元素之前创建一个空的聚合数据类型。
  2. 当我们从聚合数据类型中删除元素时,如果该值保持为空,则键将自动销毁。 Stream数据类型是此规则的唯一例外。
  3. 调用带有空键的只读命令(例如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)

1.11.Redis Hashes

Redis hashe 与field-value对看起来完全一样,可能是人们期望的“hash”外观:

> 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"

尽管hashes很容易表示对象,但是实际上可以放入hashes中的字段数没有实际限制(可用内存除外),因此你可以在应用程序内部以多种不同方式使用hashes。

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

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

值得注意的是,小hashes(即一些具有较小值的元素)在内存中以特殊方式进行了编码,从而使其具有很高的内存效率。

1.12.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 hash,它将标签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)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上对其进行称呼,那么在游戏的下一场比赛中,我们将需要再次填充卡片组,这可能并不理想。 首先,我们可以将存储在套牌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"

一对jacks,不是很好...

现在是引入set命令的好时机,该命令提供集合中元素的数量。 在集合论的上下文中,这通常称为集合的基数(cardinality of a set),因此Redis命令称为SCARD。

> scard game:1:deck
(integer) 47

数学计算:52-5 = 47。

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

1.13.Redis Sorted sets

排序的集合是一种数据类型,类似于集合和哈希之间的混合。 像集一样,排序集由唯一的,非重复的字符串元素组成,因此从某种意义上说,排序集也是一个集。

但是,虽然集内的元素没有排序,但是排序后的集合中的每个元素都与一个称为得分的浮点值相关联(这就是为什么该类型也类似于哈希的原因,因为每个元素都映射到一个值)。

此外,已排序集合中的元素是按顺序进行的(因此,它们不是应请求而排序的,顺序是用于表示已排序集合的数据结构的特殊性)。 它们按照以下规则排序:

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

让我们从一个简单的示例开始,添加一些选定的黑客名称作为排序的集合元素,并以其出生年份为“score”。

 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也是可变参数,因此即使在上面的示例中未使用它,你也可以自由指定多个score-value对。

使用排序集,返回按其出生年份排序的黑客列表很简单,因为实际上他们已经被排序了。

实施注意事项:排序集是通过包含跳过列表和哈希表的双端口数据结构实现的,因此,每次添加元素时,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"

也可以使用 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.14.在范围内操作

排序集比这更强大。 它们可以在范围内操作。 让我们获取所有出生到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) 4

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

为排序的集合元素定义的另一个极其有用的操作是get-rank操作。 可以问一个元素在有序元素集合中的位置是什么。

> zrank hackers "Anita Borg"
(integer) 4

ZREVRANK命令也可用于获得排名,考虑到元素按降序排序。

1.15.Lexicographical分数

在最新版本的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字节前缀的sorted set。由于在大尾数中的数字实际上按数字顺序也按字典顺序(以原始字节顺序)排序,因此你可以要求128位空间中的范围,并获得丢弃前缀的元素值。

如果要在更严重的演示环境中查看该功能,请查看Redis自动完成演示。

1.16.更新分数:排行榜

在切换到下一个主题之前,请只对已排序集做最后的说明。 排序集的分数可以随时更新。 只需对已包含在排序集中的元素调用ZADD,将以O(log(N))时间复杂度更新其得分(和位置)。 这样,当有大量更新时,排序集是合适的。

由于此特性,常见的用例是排行榜。 典型的应用是Facebook游戏,你可以结合使用按照高分对用户进行排序的能力以及排名操作,以显示排名前N位的用户以及排行榜中的用户排名(例如,“你是这里的#4932最佳成绩”)。

1.17.Bitmaps

Bitmaps不是实际的数据类型,而是在String类型上定义的一组bit-oriented的操作。 由于字符串是二进制安全Blob,并且最大长度为512 MB,因此它们适合设置多达2^{32}个不同的bits。

Bit操作分为两类:固定时间的单个位操作(如将一个位设置为1或0或获取其值),以及对位组的操作,例如计算给定位范围内设置的位的数量 (例如,人口计数)。

bitmaps的最大优点之一是,它们在存储信息时通常可以节省大量空间。 例如,在以增量用户ID表示不同用户的系统中,仅使用512 MB内存就可以记住40亿用户的一位信息(例如,知道用户是否要接收新闻通讯)。

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

> setbit key 10 1
(integer) 1
> getbit key 10
(integer) 1
> getbit key 11
(integer) 0

SETBIT命令将位号作为其第一个参数,将其设置为1或0的值作为其第二个参数。如果寻址的位超出当前字符串长度,则该命令将自动放大字符串。

SETBIT只是返回指定索引处的bit的值。 超出范围的位(寻址超出存储在目标键中的字符串长度的位)始终被视为零。

在bits组上有三个命令:

  1. BITOP在不同的字符串之间执行bit-wise运算。 提供的运算为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

bitmaps的常见用例是:

  • 各种实时分析。
  • 存储与对象ID相关联的空间高效但高性能的布尔信息。

例如,假设你想知道网站用户每天访问量最长的时间。 你开始从零开始计数,即从你公开网站的那一天开始,并在用户每次访问该网站时对SETBIT进行设置。 作为位索引,你只需花费当前的unix时间,减去初始偏移量,然后除以一天中的秒数(通常为3600*24)。

这样,对于每个用户,你都有一个小的字符串,其中包含每天的访问信息。 使用BITCOUNT,可以轻松获得给定用户访问该网站的天数,而只需几个BITPOS调用,或者仅获取并分析客户端的bitmap,就可以轻松计算出最长的轨迹。

Bitmaps很容易拆分成多个键,例如,为了分片数据集,因为通常最好避免使用大键。 要在不同的键上拆分位图,而不是将所有位都设置为键,一个简单的策略是每个键存储M位,并获得具有bit-number/M的键名,并使用bit获得第N位以 bit-number MOD M 内部寻址键。

1.18.HyperLogLogs

HyperLogLog是一种概率数据结构,用于对唯一事物进行计数(从技术上讲,这是指估计集合的基数)。 通常,对唯一项目进行计数需要使用与要计数的项目数量成比例的内存量,因为你需要记住过去已经看到的元素,以避免多次对其进行计数。 但是,有一组算法会以内存为代价来交换精度:你最终会得到带有标准误差的估计量,在Redis实现的情况下,该误差小于1%。 该算法的神奇之处在于,你不再需要使用与所计数项目数成正比的内存量,而是可以使用恒定数量的内存! 在最坏的情况下为12k bytes,如果你的HyperLogLog(从现在开始将它们称为HLL)看到的元素很少,则少得多。

Redis中的HLL尽管在技术上是不同的数据结构,但是被编码为Redis字符串,因此你可以调用GET来序列化HLL,然后调用SET来将其反序列化回服务器。

从概念上讲,HLL API就像使用Set来完成相同的任务。 你可以将每个观察到的元素添加到集合中,并使用SCARD检查集合中的元素数量,这是唯一的,因为SADD不会重新添加现有元素。

尽管你并未真正将项目添加到HLL中,但由于数据结构仅包含不包含实际元素的状态,因此API相同:

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

此数据结构用例的一个例子是,每天计算用户在搜索表单中执行的唯一查询。

Redis也可以执行HLL的合并,请查看完整的文档以获取更多信息。

1.19.其他显着特征

Redis API中还有其他重要内容,在本文档的上下文中无法探讨,但值得你注意:

  • 可以逐步迭代大型集合的键空间。
  • 可以在服务器端运行Lua脚本以改善延迟和带宽。
  • Redis还是Pub-Sub服务器。

1.20.学到更多

本教程绝不完整,仅介绍了API的基础知识。 阅读命令参考以发现更多内容。

感谢你的阅读,并祝你使用Redis玩得开心!

你可能感兴趣的:(Redis,redis)