Redis:Redis 命令详解

文章目录

Redis 命令详解

客户端应用程序和各种工具都是通过命令与 Redis 进行交互的。大多数命令是用来实现数据类型的存储和检索,但也有一部分命令专门用于处理服务器配置、安全性以及其他管理功能。 下面的章节将概述 Redis 命令是如何运作的。如果你想查阅所有命令的完整列表,可以参考 Redis 命令参考文档

命令结构

每个命令都有一个独一无二的名称来标识。相关的一组命令通常遵循一致的命名约定。举个例子,所有处理哈希的命令都以 H 开头(如 HSET, HGET)。大多数命令都会接收一个或多个参数,用来指定要操作的数据。对于数据类型相关的命令来说,第一个参数通常就是用来标识目标数据对象的

当你发出一个命令后,服务器会尝试处理它,然后返回一个响应。

  • 更新数据的命令 :通常会返回一个状态消息(比如 OK),或者一个数字,表示被修改或更新的条目数量。
  • 检索数据的命令:直接返回你请求的数据。
  • 失败的命令:返回一个错误消息,描述具体出了什么问题。

Redis 服务器 客户端 Redis 服务器 客户端 例如: SET user:1 "Alice" alt [命令成功] [命令失败] 发送命令 (Command + Key + Args) 处理请求 返回响应 (OK / 数据 / 计数) 返回错误

键与值

你在 Redis 数据库中存储的每一个数据对象都有其唯一的 。键是一个字符串,你把它传递给 Redis 命令,用来获取对应的对象或修改其数据。与特定键关联的数据对象被称为 ,这两者合在一起就是我们常说的键值对

键的内容

键通常是一个在你的数据模型中有特定意义的文本名称。与编程语言中的变量名不同,Redis 对键的格式几乎没有限制,因此包含空格或标点符号的键大多也是可以的(例如 "1st Attempt""% of price in $")。

Redis 本身不支持命名空间或其他键的分类机制,所以你必须小心避免命名冲突。不过,有一个约定俗成的做法是使用冒号 : 字符将键分成几个部分(例如 "person:1""person:2""office:London""office:NewYork:1")。你可以将其作为一种将键归类到简单类别中的方法。

虽然键通常是文本,但 Redis 实际上实现了二进制安全的键,这意味着你可以使用任何字节序列作为有效的键,比如一个 JPEG 文件或你应用程序中的结构体值。空字符串在 Redis 中也是一个有效的键。

关于键,还有几点需要牢记在心:

  • 非常长的键不是好主意。例如,一个 1024 字节的键不仅从内存角度看是不好的,而且在数据集中查找该键可能需要多次昂贵的键比较。即使当前的任务是匹配一个大值的存在性,对其进行哈希处理(例如使用 SHA1)也是个更好的主意,尤其是在内存和带宽方面。
  • 非常短的键通常也不是好主意 。如果你能写 "user:1000:followers",就没必要写 "u1000flw"。后者更具可读性,而且与键对象本身和值对象所使用的空间相比,增加的空间微乎其微。虽然短键显然会消耗更少的内存,但你的任务是找到正确的平衡点。
  • 尽量坚持使用某种模式 。例如 "object-type:id" 是个好主意,如 "user:1000"。点或连字符常用于多单词字段,如 "comment:4321:reply.to""comment:4321:reply-to"
  • 允许的最大键大小是 512 MB

映射到

Key

Value
user:1000:followers

有结构,可读性好
u1000flw

太短,难懂
1024字节的超长字符串...

浪费资源,查找慢

哈希标签

Redis 使用哈希来高效地检索与键关联的值。哈希涉及将键中的原始字节值组合起来,生成一个整数索引。然后,该索引用于定位存储该键值的哈希槽。

通常,计算哈希索引时会使用整个键,但在某些情况下,你只需要对键的一部分进行哈希计算。你可以使用一对花括号 {...} 选择你想要哈希的键部分,从而创建一个哈希标签 。例如,键 person:1person:2 会产生不同的哈希索引,但 {person}:1{person}:2 会产生相同的索引,因为计算哈希时只使用了花括号内的 person 部分。

哈希标签的一个常见用途是允许在集群数据库中进行多键操作。除非所有键都产生相同的哈希索引,否则 Redis 不允许在集群数据库中执行大多数多键操作。例如,SINTER 命令查找两个不同集合值的交集。这意味着:

