进阶 docs.memcached.org 2026-10-13 18:51:48 · 21 阅读

第27章 Memcached Meta 文本协议详解与命令指南

协议 Meta Text Protocol Meta Text Protocol

注意:这些命令是新增的。如果在 API 或文档的使用过程中遇到任何问题,请随时告知!Meta 协议已不再被视为实验性协议,欢迎试用!注意:二进制协议已弃用。Meta 协议与文本协议完全兼容,包含二进制协议的所有功能,并提供了多项增强特性。

Memcached 增加了额外的命令,用于减少网络传输字节数、降低复杂查询(如防止羊群效应技术)所需的网络往返次数、公开此前隐藏的数据项信息,并添加了许多新功能。这些命令是在现有文本协议基础上的扩展,能够执行此前二进制协议所能完成的所有操作。

Meta 命令的完整说明请参阅文本协议文档: 文本协议

本 wiki 作为 protocol.txt 的配套文档,提供新命令的使用场景和示例。

命令基础

命令和响应以两位字符代码开头,随后是一组标志,以及可能的数据值。标志可以附带令牌数据。

set 请求: ` ms foo 2 T90 F1\r\n` ` hi\r\n` 响应: ` HD\r\n`

get 请求: ` mg foo t f v\r\n` 响应: ` VA 2 t78 f1\r\n` ` hi\r\n`

delete 请求: ` md foo\r\n` 响应: ` HD\r\n`

命令由两位字符组成,随后是键、标志,以及标志请求的令牌。 响应是两位字符代码,加上额外添加的标志(针对 mg)以及请求的令牌。标志响应顺序与客户端请求中的顺序一致。

详细请参阅文本协议文档。

替换 GET/GETS/TOUCH/GAT/GATS

标准 GET: `mg foo f v\r\n` -- 请求客户端标志和值 `VA 2 f30\r\n` -- 获取长度、客户端标志、值 `hi\r\n` … 将返回客户端标志和值。添加 k 还可获取键。

GETS(带 CAS 的 get): `mg foo f c v\r\n` `VA 2 f30 c3\r\n` -- 还获取 CAS 值 `hi\r\n`

TOUCH(仅更新 TTL,无响应数据): `mg foo T30\r\n` -- 更新 TTL 为 30 秒。 `HD\r\n` -- 未请求标志或值,返回 HD 状态码

GAT(get and touch): `mg foo f v T90` … 获取客户端标志和值,并将 TTL 更新为 90 秒。

GATS(带 CAS 的 get and touch): `mg foo f c v T100` … 与上述相同,但还获取 CAS 值。

新 MetaData 标志

l 标志将显示自数据项最后一次访问以来经过的秒数。 h 标志将根据数据项自存储以来是否被获取过返回 0 或 1。 t 标志将返回数据项过期前剩余的秒数(无限期为 -1) 其他标志将在 protocol.txt 中说明。

二进制编码键

使用 Meta 协议可以存储和检索非 ASCII 键。如果键非常长而数据非常短,这可以节省服务器上的内存。你还可以通过这种方式存储 UTF8 字符串。

例如,你的键可能长这样:“string-12345678910-9999313149” 或 “reallylongidentifierstringbecausewehavealotofthese-[ID number]。为了节省内存,可以将字符串标识符编码为一个较小的数字,而 ID 可以是 32 位或 64 位整数。

为了存储/检索编码后的键,它们必须进行 BASE64 编码。这会在网络部分增加一些开销,但在现代硬件上编码和解码通常速度极快。 二进制键 set 请求:(“tesuto” 即日文假名) ` ms 44OG44K544OI 2 b\r\n` ` hi\r\n`

` mg 44OG44K544OI b v k\r\n` ` VA 2 k44OG44K544OI b\r\n` ` hi\r\n`

原子羊群效应处理

我们可以使用 CAS 值和新的标志,以极少的往返次数实现羊群效应(dogpiling)保护: 请求: ` mg foo f c v N30\r\n` 响应: ` VA 0 f0 c2 W\r\n` `\r\n`

