命令键规范
什么是命令键规范以及如何在客户端中使用它们
Redis 中的许多命令都接受键名作为输入参数。COMMAND
(以及 COMMAND INFO
)回复的第 9 个元素是一个数组,其中包含该命令的键规范。
键规范描述了从给定命令的参数中提取一个或多个键名的规则。与 Redis 7.0 之前使用的第一个键、最后一个键和步长方案相比,键规范提供了一种健壮且灵活的机制。在引入这些规范之前,Redis 客户端没有简单的编程方法来提取所有命令的键名。
对集群感知的 Redis 客户端必须在处理诸如 EVAL
和 ZUNIONSTORE
这样依赖于 numkeys 参数的命令,或者 SORT
及其许多子句时,硬编码键提取逻辑。或者,可以使用 COMMAND GETKEYS
命令实现类似的提取效果,但延迟较高。
Redis 客户端不强制要求支持键规范。它可以继续使用遗留的第一个键、最后一个键和步长方案以及保持不变的movablekeys 标志。
然而,支持键规范的 Redis 客户端可以整合其大部分键提取逻辑。即使客户端遇到不熟悉的键规范类型,它始终可以回退到 COMMAND GETKEYS
命令。
话虽如此,大多数对集群感知的客户端只需一个键名即可执行正确的命令路由,因此即使一个命令具有一个不熟悉的规范,其其他规范仍然可能被客户端使用。
键规范是包含以下键的映射
- begin_search:: 键提取的起始索引。
- find_keys: 相对于 BS 识别键的规则。
- notes: 关于此键规范的说明,如果存在。
- flags: 指示数据访问类型。
begin_search
规范的 begin_search 值告知客户端提取的开始位置。该值是一个映射。begin_search
有三种类型
- index: 键名参数从一个常量索引开始。
- keyword: 键名出现在特定关键字(token)之后。
- unknown: 未知类型的规范 - 有关详细信息,请参阅不完整标志部分。
index
begin_search
的 index 类型表示输入键出现在一个常量索引处。它是 spec 键下的一个映射,只有一个键
- index: 客户端应开始提取键名的 0-基索引。
keyword
begin_search
的 keyword 类型意味着一个字面 token 出现在键名参数之前。它是 spec 下的一个包含两个键的映射
- keyword: 标记键名参数开始位置的关键字(token)。
- startfrom: 客户端应开始搜索的参数数组索引。这可以是一个负值,表示搜索应从参数数组的末尾开始,按逆序进行。例如,-2 的意思是反向搜索倒数第二个参数。
keyword 搜索类型的更多示例如下
SET
命令有一个类型为 index 的begin_search
规范,其值为 1。XREAD
命令有一个类型为 keyword 的begin_search
规范,其值分别为 "STREAMS" 和 1 作为 keyword 和 startfrom。MIGRATE
命令有一个类型为 keyword 的 start_search 规范,其值为 "KEYS" 和 -2。
find_keys
键规范的 find_keys
值告诉客户端如何继续搜索键名。find_keys
有三种可能的类型
- range: 键在特定索引处或相对于最后一个参数停止。
- keynum: 附加参数指定输入键的数量。
- unknown: 未知类型的规范 - 有关详细信息,请参阅不完整标志部分。
range
find_keys
的 range 类型是 spec 键下的一个包含三个键的映射
- lastkey: 相对于
begin_search
的最后一个键参数的索引。这可以是一个负值,此时它不是相对的。例如,-1 表示一直提取键直到最后一个参数,-2 直到倒数第二个参数,以此类推。 - keystep: 找到键后,应跳过的参数数量,以找到下一个键。
- limit: 如果 lastkey 的值为 -1,我们使用 limit 按因子停止搜索。0 和 1 表示没有限制。2 表示剩余参数的一半,3 表示三分之一,以此类推。
keynum
find_keys
的 keynum 类型是 spec 键下的一个包含三个键的映射
- keynumidx: 包含键数量的参数索引,相对于
begin_search
。 - firstkey: 第一个键的索引,相对于
begin_search
。这通常是 keynumidx 之后的下一个参数,在这种情况下,其值大一。 - keystep: 找到键后,应跳过的参数数量,以找到下一个键。
示例
SET
命令有一个 range,值为 0、1 和 0。MSET
命令有一个 range,值为 -1、2 和 0。XREAD
命令有一个 range,值为 -1、1 和 2。ZUNION
命令有一个类型为 index 的 start_search,值为 1,以及一个类型为 keynum 的find_keys
,值为 0、1 和 1。
注意:由于模块编写者可以创建任何东西,这不是一个完美的解决方案。但是,此机制应允许提取绝大多数命令的键名参数。
notes
关于不明显的键规范考量的说明,如果适用。
flags
键规范可以有附加标志,提供关于键的更多细节。这些标志分为以下三组,如下所述。
访问类型标志
以下标志声明了命令访问键的值或其元数据的类型。键的元数据包括 LRU/LFU 计数器、类型和基数。这些标志与发送回客户端的回复无关。
每个键规范都精确包含以下标志之一
- RW: 读写标志。命令修改存储在键值或其元数据中的数据。此标志标记了不是明确的删除、覆盖或只读的所有操作。
- RO: 只读标志。命令只读取键的值(尽管不一定返回它)。
- OW: 覆盖标志。命令覆盖存储在键值中的数据。
- RM: 移除标志。命令删除键。
逻辑操作标志
以下标志声明了对存储为键值及其 TTL(如果存在)的数据执行的操作类型,而不是元数据。这些标志描述了命令根据输入参数执行的数据逻辑操作。这些标志与修改或返回元数据(如键的类型、基数或存在性)无关。
每个键规范可能包含以下标志
- access: 访问标志。此标志表示命令返回、复制或以某种方式使用存储在键中的用户数据。
此外,该规范可能精确包含以下之一
- update: 更新标志。命令更新存储在键值中的数据。新值可能取决于旧值。此标志标记了不是明确的插入或删除的所有操作。
- insert: 插入标志。命令仅向值添加数据;现有数据不被修改或删除。
- delete: 删除标志。命令明确删除存储在键中的数据。
杂项标志
键规范可能包含以下标志
- not_key: 此标志表示指定的参数不是键。在计算 Redis 集群命令应分配到哪个槽时,此参数被视为与键相同。对于所有其他目的,此参数不应被视为键。
- incomplete: 此标志在下文解释。
- variable_flags: 此标志在下文解释。
incomplete
有些命令在指定其键时采用特殊方法,这使得提取变得困难。例如,考虑调用 MIGRATE
命令,其中包含字面字符串 "KEYS" 作为其 AUTH 子句的参数时会发生什么。我们的键规范会出错,提取将从错误的索引开始。
因此,我们认识到键规范是不完整的,可能无法提取所有键。但是,我们保证即使是不完整的规范也绝不会产生错误的键名,前提是命令在语法上是正确的。
对于 MIGRATE
命令,搜索从末尾开始(startfrom 的值为 -1)。如果当我们遇到一个名为 "KEYS" 的键时,我们将只提取其后面的键名参数子集。这就是为什么 MIGRATE
命令在其键规范中带有 incomplete 标志的原因。
不完整性的另一个例子是 SORT
命令。在这里,begin_search
和 find_keys
的类型是 unknown。客户端应该回退到调用 COMMAND GETKEYS
命令来从参数中提取键名,而不是原生实现它。困难在于,例如,字符串 "STORE" 既是关键字(token),也是 SORT
的有效字面量参数。
注意:唯一具有 incomplete 键规范的命令是 SORT
和 MIGRATE
。我们预计将来不会添加此类命令。
variable_flags
在某些命令中,同一键名参数的标志可能取决于其他参数。例如,考虑 SET
命令及其可选的 GET 参数。如果没有 GET 参数,SET
是只写操作,但有了它,就变成了读写命令。当此标志存在时,表示键规范标志涵盖了所有可能的选项,但实际生效的标志取决于其他参数。
示例
SET 键规范
1) 1) "flags"
2) 1) RW
2) access
3) update
3) "begin_search"
4) 1) "type"
2) "index"
3) "spec"
4) 1) "index"
2) (integer) 1
5) "find_keys"
6) 1) "type"
2) "range"
3) "spec"
4) 1) "lastkey"
2) (integer) 0
3) "keystep"
4) (integer) 1
5) "limit"
6) (integer) 0
ZUNION 键规范
1) 1) "flags"
2) 1) RO
2) access
3) "begin_search"
4) 1) "type"
2) "index"
3) "spec"
4) 1) "index"
2) (integer) 1
5) "find_keys"
6) 1) "type"
2) "keynum"
3) "spec"
4) 1) "keynumidx"
2) (integer) 0
3) "firstkey"
4) (integer) 1
5) "keystep"
6) (integer) 1