bash 复制代码
SINTER group:1 group:2

这个命令在集群数据库中是行不通的,但是:

bash 复制代码
SINTER {group}:1 {group}:2

这行得通,因为哈希标签确保了这两个键产生相同的哈希索引。

请注意,虽然哈希标签在某些情况下很有用,但你不应该习惯性地到处使用它们。如果有太多键映射到同一个哈希槽,最终会损害数据库的性能。
使用哈希标签
计算出 Slot X
计算出 Slot X
{group}:1
哈希函数 仅计算 {group}
{group}:2
哈希函数 仅计算 {group}
Slot X
正常哈希
计算出 Slot A
计算出 Slot B
group:1
哈希函数
group:2
哈希函数
Slot A
Slot B

修改和查询键空间

有一些命令并不是针对特定类型定义的,但它们用于与键空间交互,因此可以用于任何类型的键。 例如,EXISTS 命令返回 1 或 0,以指示给定键是否存在于数据库中;而 DEL 命令则删除键及其关联的值,无论该值是什么类型。

bash 复制代码
> set mykey hello
OK
> exists mykey
(integer) 1
> del mykey
(integer) 1
> exists mykey
(integer) 0

有许多与键空间相关的命令,但上述两个命令与 TYPE 命令是最基础的,TYPE 命令返回指定键处存储的值的类型:

bash 复制代码
> set mykey x
OK
> type mykey
string
> del mykey
(integer) 1
> type mykey
none

键的过期

在继续之前,我们应该看看 Redis 的一个重要特性,该特性无论你存储的值类型如何都能工作:键过期。 键过期允许你为一个键设置超时时间,也称为"生存时间"或"TTL"。当生存时间耗尽时,键会自动销毁。 关于键过期的几个重要说明:

  • 可以使用秒或毫秒精度来设置。
  • 但过期时间的分辨率始终为 1 毫秒。
  • 过期信息会被复制并持久化到磁盘上,当你的 Redis 服务器保持停止状态时,时间实际上也在流逝(这意味着 Redis 保存了键将要过期的日期)。

使用 EXPIRE 命令设置键的过期时间:

bash 复制代码
> set key some-value
OK
> expire key 5
(integer) 1
> get key (立即执行)
"some-value"
> get key (一段时间后)
(nil)

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

bash 复制代码
> set key 100 ex 10
OK
> ttl key
(integer) 9

上面的例子设置了一个值为字符串 100 的键,过期时间为十秒。稍后调用 TTL 命令以检查键的剩余生存时间。
Redis 服务器 Client Redis 服务器 Client 设置键与 TTL 生存期内 5秒后 TTL 到期 SET key value EXPIRE key 5秒 GET key 返回 value GET key (nil) 键已自动删除

遍历键空间

SCAN

要以高效的方式增量迭代 Redis 数据库中的键,可以使用 SCAN 命令。

由于 SCAN 允许增量迭代,每次调用只返回少量元素,因此可以在生产环境中使用,而不会有 KEYSSMEMBERS 等命令的缺点------当针对大量键或元素集合调用时,这些命令可能会阻塞服务器很长时间(甚至几秒钟)。

然而,虽然像 SMEMBERS 这样的阻塞命令能够提供给定时刻集合中包含的所有元素。但由于我们在迭代过程中增量迭代的集合可能会发生变化,SCAN 系列命令只对返回的元素提供有限的保证。

KEYS

遍历键空间的另一种方法是使用 KEYS 命令,但这种方法应谨慎使用,因为 KEYS 会阻塞 Redis 服务器,直到返回所有键。

警告 :将 KEYS 视为只能在生产环境中极其谨慎地使用的命令。

当对大型数据库执行 KEYS 时,它可能会破坏性能。该命令旨在用于调试和特殊操作,例如更改键空间布局。不要在常规应用程序代码中使用 KEYS。如果你正在寻找一种方法来查找键空间子集中的键,请考虑使用 SCAN 或集合。

支持的全局风格模式:

  • h?llo 匹配 hellohallohxllo
  • h*llo 匹配 hlloheeeello
  • h[ae]llo 匹配 hellohallo,但不匹配 hillo
  • h[^e]llo 匹配 hallohbllo... 但不匹配 hello
  • h[a-b]llo 匹配 hallohbllo