N 标志指示 memcached 在未命中时自动创建数据项,TTL 在此例中为 30 秒。 在返回的标志中,新增了一个 W 标志。这表示客户端接收到了未命中响应,并获得了“胜出”重新缓存该数据项的权利。客户端随后可以直接更新获取或计算后的值,或者使用检索到的 CAS 值进行设置。

如果另一个客户端在此期间请求相同的数据项,它会看到略有不同的响应: 请求: ` mg foo f c v N30\r\n` 响应: ` VA 0 f0 c2 Z\r\n` `\r\n`

Z 标志表明客户端 W 标志已经发送过,这是一个特殊的非数据项。客户端可以选择重试、等待或尝试其他操作。

过去,客户端通过在不命中后使用 add 命令进行竞争,或使用特殊响应值等方式来实现这一点。现在,这些已被整合到正常的请求/响应流程中,并具备额外功能。

提前重新缓存

‘R’ 标志可用于在数据项接近过期时进行反羊群效应的提前重新缓存。这有助于减少频繁访问数据项的未命中次数。不频繁访问的数据项可以保留直至过期。 请求: ` mg foo v t R30\r\n` 响应: ` VA 2 t29 W\r\n` `hi\r\n`

在此示例中,R 标志附带一个令牌‘30’,这意味着如果数据项的剩余 TTL 少于 30 秒,则尝试刷新该数据项。 在响应中,我们看到额外的‘W’(win)标志被返回,指示该客户端应重新缓存数据项。后续客户端将看到‘Z’标志,表示请求已经发出。 我们还看到剩余 TTL,通过‘t’标志,确认 TTL 低于 30 秒。 也可以通过‘c’标志请求 CAS 值,这允许仅当对象自接收 win 令牌以来没有发生变化时才执行重新缓存。 get 请求: ` mg foo v c R30\r\n` 响应: ` VA 0 c999\r\n` `\r\n`

set 请求: ` ms foo 3 C999` ` new\r\n` 响应: ` HD\r\n`

提供过期数据

类似于 HTTP 中的 Stale-While-Revalidate 或 Stale-While-Error,Meta 命令可以有意提供过期但仍可用的数据。 在某些情况下,你希望主动将内存中的数据项标记为过期。然后,你可以让获取过期值的第一个客户端也处理重新验证,或者启动异步重新缓存,同时通知客户端数据可能不是最新的。

我们使用 Meta 删除命令将数据项标记为过期。 请求: ` md foo I T30\r\n` 响应: ` HD\r\n` 我们还可以选择更改 TTL。在这种情况下,过期数据最多提供 30 秒。将数据项标记为过期也会改变其 CAS ID。 请求: ` mg foo t c v` 响应: ` VA 4 t29 c777 W X\r\n` ` data\r\n` 下一个 metaget 获取 ‘W’ 标志,表明其拥有独占重新缓存数据项的权利。它还获取 ‘X’ 标志,表明数据项已过期。如何处理取决于应用程序;它可以使用数据原样、调整数据、警告用户,或稍后安静地重新获取。 如果在上面的数据项重新缓存之前收到另一个 metadelete,CAS 将变为 778(或更高),后续的 metaset 调用将失败。 然而,通过 metaset 的 ‘I’ 标志,仍可以更新数据项但保持其过期状态。 请求: ` ms foo S3 T360 C777 I\r\n` ` new\r\n` 响应: ` HD\r\n` 下一个 metaget 将继续把该数据项视为过期,保持其先前的 TTL 和先前的 CAS: 请求: ` mg foo t c v\r\n` 响应: ` VA 3 t25 c778 X\r\n` ` new\r\n` CAS 值必须小于真实的 CAS 值,此设置才能生效。一旦收到与 CAS 值匹配的 set,所有数据都将被覆盖,TTL 会被正确更新。

管道传输 Quiet 模式与 Opaque 或 Key

