Redis 是一个数据结构服务器。其核心是提供了一系列原生数据类型,可帮助您解决从缓存、队列到事件处理等各种各样的问题。以下是对常用的数据类型的简要描述,并提供指向更详细概述和命令参考的链接。
Strings
Redis的字符串类型是最基本的数据类型,字符串存储字节序列,包括文本、序列化的对象和二进制数组。它们通常用于缓存,但还支持额外的功能,比如可以使用string实现计数器并执行位运算。
使用 SET 和 GET 命令是我们设置和检索字符串值的方式。请注意,如果键已经存在,即使键关联的是非字符串值, SET 也会替换任何已经存储在键中的值。所以 SET 执行的是赋值操作。
bash
> SET bike:1 Deimos
OK
> GET bike:1
"Deimos"
值可以是各种类型的字符串(包括二进制数据),例如你可以在一个值中存储一个 jpeg 图像。值的大小不能超过 512 MB。
SET 命令有一些有趣的选项,这些选项作为附加参数提供。例如,我可以要求 SET 如果键已存在则失败,或者相反,只有当键已存在时才成功:
bash
> set bike:1 bike nx
(nil)
> set bike:1 bike xx
OK
字符串操作有许多其他命令。例如, GETSET 命令将键设置为新的值,并返回旧值作为结果
在单个命令中设置或检索多个键的值的能力对于降低延迟也很有用。因此,存在 MSET 和 MGET 命令:
bash
> mset bike:1 "Deimos" bike:2 "Ares" bike:3 "Vanth"
OK
> mget bike:1 bike:2 bike:3
1) "Deimos"
2) "Ares"
3) "Vanth"
string 作为计数器
即使字符串是 Redis 的基本值,我们仍然可以对他们执行一些有趣的操作。例如,一个是原子自增:
bash
> set total_crashes 0
OK
> incr total_crashes
(integer) 1
> incrby total_crashes 10
(integer) 11
INCR 命令将字符串值解析为整数,增加一,并将获得的新值设置为新的值。还有其他类似的命令,如 INCRBY 、 DECR 和 DECRBY 。内部始终是相同的命令,但以稍微不同的方式执行。
INCR的原子性 意味着即即使多个客户端同时对同一个键执行 INCR 操作,也不会进入竞争条件。例如,永远不会发生客户端 1 读取"10",客户端 2 同时读取"10",两者都增加到 11,并将新值设置为 11 的情况。最终值总是 12,而读取-增加-设置操作是在所有其他客户端不在执行命令的同时完成的。
Limits
默认情况下,单个 Redis 字符串的最大长度为 512 MB。
基本命令
SET存储一个字符串值。SETNX仅当键不存在时存储字符串值。用于实现锁。GET获取一个字符串值。MGET在一次操作中检索多个字符串值。INCR原子性地将给定键存储的计数器加 1。INCRBY原子性地增加(当传递负数时减少)给定键存储的计数器。INCRBYFLOAT用于浮点计数器的命令
Lists
Redis 列表是由字符串值组成的链表。Redis 列表常用于:
- 实现栈和队列。
- 为后台工作系统构建队列管理。
什么是列表?
为了解释列表数据类型,最好先从一点理论开始,因为列表这个词在信息技术人员中经常被错误地使用。例如,"Python 列表"并不像名字暗示的那样(链表),而是数组(在 Ruby 中,这个数据类型实际上也被称为数组)。
从非常一般的角度来看,列表只是一个有序元素的序列:10,20,1,2,3 就是一个列表。但是使用数组实现的列表属性与使用链表实现的列表属性非常不同。
Redis 列表通过链表实现。这意味着即使列表中有数百万个元素,向列表头部或尾部添加新元素的操作也是常数时间。使用 LPUSH 命令向包含十个元素的列表头部添加新元素的速率与向包含一千万个元素的列表头部添加元素的速率相同。
有什么缺点?在用数组实现的列表中,通过索引访问元素非常快(常数时间索引访问),而在用链表实现的列表中则不那么快(操作所需工作量与访问元素的索引成正比)。
Redis 列表使用链表实现,因为对于一个数据库系统来说,能够非常快速地向一个非常长的列表中添加元素至关重要。另一个显著的优点,是 Redis 列表可以在常数时间内以常数长度进行读取。
当快速访问大量元素的中很重要时,可以使用另一种数据结构,称为有序集合。
基本命令
LPUSH向列表头部添加新元素RPUSH向尾部添加新元素LPOP从列表头部移除并返回一个元素RPOP从列表尾部移除并返回一个元素LLEN返回列表长度LMOVE原子性地将元素从一个列表移动到另一个列表。LRANGE从列表中提取一系列元素LTRIM将列表缩减到指定的元素范围BLPOP从列表头部移除并返回一个元素。如果列表为空,则命令会阻塞,直到有元素可用或达到指定的超时时间。BLMOVE原子地将元素从源列表移动到目标列表。如果源列表为空,则命令会阻塞,直到有新元素可用。
快速入门
LPUSH 命令将新元素添加到列表的左侧(头部),而 RPUSH 命令将新元素添加到列表的右侧(尾部)。最后, LRANGE 命令可以从列表中提取元素范围:
bash
> RPUSH bikes:repairs bike:1
(integer) 1
> RPUSH bikes:repairs bike:2
(integer) 2
> LPUSH bikes:repairs bike:important_bike
(integer) 3
> LRANGE bikes:repairs 0 -1
1) "bike:important_bike"
2) "bike:1"
3) "bike:2"
注意 LRANGE 需要两个索引,即返回范围的第一个和最后一个元素。这两个索引都可以是负数,告诉 Redis 从末尾开始计数:所以-1 是最后一个元素,-2 是倒数第二个元素,依此类推。
RPUSH 将元素添加到列表的右侧,而最后的 LPUSH 将元素添加到左侧。这两个命令都是可变参数命令,意味着您可以在一个调用中自由地推送多个元素到列表中
Lists的另一个重要操作是弹出元素的功能。弹出元素是指同时从列表中检索元素并消除它的操作。你可以从左右两边弹出元素,就像你可以在列表的两边推入元素一样。我们将添加三个元素并弹出三个元素,因此在这一系列命令执行完毕后,列表为空,没有更多元素可以弹出:
bash
> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
> RPOP bikes:repairs
"bike:3"
> LPOP bikes:repairs
"bike:1"
> RPOP bikes:repairs
"bike:2"
> RPOP bikes:repairs
(nil)
Redis 返回了一个 NULL 值,表示列表中没有元素。
示例
常见用例
列表在许多任务中都很有用,以下两个非常典型的用例是:
- 记住用户在社交网络中发布的最新更新。
- 使用生产者-消费者模式进行进程间通信,其中生产者将项目推入列表,消费者(通常是工作者)消费这些项目并执行操作。Redis 有特殊的列表命令,使这个用例更加可靠和高效。
例如,流行的 Ruby 库 resque 和 sidekiq 在底层都使用 Redis 列表来实现后台任务。Twitter 社交网络将用户发布的最新推文纳入 Redis 列表。
你的主页显示了最新发布的照片,而希望加快访问速度:
- 每次用户发布一张新照片,我们都会将其 ID 添加到包含
LPUSH的列表中。 - 当用户访问主页时,我们使用
LRANGE 0 9来获取最新的 10 个发布条目。
对列表的阻塞操作
列表具有一个特殊功能,使它们适合实现队列,并且在一般情况下,作为进程间通信系统的构建块:阻塞操作。
如果你想要用一个进程将数据推入列表,而用另一个进程实际对这些数据进行某种处理。这是常见的生产者/消费者设置,可以以下简单的这种方式实现:
- 要将数据推入列表,生产者调用
LPUSH。 - 要从列表中提取/处理数据,消费者调用
RPOP。
然而,有时列表可能为空,没有可处理的内容,所以 RPOP 只会返回 NULL。在这种情况下,消费者被迫等待一段时间后,再使用 RPOP 重新尝试。这被称为轮询,在这个上下文中并不是一个好主意,因为它有几个缺点:
- 迫使 Redis 和客户端处理无用的命令(当列表为空时,所有请求都不会完成实际工作,它们只会返回 NULL)。
- 增加了处理项目的延迟,因为工人在收到 NULL 后会等待一段时间。为了缩短延迟,我们可以在调用
RPOP之间等待更短的时间,但这会放大问题 1,即更多无用的 Redis 调用。
Redis 实现了名为 BRPOP 和 BLPOP 的命令,它们是 RPOP 和 LPOP 的阻塞版本,能够在列表为空时阻塞:只有当列表中添加了新元素或达到用户指定的超时时间时,才会返回给调用者。
这是一个在工作者中可以使用的 BRPOP 调用的示例:
bash
> RPUSH bikes:repairs bike:1 bike:2
(integer) 2
> BRPOP bikes:repairs 1
1) "bikes:repairs"
2) "bike:2"
> BRPOP bikes:repairs 1
3) "bikes:repairs"
4) "bike:1"
> BRPOP bikes:repairs 1
(nil)
(2.01s)
这意味着:"等待列表 bikes:repairs 中的元素,但若 1 秒后没有元素可用则返回"。
需要注意的是,可以将 0 用作超时以无限期等待元素,并且您还可以指定多个列表而不是仅一个,以便同时等待多个列表,并在第一个列表收到元素时得到通知。
关于 BRPOP 的注意事项:
- 客户端按顺序服务:当某个客户端向列表中推入元素时,首先服务第一个因等待列表而阻塞的客户端,依此类推。
- 与
RPOP相比,返回值不同:它是一个包含两个元素的数组,因为它还包括键名,因为BRPOP和BLPOP能够阻塞等待多个列表中的元素。 - 如果超时时间到达,则返回 NULL。
关于列表和阻塞操作,还有更多应该知道的事情
- 可以使用
LMOVE来构建更安全的队列或循环队列LMOVE - 该命令还有一个阻塞变体,称为
BLMOVE。
Limits
Redis 列表的最大长度为 2^32 - 1(4,294,967,295)个元素。
性能
列表操作访问头部或尾部的时间复杂度为 O(1),这意味着它们非常高效。然而,操作列表内部元素的操作通常为 O(n)。这些操作的例子包括 LINDEX 、 LINSERT 和 LSET 。运行这些命令时要小心,尤其是在操作大型列表时。
Sets
Redis 集合是一个无序的唯一字符串(成员)集合。可以使用 Redis 集合高效地:
- 追踪独特项目(例如,追踪访问特定博客文章的所有唯一 IP 地址)。
- 表示关系(例如,具有特定角色的所有用户的集合)。
- 执行常见的集合操作,如交集、并集和差集。
基本命令
SADD向集合中添加新成员。SREM从集合中删除指定的成员。SISMEMBER测试字符串是否为集合成员SINTER返回两个或更多集合共有的成员集合(即交集)SCARD返回集合的大小(也称为基数)。
示例
SADD 命令可以向集合中添加新元素。还可以对集合执行许多其他操作,例如检查给定元素是否已存在,执行多个集合的交集、并集或差集操作等。
bash
> SADD bikes:racing:france bike:1 bike:2 bike:3
(integer) 3
> SMEMBERS bikes:racing:france
1) bike:3
2) bike:1
3) bike:2
集合中添加了三个元素,并告诉 Redis 返回所有元素。集合没有顺序保证。Redis 可以在每次调用时自由地以任何顺序返回元素。
Redis 有用于测试集合成员资格的命令。这些命令可以用于单个数据,也可以用于多个数据:
bash
> SISMEMBER bikes:racing:france bike:1
(integer) 1
> SMISMEMBER bikes:racing:france bike:2 bike:3 bike:4
1) (integer) 1
2) (integer) 1
3) (integer) 0
我们还可以找到两个集合之间的差异。例如,我们可能想知道哪些自行车在法国比赛,但不在美国比赛:
bash
> SADD bikes:racing:usa bike:1 bike:4
(integer) 2
> SDIFF bikes:racing:france bikes:racing:usa
1) "bike:3"
2) "bike:2"
还有一些其他特殊的操作,仍然可以通过使用 Redis 命令轻松实现。例如,我们可能想要一个在法国、美国和某些其他比赛中比赛的自行车的列表。我们可以使用 SINTER 命令来完成这个操作,该命令执行不同集合之间的交集。除了交集,您还可以执行并集、差集等操作。例如,如果我们添加第三个比赛,我们可以看到一些这些命令的实际应用:
bash
> SADD bikes:racing:france bike:1 bike:2 bike:3
(integer) 3
> SADD bikes:racing:usa bike:1 bike:4
(integer) 2
> SADD bikes:racing:italy bike:1 bike:2 bike:3 bike:4
(integer) 4
> SINTER bikes:racing:france bikes:racing:usa bikes:racing:italy
1) "bike:1"
> SUNION bikes:racing:france bikes:racing:usa bikes:racing:italy
2) "bike:2"
3) "bike:1"
4) "bike:4"
5) "bike:3"
> SDIFF bikes:racing:france bikes:racing:usa bikes:racing:italy
(empty array)
> SDIFF bikes:racing:france bikes:racing:usa
6) "bike:3"
7) "bike:2"
> SDIFF bikes:racing:usa bikes:racing:france
8) "bike:4"
当所有集合之间的差异为空时, SDIFF 命令返回一个空数组。还会注意到,传递给 SDIFF 命令的集合顺序很重要,因为差集不是交换的。
Limits
Redis 集合的最大大小为 2^32 - 1(4,294,967,295)个成员。
性能
大多数集合操作,包括添加、删除和检查一个项目是否是集合成员,都是 O(1)的。这意味着它们非常高效。然而,对于拥有数十万或更多成员的大集合,在运行 SMEMBERS 命令时应该谨慎。这个命令是 O(n)的,并且会一次性返回整个集合。作为替代方案,可以考虑使用 SSCAN ,它允许你迭代地检索集合的所有成员。
替代方案
集合在大数据集(或流数据)上的成员资格检查可能会消耗大量内存。如果您担心内存使用,并且不需要完美的精度,可以考虑使用布隆过滤器或布谷鸟过滤器作为集合的替代方案。
Hashes
Redis 哈希表是一种记录类型,结构为字段-值对的集合。可以使用哈希表来表示基本对象,以及存储计数器分组等。
bash
> HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972
(integer) 4
> HGET bike:1 model
"Deimos"
> HGET bike:1 price
"4972"
> HGETALL bike:1
1) "model"
2) "Deimos"
3) "brand"
4) "Ergonom"
5) "type"
6) "Enduro bikes"
7) "price"
8) "4972"
虽然hashes很方便用来表示对象,但实际上可以在hashes中放入的字段数量没有实际限制(除了可用内存),所以可以在应用程序中以多种方式使用hashes。
命令 HSET 可以设置散列的多个字段,而 HGET 可以检索单个字段。 HMGET 与 HGET 类似,但返回一个值数组:
bash
> HMGET bike:1 model price no-such-field
1) "Deimos"
2) "4972"
3) (nil)
还有一些命令可以对单个字段执行操作,例如 HINCRBY :
bash
> HINCRBY bike:1 price 100
(integer) 5072
> HINCRBY bike:1 price -100
(integer) 4972
基本命令
字段过期
Redis 7.4 引入了为单个哈希字段指定过期时间或生存时间(TTL)值的功能。这个功能与键过期类似,包括一系列相似的命令。
使用以下命令设置特定字段的精确过期时间或 TTL 值:
HEXPIRE: 设置剩余的 TTL(生存时间)为秒。HPEXPIRE: 设置剩余的 TTL(生存时间)为毫秒。HEXPIREAT: 将过期时间设置为以秒为单位的时间戳。HPEXPIREAT: 将过期时间设置为以毫秒为单位的时间戳。
使用以下命令可以检索特定字段将过期的确切时间或剩余的 TTL:
HEXPIRETIME: 以秒为单位获取过期时间的时间戳HPEXPIRETIME: 获取过期时间,以毫秒为时间戳。HTTL: 获取剩余的 TTL(生存时间),以秒为单位。HPTTL: 获取剩余的 TTL,以毫秒为单位
使用以下命令来移除特定字段的过期时间:
HPERSIST: 移除过期时间。
Redis 8.0 引入了以下命令:
HGETEX: 获取给定哈希键的一个或多个字段的值,并可选地设置它们的过期时间或生存时间(TTL)。HSETEX: 设置给定哈希键的一个或多个字段的值,并可选地设置它们的过期时间或生存时间(TTL)。
Limits
每个哈希可以存储最多 4,294,967,295(2^32 - 1)个键值对。实际上,你哈希存储限制仅由托管 Redis 部署的虚拟机的总内存决定。
性能
大多数 Redis 哈希命令都是 O(1)。
一些命令,如 HKEYS 、 HVALS 、 HGETALL 以及大多数与过期相关的命令,是 O(n),其中 n 是键值对的数量。
Sorted sets
Redis 有序集合是一组唯一的字符串(成员),按照与它们关联的分数进行排序。当多个字符串具有相同的分数时,这些字符串将按照字典顺序排序。有序集合的一些用例包括:
- 排行榜。例如可以使用有序集合轻松维护一个大型在线游戏中最高分数的有序列表。
- 速率限制器。特别是可以使用有序集合构建一个滑动窗口速率限制器,以防止过多的 API 请求。
你可以将有序集合视为集合和哈希的混合体。像集合一样,有序集合由唯一的、不重复的字符串元素组成,所以在某种程度上,有序集合也是一个集合。
然而,虽然集合内的元素是无序的,但有序集合中的每个元素都与一个浮点值相关联,称为分数(这也是为什么类型也类似于哈希,因为每个元素都映射到一个值)。
此外,有序集合中的元素是按顺序取出的(因此它们不是按请求排序的,顺序是有序集合所使用的数据结构的特性)。它们的排序规则如下:
- 如果 B 和 A 是两个具有不同分数的元素,那么当 A.score > B.score 时,A > B。
- 如果 B 和 A 的分数完全相同,那么当 A 字符串在字典序上大于 B 字符串时,A > B。由于有序集合只包含唯一元素,B 和 A 字符串不能相等。
基本命令
ZADD向有序集合中添加新成员及其关联的分数。如果成员已存在,则更新分数。ZRANGE返回有序集合中给定范围内的成员,并按顺序排序。ZRANK返回提供的成员的排名,有序集合是升序排列的。ZREVRANK返回提供的成员的排名,有序集合是降序排列的。
示例
让我们从一个简单的例子开始,我们将添加所有我们的赛车手以及他们在第一场比赛中获得的分数:
bash
> ZADD racer_scores 10 "Norem"
(integer) 1
> ZADD racer_scores 12 "Castilla"
(integer) 1
> ZADD racer_scores 8 "Sam-Bodden" 10 "Royce" 6 "Ford" 14 "Prickett"
(integer) 4
ZADD 与 SADD 类似,但多了一个额外的参数(放置在要添加的元素之前),即分数。 ZADD 同样是可变参数,因此可以自由指定多个分数-值对,如上面示例所示。
使用有序集合,按分数返回赛车手列表是件简单的事,因为实际上它们已经是有序的。
实现说明:有序集合通过包含跳表和哈希表的二端口数据结构实现,因此每次我们添加一个元素,Redis 都会执行 O(log(N)) 操作。所以当我们请求有序元素时,Redis 不需要做任何工作,它已经是有序的。注意, ZRANGE 的顺序是低到高,而 ZREVRANGE 的顺序是高到低:
bash
> ZRANGE racer_scores 0 -1
1) "Ford"
2) "Sam-Bodden"
3) "Norem"
4) "Royce"
5) "Castilla"
6) "Prickett"
> ZREVRANGE racer_scores 0 -1
7) "Prickett"
8) "Castilla"
9) "Royce"
10) "Norem"
11) "Sam-Bodden"
12) "Ford"
注意:0 和 -1 表示从元素索引 0 到最后一个元素(-1 在这里的作用与 LRANGE 命令中的作用相同)。
可以返回分数,使用 WITHSCORES 参数:
bash
> ZRANGE racer_scores 0 -1 withscores
1) "Ford"
2) "6"
3) "Sam-Bodden"
4) "8"
5) "Norem"
6) "10"
7) "Royce"
8) "10"
9) "Castilla"
10) "12"
11) "Prickett"
12) "14"
有序集合可以操作范围。让我们获取所有得分在 10 分或更少的赛车手。我们使用 ZRANGEBYSCORE 命令来完成:
bash
> ZRANGEBYSCORE racer_scores -inf 10
1) "Ford"
2) "Sam-Bodden"
3) "Norem"
4) "Royce"
我们要求 Redis 返回所有分数在负无穷大到 10(包括两个极端)之间的所有元素。
要删除一个元素,我们只需调用带有车手名字的 ZREM 即可。也可以删除一系列元素。让我们删除车手 Castilla 以及所有得分严格少于 10 分的车手:
bash
> ZREM racer_scores "Castilla"
(integer) 1
> ZREMRANGEBYSCORE racer_scores -inf 9
(integer) 2
> ZRANGE racer_scores 0 -1
1) "Norem"
2) "Royce"
3) "Prickett"
为有序集合元素定义的另一个极其有用的操作是获取排名操作。可以询问一个元素在有序元素集中的位置。 ZREVRANK 命令也可以用来获取排名,考虑到元素按降序排列。
bash
> ZRANK racer_scores "Norem"
(integer) 0
> ZREVRANK racer_scores "Norem"
(integer) 2
有两种方式可以使用有序集合来表示排行榜。如果我们知道一名选手的新分数,我们可以直接通过 ZADD 命令来更新它。然而,如果我们想给现有分数加分,我们可以使用 ZINCRBY 命令。
bash
> ZADD racer_scores 100 "Wood"
(integer) 1
> ZADD racer_scores 100 "Henshaw"
(integer) 1
> ZADD racer_scores 150 "Henshaw"
(integer) 0
> ZINCRBY racer_scores 50 "Wood"
"150"
> ZINCRBY racer_scores 50 "Henshaw"
"200"
你会发现当成员已经存在(分数被更新)时, ZADD 返回 0,而 ZINCRBY 返回新的分数。选手 Henshaw 的分数从 100 变为 150,不考虑之前的分数是多少,然后增加了 50 分,变成了 200。
字典序分数
在 Redis 2.8 版本中,引入了一个新功能,允许按字典序获取范围,假设排序集合中的元素都是使用相同的相同分数插入的(元素使用 C memcmp 函数进行比较,因此保证没有排序,每个 Redis 实例都将返回相同的输出)。
操作字典序范围的主要命令是 ZRANGEBYLEX 、 ZREVRANGEBYLEX 、 ZREMRANGEBYLEX 和 ZLEXCOUNT 。
例如,让我们再次添加我们的著名赛车手列表,但这次为所有元素使用零分数。我们会看到,由于排序集合的排序规则,它们已经按字典序排序。使用 ZRANGEBYLEX 我们可以请求字典序范围:
bash
> ZADD racer_scores 0 "Norem" 0 "Sam-Bodden" 0 "Royce" 0 "Castilla" 0 "Prickett" 0 "Ford"
(integer) 3
> ZRANGE racer_scores 0 -1
1) "Castilla"
2) "Ford"
3) "Norem"
4) "Prickett"
5) "Royce"
6) "Sam-Bodden"
> ZRANGEBYLEX racer_scores [A [L
7) "Castilla"
8) "Ford"
范围可以是包含的或排除的(取决于第一个字符),字符串的无穷大和负无穷大分别用 + 和 - 字符串指定。
这个功能很重要,因为它允许我们将有序集合用作通用索引。例如,如果您想按 128 位无符号整数参数索引元素,您只需将元素添加到具有相同分数(例如 0)的有序集合中,但具有由大端 128 位数字组成的 16 字节前缀。由于大端序号在按字典序(原始字节顺序)排序时实际上是按数值排序的,因此您可以请求 128 位空间内的范围,并获取元素的值,忽略前缀。
性能
大多数有序集合操作的时间复杂度为 O(log(n)),其中 n 是成员数量。
运行带有大返回值(例如,数万或更多)的 ZRANGE 命令时要小心。该命令的时间复杂度为 O(log(n) + m),其中 m 是返回的结果数量。
替代方案
Redis 有序集合有时用于索引其他 Redis 数据结构。如果需要索引和查询数据,请考虑使用 JSON 数据类型和 Redis 查询引擎功能。