如果你想按字面意思匹配特殊字符,请使用 \ 进行转义。
000 000 000 000 000 000 000 执行搜索并返回全部结果 服务器在此期间无法处理其他请求 返回第一批结果 (Cursor 0) 服务器可以处理其他请求 返回第二批结果 (Cursor 1) 服务器可以处理其他请求 返回第三批结果 (Cursor 2) KEYS (阻塞) SCAN (非阻塞) 遍历命令对比:KEYS vs SCAN

批量执行命令

虽然你可以一条接一条地发送 Redis 命令,但为了提高效率,我们通常会把一组相关的命令打包在一起批量发送。

  • 流水线:流水线允许你将多个命令作为一次通信发送给服务器,并一次性接收所有响应,这样做的好处是极大地减少了网络往返的时间开销。
  • 事务:如果你需要确保这组命令要么全部完成,要么在执行过程中不被其他客户端修改相同的数据,你就应该使用这种方式。

Redis 服务端 客户端 Redis 服务端 客户端 普通模式: 1对1 (较慢) 流水线模式: 批量发送 (高效) 打包发送,减少 RTT 事务模式: 原子执行 (隔离) 命令 1 响应 1 命令 2 响应 2 命令 1 命令 2 命令 3 响应 1 响应 2 响应 3 MULTI (开始事务) 命令 A (入队) 命令 B (入队) EXEC (执行) 响应 A, 响应 B

流水线

Redis 流水线是一种通过一次性发出多个命令而不等待每个单独命令的响应来提高性能的技术。大多数 Redis 客户端都支持流水线。

请求/响应协议与往返时间(RTT)

Redis 是一个使用客户端-服务器模型以及所谓的请求/响应协议的 TCP 服务器。 这意味着通常一个请求是通过以下步骤完成的:

  1. 客户端向服务器发送一个查询,并通常以阻塞的方式从套接字中读取服务器的响应。
  2. 服务器处理该命令,并将响应发送回客户端。

因此,例如四个命令的序列是这样的:

text 复制代码
客户端: INCR X
服务器: 1
客户端: INCR X
服务器: 2
客户端: INCR X
服务器: 3
客户端: INCR X
服务器: 4

客户端和服务器通过网络链路连接。这种链路可能非常快(如回环接口),也可能非常慢(通过互联网建立的、两个主机之间有许多跳的连接)。无论网络延迟是多少,数据包从客户端传输到服务器,再从服务器携带回复传输回客户端都需要时间。

这个时间被称为 RTT(往返时间)。很容易看出,当一个客户端需要连续执行许多请求(例如向同一个列表添加许多元素,或者用许多键填充数据库)时,这会如何影响性能。例如,如果 RTT 时间是 250 毫秒(在互联网连接非常慢的情况下),即使服务器每秒能够处理 10 万个请求,我们每秒最多也只能处理四个请求。

如果使用的接口是回环接口(本机),RTT 会短得多,通常是亚毫秒级的,但如果你需要连续执行许多写入操作,这也会累积成大量的时间。
Redis 服务器 客户端 Redis 服务器 客户端 普通模式 (无流水线) ⏳ RTT 延迟 (等待响应) ⏳ RTT 延迟 (等待响应) ⏳ RTT 延迟 (等待响应) 发送命令 1 返回结果 1 发送命令 2 返回结果 2 发送命令 3 返回结果 3

使用流水线后,我们第一个例子的操作顺序将变为:
Redis 服务器 客户端 Redis 服务器 客户端 流水线模式 一次性打包发送 无需等待中间响应 服务器处理并排队响应 一次性接收所有结果 INCR X INCR X INCR X INCR X 1 2 3 4

这一次,我们不需要为每次调用支付 RTT 的成本,这 3 个命令只需要支付一次。

重要提示:当客户端使用流水线发送命令时,服务器将被迫使用内存来排队存储回复。因此,如果你需要使用流水线发送大量命令,最好将它们分批发送,每批包含合理数量的命令(例如 1 万个命令),读取回复,然后再发送另外 1 万个命令,依此类推。速度几乎是一样的,但使用的额外内存最多仅为这 1 万个命令的回复队列所需的量。

不仅仅是 RTT 的问题