使用 Meta 命令的管道传输通过 ‘q’、‘O’ 和 ‘k’ 标志的组合实现。这些标志适用于所有 metaget、metaset 和 metadelete 命令。 在常规文本协议中,请求的键会反射回给客户端。这使得在请求多个键时能够区分响应。 `get bar foooooooooooooooooooooooooooooooo baz` `VALUE foooooooooooooooooooooooooooooooo 0 2` `hi` `END` 在上面,只有 foo+ 存在。其余值没有未命中的指示行,只有在获取所有键之后的 END 令牌。 即使是获取单个键也是如此: `get foooooooooooooooooooooooooooooooo` `VALUE foooooooooooooooooooooooooooooooo 0 2` `hi` `END` Metagets 没有多键接口。一次请求多个键需要管道传输命令: `mg foo v\r\n` `mg bar v\r\n` `mg baz v\r\n` Metaget 默认也不返回键。客户端应查看响应代码来计数响应。 使用 ‘q’ 标志可以优化多个键的获取,这会隐藏未命中时的 “EN” 代码。这存在一个问题:你无法再区分响应以将数据匹配到请求的键。 优化管道传输请求有两个选项: 请求: ` mg foo t c v q k\r\n` 响应: ` VA 2 s2 t-1 c2 kfoo\r\n` ` hi\r\n` ‘k’ 标志将在响应中作为令牌添加键。这对 mg、ms 和 md 都有效。 如果你的键非常长,尤其是存储的数据非常小(甚至可能是 0 字节!),这仍然不是理想的。返回 200 字节的键和 8 字节的数据会在网络上产生很多额外字节。 还有一个选项,‘O’(opaque)标志。附带此标志的令牌将原样反射回响应中。截至本文撰写时,不透明令牌长度最多为 32 字节。它们可以是字母数字,但数字可能是常见用例。 请求: ` mg foo v q Oopaque\r\n` 响应: ` VA 2 Oopaque\r\n` ` hi\r\n` 在此示例中,字符串“opaque”作为令牌使用,以便在响应中更突出。这可以是任何(短)ASCII 字符串。一种简单方法是统计未完成请求的数量,使用数字并在接收响应后重置。这保持了数字的简短,如果需要还可以从十六进制编码中获益。 最后,如果所有请求都未命中,仍然无法判断所有键何时被处理完。有两个选项: 发送没有 ‘q’ 标志的最终 mg,这将添加 EN 响应代码。 使用 mn meta 无操作命令。此命令仅响应一个纯粹的 MN 代码。 请求: ` mn\r\n` 响应: ` MN\r\n` 将其放在管道末尾: `mg [etc]\r\n` `mg [etc]\r\n` `mg [etc]\r\n` `mn\r\n`

Quiet 模式语义

‘q’ 标志在管道传输部分有简要描述,但它还做什么? 通常,‘q’ 标志会隐藏“正常”响应。如果你希望管道传输多个 set 但不想看到所有 “ST” 代码响应,请传递 ‘q’ 标志。如果 set 的结果代码不是 “ST”(例如 CAS 失败的 “EX”),响应仍会返回。 任何语法错误也会导致响应(CLIENT_ERROR)。

带 CAS 覆盖的数据一致性

数据版本或基于时间的一致性

从版本 1.6.27 开始,Meta 协议支持在变更操作期间直接提供 CAS 值。默认情况下,数据项的 CAS 值(或 CAS ID)由 memcached 使用全局递增计数器直接生成,但我们现在可以用 E 标志覆盖此。 例如,如果你缓存的数据有“版本 ID”或“行版本”,我们可以提供它: `ms foo 2 E73 -- 直接提供 CAS 值` `hi` `HD` `mg foo c v -- 之后可以检索` `VA 2 c73` `hi` 我们直接设置了该值的 CAS ID 为 73。现在我们可以使用标准的 CAS 操作来更新数据。例如,尝试使用旧版本更新数据现在将失败: `ms foo 2 C72 E73` `hi` `EX` 上面可能是竞争条件的结果:两个进程试图同时将数据从版本 72 移动到 73。由于底层版本已经是 73,第二个命令将失败。 注意,任何可以生成新 CAS ID 的命令都接受 E 标志。例如:带失效的删除,ma 用于递增/递减,等等。

时间和类型

任何能容纳在 8 字节递增数字中的都可以用作 CAS ID:版本、时钟、HLC 等。只要下一个数字高于前一个数字。

跨多个池的数据一致性

如果你试图保持多个 memcached 服务器池的同步,我们可以使用 CAS 覆盖来帮助改善一致性结果。请注意,此系统不是严格的。

领导者与跟随者池

假设你有池 A、B、C,其中一个池被指定为“领导者”,我们可以提供通用的一致性,也可以修复: -- 针对池 A: `mg foo c v\r\n` `VA 2 C50\r\n` `hi\r\n` -- 我们获取了一个值。我们有一个新的行版本(或时间戳): `ms foo 2 C50 E51\r\n` `ih\r\n` `HD\r\n` -- 成功。现在,针对池 B 和 C,我们发出“盲 set”: -- 池 B: `ms foo 2 E51\r\n` `ih\r\n` `HD\r\n` -- 池 C: `ms foo 2 E51\r\n` `ih\r\n` `HD\r\n` 如果一切顺利,所有副本都将具有与池 A 中数据相同的 CAS ID。这再次,不是严格的,但可以帮助验证数据是否一致。如果池 A 中的数据丢失,你可以决定通过多数或最高 ID 从 B/C 修复数据。或者在 A 修复前不允许数据变更,但在此期间可以从 B/C 读取数据。

完全交叉一致性

保证跨多个池的一致性很困难,甚至不可能。你可以尝试这样: -- 针对池 A: `mg foo c v\r\n` -- 如果需要值。否则丢弃 v。 -- 针对池 B/C: `mg foo c\r\n` -- 不一定需要值 -- 在所有三个主机上使用相同的 set 命令: `ms foo 2 Cnnnn E90\r\n` `hi\r\n` 如果这在任何主机上失败,重新执行整个过程。如果一个值被频繁更新,这可能是有问题的或永久阻塞。 你可以通过选择“领导者”池并发出失效来补充 stale/win 标志: `md foo I E74\r\n` -- 如果存在则失效该键,为新版做准备 `HD\r\n` `mg foo c v\r\n` `VA 2 c75 X W\r\n` -- 数据已过期 (X) 且我们原子胜出 (W) `hi\r\n` 现在,此客户端在所有池中拥有独占更新此值的权利。如果对池 B/C 的更新乱序,如果更新是异步的,最高的 CAS 值最终应该会成功: 如果池 B 从版本 70 开始,然后更新为 75,如上所述 后续更新到 73 将失败(CAS 太旧) 如果池 C 从版本 70 开始,更新到 73,然后更新到 75 最终结果将是正确的版本 如果客户端在响应给用户之前等待多数(本例中为 2/3)或所有(3/3)主机响应,数据应该是相同的。 此方法失败的问题应该相对罕见,例如: 池 A 是我们的领导者,我们获取 win 标志 (W) 并成功更新。 我们更新池 B、C,但在此期间池 A 失败了。 取决于如何选举新领导者以及更新需要多长时间,我们可能会得到不一致的数据。 实际上,主机或池选举很慢,且跨流更新相对较快,所以这种情况应该不常见。

概率性热缓存

Meta 命令使一种新的技术成为可能:概率性客户端热键缓存。这意味着一种无需协调的方法,用于填充本地缓存,以避免对非常频繁访问的数据项向 memcached 发出请求。 使用新的 h 和 l 标志,我们可以查看数据项是否之前被命中过,以及自最后一次命中以来过了多少秒。让我们将它们组合起来: 请求: ` mg foo v h l\r\n` 响应: ` VA 4 h1 l5\r\n` ` data\r\n` 分解标志: s 是数据大小,响应获取 4。 v 意味着返回值,响应块中获取 “data\r\n”。 h 意味着根据数据项存储后是否被请求过返回 0 或 1。 l 意味着自最后一次访问以来的秒数,这里显示为 5 秒。 我们可以加权这些值来创建概率性缓存: `if (h == 1 && l < 5 && random(1000) == 0) { add_to_local_cache(it); }` 这里的随机因素是一个权重,可以是其他因素(数据库/网络/系统负载/本地缓存命中率等)。流量突然增加很多的键会缓慢地过滤进来,流量最大的键会非常快

评论 (0)