流水线不仅仅是一种减少与往返时间相关的延迟成本的方法,它实际上极大地提高了你在给定的 Redis 服务器中每秒可以执行的操作数量。这是因为如果不使用流水线,从访问数据结构和生成回复的角度来看,服务每个命令的成本非常低,但从进行套接字 I/O 的角度来看,成本非常高。这涉及到调用 read()write() 系统调用,这意味着要从用户态切换到内核态。上下文切换是一个巨大的速度惩罚。

当使用流水线时,通常通过单个 read() 系统调用读取许多命令,并通过单个 write() 系统调用传递多个回复。因此,每秒执行的总查询数量最初随着流水线的变长几乎呈线性增长,最终达到不使用流水线时获得的基线的 10 倍,
流水线大小 (Pipeline Size) 与 每秒操作数 (Ops/sec) 的关系 1 10 50 100 500 1000 120000 110000 100000 90000 80000 70000 60000 50000 40000 30000 20000 10000 0 Operations per Second

事务

Redis 事务允许在一个单独的步骤中执行一组命令,它们的核心围绕着 MULTIEXECDISCARDWATCH 命令构建。Redis 事务提供两个重要的保证:

  1. 事务中的所有命令都会被序列化并按顺序执行。在 Redis 事务执行期间,另一个客户端发出的请求绝不会被打断插入。这保证了命令是作为一个单独的隔离操作执行的。
  2. EXEC 命令触发事务中所有命令的执行 。因此,如果客户端在调用 EXEC 命令之前的上下文中失去了与服务器的连接,则不会执行任何操作;相反,如果调用了 EXEC 命令,则执行所有操作。当使用仅追加文件(AOF)时,Redis 确保使用单个 write(2) 系统调用将事务写入磁盘。但是,如果 Redis 服务器崩溃或被系统管理员以某种强硬方式杀死,则可能只注册了部分数量的操作。Redis 会在重启时检测到这种情况,并报错退出。可以使用 redis-check-aof 工具修复仅追加文件,该文件将删除部分事务,以便服务器可以再次启动。

基本用法

通过 MULTI 命令进入 Redis 事务。该命令总是回复 OK。此时,用户可以发出多个命令。Redis 不会立即执行这些命令,而是将它们放入队列。一旦调用了 EXEC,所有命令才会被执行。 相反,调用 DISCARD 将刷新事务队列并退出事务。 以下示例原子性地递增键 foobar

bash 复制代码
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

从上面的会话中可以清楚地看出,EXEC 返回一个回复数组,其中每个元素是事务中单个命令的回复,顺序与命令发出的顺序相同。

当 Redis 连接处于 MULTI 请求的上下文中时,所有命令都将回复字符串 QUEUED(从 Redis 协议的角度来看作为状态回复发送)。排队的命令只是安排在调用 EXEC 时执行。
常规状态
发送 MULTI
发送命令 (如 SET, GET)
发送 EXEC
发送 DISCARD
返回所有结果
恢复常规状态
Normal
Queuing
命令被放入队列

回复 "QUEUED"
Executing
按顺序执行所有命令
Discarded
清空队列

取消事务

事务中的错误

在事务期间,可能会遇到两种类型的命令错误:

  1. 命令可能无法入队 ,因此可能在调用 EXEC 之前发生错误。例如,命令可能在语法上错误(参数数量错误、命令名称错误等),或者可能存在某些关键条件,例如内存不足(如果服务器配置了使用 maxmemory 指令限制内存)。
  2. 命令可能在调用 EXEC 之后失败,例如因为我们对具有错误值的键执行了操作(比如对字符串值调用列表操作)。

服务器会在命令累积期间检测到错误。然后它将拒绝执行事务,在 EXEC 期间返回错误,并丢弃该事务。 相反,在 EXEC 之后发生的错误没有特殊处理:即使某些命令在事务期间失败,所有其他命令也将被执行

关于回滚?

Redis 不支持事务回滚,因为支持回滚会对 Redis 的简单性和性能产生重大影响。
是 (如语法错误)

是 (如类型错误)

开始事务 MULTI
入队命令
编译/入队时

有错误?
EXEC 返回错误

事务整体取消
EXEC 执行命令
运行时

有错误?
继续执行剩余命令

注意: Redis 不回滚
全部成功

丢弃命令队列

可以使用 DISCARD 来中止事务。在这种情况下,不会执行任何命令,并且连接的状态将恢复为正常。

bash 复制代码
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"

使用检查并设置(CAS)实现乐观锁

WATCH 用于为 Redis 事务提供检查并设置(CAS)行为。 被 WATCH 的键会受到监控,以检测针对它们的更改。如果在 EXEC 命令之前修改了至少一个被监视的键,则整个事务中止,并且 EXEC 返回 Null 回复以通知事务失败。

例如,假设我们需要原子性地将键的值增加 1(假设 Redis 没有 INCR)。 第一次尝试可能如下:

text 复制代码
val = GET mykey
val = val + 1
SET mykey $val

只有在给定时间有一个客户端执行该操作时,这才能可靠地工作。如果多个客户端尝试大约在同一时间递增键,则会出现竞态条件。例如,客户端 A 和 B 将读取旧值,例如 10。该值将被两个客户端增加到 11,并最终 SET 为键的值。所以最终值将是 11 而不是 12。

得益于 WATCH,我们能够很好地对问题进行建模:

text 复制代码
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

使用上面的代码,如果存在竞态条件,并且另一个客户端在我们调用 WATCH 和调用 EXEC 之间修改了 val 的结果,则事务将失败。

我们只需要重复操作,希望这次我们不会遇到新的竞态。这种形式的锁定称为乐观锁。在许多用例中,多个客户端将访问不同的键,因此冲突不太可能发生 ------ 通常不需要重复该操作。
Redis 服务器 客户端 B (竞争者) 客户端 A (使用 WATCH) Redis 服务器 客户端 B (竞争者) 客户端 A (使用 WATCH) 乐观锁机制演示 并发修改 Redis 标记 mykey 已被修改 EXEC 返回 null 因为 mykey 已被改动 事务中止 WATCH mykey GET mykey (获得 10) SET mykey 20 OK MULTI SET mykey 11 EXEC (nil) 失败

WATCH 详解

那么 WATCH 到底是关于什么的?它是一个使 EXEC 有条件的命令:我们要求 Redis 仅在没有 WATCH 的键被修改的情况下才执行事务。这包括客户端所做的修改(如写入命令)和 Redis 本身所做的修改(如过期或驱逐)。如果在键被 WATCH 到收到 EXEC 之间修改了键,则整个事务将中止。

事务中的命令不会触发 WATCH 条件,因为它们只是排队,直到发送 EXEC

可以多次调用 WATCH。简单地,所有 WATCH 调用都将产生从调用开始直到调用 EXEC 时监视更改的效果。您也可以向单个 WATCH 调用发送任意数量的键。

当调用 EXEC 时,无论事务是否中止,所有键都是 UNWATCH(取消监视)的。此外,当客户端连接关闭时,所有内容都会被 UNWATCH

也可以使用 UNWATCH 命令(不带参数)来刷新所有监视的键。有时这很有用,因为我们乐观地锁定了一些键,因为我们可能需要执行事务来更改这些键,但是在读取键的当前内容后我们不想继续。发生这种情况时,我们只需调用 UNWATCH,以便该连接已经可以自由地用于新事务。

相关推荐
qq_401700412 小时前
Qt 事件处理机制
java·数据库·qt
Elastic 中国社区官方博客2 小时前
使用 jina-embeddings-v3 和 Elasticsearch 进行多语言搜索
大数据·数据库·人工智能·elasticsearch·搜索引擎·全文检索·jina
深海小黄鱼3 小时前
mysql 导入csv文件太慢, Error Code: 1290.
数据库·mysql
小宇的天下3 小时前
Calibre Connectivity Extraction(21-1)
数据库·oracle
rannn_1113 小时前
【Java项目】中北大学Java+数据库课设|校园食堂智能推荐与反馈系统
java·数据库·后端·课程设计·中北大学
DBA小马哥3 小时前
从Oracle到信创数据库:一场技术迁移的探索之旅
数据库·oracle
2501_944521003 小时前
rn_for_openharmony商城项目app实战-语言设置实现
javascript·数据库·react native·react.js·harmonyos
飞Link3 小时前
【Sqoop】Sqoop 使用教程:从原理到实战的完整指南
数据库·hadoop·sqoop
此生只爱蛋3 小时前
【Redis】事务
数据库·redis·缓存