第28章 Memcache 二进制协议解析
协议:Binary Protocol(二进制协议)
简介
Memcache 是一个高性能的 key-value 缓存。它被刻意设计成一个“傻”缓存,只为速度优化。使用 memcache 的应用不应把它当作可靠的数据来源——强烈建议配合一个有可靠性保证的持久化数据库使用——但有了 memcache 中的缓存数据,应用可以快得多。
Memcache 最初是为了让 LiveJournal [LJ] 变得更快而开发的。如今,你喜欢的那些最快的网站背后都有它的身影。
本文档中的约定
本文档中的关键词 “MUST”(必须)、“MUST NOT”(禁止)、“REQUIRED”(必须)、“SHALL”(应)、“SHALL NOT”(不得)、“SHOULD”(应该)、“SHOULD NOT”(不应)、“RECOMMENDED”(推荐)、“MAY”(可以)、“OPTIONAL”(可选)的解释遵循 [BinaryProtocolRevamped#Normative_References] 中的描述。
包结构
包的通用格式:
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0/ HEADER / / / / / / / +---------------+---------------+---------------+---------------+ 24/ COMMAND-SPECIFIC EXTRAS (as needed) / +/ (note length in the extras length header field) / +---------------+---------------+---------------+---------------+ m/ Key (as needed) / +/ (note length in key length header field) / +---------------+---------------+---------------+---------------+ n/ Value (as needed) / +/ (note length is total body length header field, minus / +/ sum of the extras and key length body fields) / +---------------+---------------+---------------+---------------+ Total 24 bytes
请求头
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| Magic | Opcode | Key length | +---------------+---------------+---------------+---------------+ 4| Extras length | Data type | vbucket id | +---------------+---------------+---------------+---------------+ 8| Total body length | +---------------+---------------+---------------+---------------+ 12| Opaque | +---------------+---------------+---------------+---------------+ 16| CAS | | | +---------------+---------------+---------------+---------------+ Total 24 bytes
响应头
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| Magic | Opcode | Key Length | +---------------+---------------+---------------+---------------+ 4| Extras length | Data type | Status | +---------------+---------------+---------------+---------------+ 8| Total body length | +---------------+---------------+---------------+---------------+ 12| Opaque | +---------------+---------------+---------------+---------------+ 16| CAS | | | +---------------+---------------+---------------+---------------+ Total 24 bytes
头部字段说明
- Magic:标识数据包的魔数(见 Magic Byte) - Opcode:命令代码(见 Command Opcodes) - Key length:命令 extras 之后文本 key 的字节长度 - Status:响应状态(非零表示出错)(见 Response Status) - Extras length:命令 extras 的字节长度 - Data type:预留字段,供将来使用(见 Data Types) - vbucket id:本命令所属的虚拟 bucket - Total body length:extras + key + value 的总字节长度 - Opaque:会在响应中原样返回给客户端 - CAS:数据版本检查
已定义的值
Magic Byte
| 字节 | 含义 | | 0x80 | 本协议版本的请求包 | | 0x81 | 本协议版本的响应包 |
Magic byte / 版本号。协议的每个版本都使用不同的请求/响应取值对。这样协议分析工具就能根据数据包的传输方向判断其性质。注意,memcached 实例常常和应用服务器部署在同一台主机上,这样的主机既会发送也会接收 memcache 包。
版本号应当只对应命令字节的含义变化。理想情况下,头部格式不会改变。当预留字节被赋予新含义时,协议版本 / magic byte 的取值应当递增。
建议流量分析工具识别 memcache 包:如果能识别 magic byte,就给出详细的解析结果;否则给出通用的包结构分解。注意,即使无法识别 magic byte 或命令 opcode,key 和 value 的位置也总是可以确定的。
Response Status(响应状态)
这个两字节字段的可能取值:
| 字节 | 含义 | | 0x0000 | 无错误 | | 0x0001 | Key 未找到 | | 0x0002 | Key 已存在 | | 0x0003 | Value 过大 | | 0x0004 | 无效参数 | | 0x0005 | Item 未存储 | | 0x0006 | 对非数值执行 Incr/Decr | | 0x0007 | 该 vbucket 属于另一台服务器 | | 0x0008 | 认证错误 | | 0x0009 | 认证继续 | | 0x0081 | 未知命令 | | 0x0082 | 内存不足 | | 0x0083 | 不支持 | | 0x0084 | 内部错误 | | 0x0085 | 忙碌 | | 0x0086 | 临时故障 |
Command Opcodes(命令操作码)
这个单字节字段的可能取值。各命令的详细信息见 [BinaryProtocolRevamped#Commands]。
| 字节 | 含义 | | 0x00 | Get | | 0x01 | Set | | 0x02 | Add | | 0x03 | Replace | | 0x04 | Delete | | 0x05 | Increment | | 0x06 | Decrement | | 0x07 | Quit | | 0x08 | Flush | | 0x09 | GetQ | | 0x0a | No-op | | 0x0b | Version | | 0x0c | GetK | | 0x0d | GetKQ | | 0x0e | Append | | 0x0f | Prepend | | 0x10 | Stat | | 0x11 | SetQ | | 0x12 | AddQ | | 0x13 | ReplaceQ | | 0x14 | DeleteQ | | 0x15 | IncrementQ | | 0x16 | DecrementQ | | 0x17 | QuitQ | | 0x18 | FlushQ | | 0x19 | AppendQ | | 0x1a | PrependQ | | 0x1b | Verbosity * | | 0x1c | Touch * | | 0x1d | GAT * | | 0x1e | GATQ * | | 0x20 | SASL list mechs | | 0x21 | SASL Auth | | 0x22 | SASL Step | | 0x30 | RGet | | 0x31 | RSet | | 0x32 | RSetQ | | 0x33 | RAppend | | 0x34 | RAppendQ | | 0x35 | RPrepend | | 0x36 | RPrependQ | | 0x37 | RDelete | | 0x38 | RDeleteQ | | 0x39 | RIncr | | 0x3a | RIncrQ | | 0x3b | RDecr | | 0x3c | RDecrQ | | 0x3d | Set VBucket * | | 0x3e | Get VBucket * | | 0x3f | Del VBucket * | | 0x40 | TAP Connect * | | 0x41 | TAP Mutation * | | 0x42 | TAP Delete * | | 0x43 | TAP Flush * | | 0x44 | TAP Opaque * | | 0x45 | TAP VBucket Set * | | 0x46 | TAP Checkpoint Start * | | 0x47 | TAP Checkpoint End * |
带 * 的命令尚未最终确定,这是 1.6 版本的当前提案。
按惯例,所有以 “Q” 结尾的命令都是 Quiet(静默)版本。静默版命令会省略那些被认为“没什么可说的”响应。某个响应是否值得返回取决于具体命令,参考 set 命令(4.2 节)和 set 命令(4.3 节)中包含静默变体的命令示例。
Data Types(数据类型)
这个单字节字段的可能取值:
| 字节 | 含义 | | 0x00 | 原始字节 |
命令
简介
所有通信都由客户端的请求发起,服务器对每个请求返回零个或多个响应包。如果响应包的状态码非零,包体将包含一条文本错误消息;如果状态码为零,包体的布局由命令 opcode 决定。
示例
下图展示了一个包含错误消息的数据包布局。
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| 0x81 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 4| 0x00 | 0x00 | 0x00 | 0x01 | +---------------+---------------+---------------+---------------+ 8| 0x00 | 0x00 | 0x00 | 0x09 | +---------------+---------------+---------------+---------------+ 12| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 16| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 20| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 24| 0x4e ('N') | 0x6f ('o') | 0x74 ('t') | 0x20 (' ') | +---------------+---------------+---------------+---------------+ 28| 0x66 ('f') | 0x6f ('o') | 0x75 ('u') | 0x6e ('n') | +---------------+---------------+---------------+---------------+ 32| 0x64 ('d') | +---------------+ Total 33 bytes (24 byte header, and 9 bytes value)
Field (offset) (value) Magic (0) : 0x81 Opcode (1) : 0x00 Key length (2,3) : 0x0000 Extra length (4) : 0x00 Data type (5) : 0x00 Status (6,7) : 0x0001 Total body (8-11) : 0x00000009 Opaque (12-15): 0x00000000 CAS (16-23): 0x0000000000000000 Extras : None Key : None Value (24-32): The textual string "Not found"
Get、Get Quietly、Get Key、Get Key Quietly
请求: - MUST NOT 有 extras - MUST 有 key - MUST NOT 有 value
响应(找到时): - MUST 有 extras - MAY 有 key - MAY 有 value
get 命令的附加数据:
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| Flags | +---------------+---------------+---------------+---------------+ Total 4 bytes
get 命令获取单个 key。getq 在缓存未命中时不会发送响应。getk 和 getkq 与 get/getq 的区别在于响应包中会带上 key。
客户端应以 n 个流水线请求的方式实现 multi-get(这对减少网络往返依然很重要!):前 n-1 个用 getq/getkq,最后一个用普通的 get/getk。这样既能保证一定收到响应,也能知道服务器何时处理完毕。当然也可以简单粗暴地发送 n 个流水线的 get/getk,但这样可能会收到大量 “NOT_FOUND” 错误码的响应包;或者发送 n 个 getq/getkq 后再跟一个 ‘noop’ 命令。
示例
要请求 key 为 “Hello” 的数据,需要在包中指定以下字段。
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| 0x80 | 0x00 | 0x00 | 0x05 | +---------------+---------------+---------------+---------------+ 4| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 8| 0x00 | 0x00 | 0x00 | 0x05 | +---------------+---------------+---------------+---------------+ 12| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 16| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 20| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 24| 0x48 ('H') | 0x65 ('e') | 0x6c ('l') | 0x6c ('l') | +---------------+---------------+---------------+---------------+ 28| 0x6f ('o') | +---------------+
Total 29 bytes (24 byte header, and 5 bytes key)
Field (offset) (value) Magic (0) : 0x80 Opcode (1) : 0x00 Key length (2,3) : 0x0005 Extra length (4) : 0x00 Data type (5) : 0x00 VBucket (6,7) : 0x0000 Total body (8-11) : 0x00000005 Opaque (12-15): 0x00000000 CAS (16-23): 0x0000000000000000 Extras : None Key (24-29): The textual string: "Hello" Value : None
如果该 item 存在于服务器上,则返回如下数据包;否则返回一个状态码非 0 的数据包(见 4.1 节简介)。
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| 0x81 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 4| 0x04 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 8| 0x00 | 0x00 | 0x00 | 0x09 | +---------------+---------------+---------------+---------------+ 12| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 16| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 20| 0x00 | 0x00 | 0x00 | 0x01 | +---------------+---------------+---------------+---------------+ 24| 0xde | 0xad | 0xbe | 0xef | +---------------+---------------+---------------+---------------+ 28| 0x57 ('W') | 0x6f ('o') | 0x72 ('r') | 0x6c ('l') | +---------------+---------------+---------------+---------------+ 32| 0x64 ('d') | +---------------+
Total 33 bytes (24 byte header, 4 byte extras and 5 byte value)
Field (offset) (value) Magic (0) : 0x81 Opcode (1) : 0x00 Key length (2,3) : 0x0000 Extra length (4) : 0x04 Data type (5) : 0x00 Status (6,7) : 0x0000 Total body (8-11) : 0x00000009 Opaque (12-15): 0x00000000 CAS (16-23): 0x0000000000000001 Extras : Flags (24-27): 0xdeadbeef Key : None Value (28-32): The textual string "World"
getk 和 getkq 的响应包与 get(q) 的区别在于包含了 key:
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| 0x81 | 0x00 | 0x00 | 0x05 | +---------------+---------------+---------------+---------------+ 4| 0x04 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 8| 0x00 | 0x00 | 0x00 | 0x09 | +---------------+---------------+---------------+---------------+ 12| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 16| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 20| 0x00 | 0x00 | 0x00 | 0x01 | +---------------+---------------+---------------+---------------+ 24| 0xde | 0xad | 0xbe | 0xef | +---------------+---------------+---------------+---------------+ 28| 0x48 ('H') | 0x65 ('e') | 0x6c ('l') | 0x6c ('l') | +---------------+---------------+---------------+---------------+ 32| 0x6f ('o') | 0x57 ('W') | 0x6f ('o') | 0x72 ('r') | +---------------+---------------+---------------+---------------+ 36| 0x6c ('l') | 0x64 ('d') | +---------------+---------------+
Total 38 bytes (24 byte header, 4 byte extras, 5 byte key and 5 byte value)
Field (offset) (value) Magic (0) : 0x81 Opcode (1) : 0x00 Key length (2,3) : 0x0005 Extra length (4) : 0x04 Data type (5) : 0x00 Status (6,7) : 0x0000 Total body (8-11) : 0x0000000E Opaque (12-15): 0x00000000 CAS (16-23): 0x0000000000000001 Extras : Flags (24-27): 0xdeadbeef Key (28-32): The textual string: "Hello" Value (33-37): The textual string: "World"
Set、Add、Replace
请求: - MUST 有 extras - MUST 有 key - MAY 有 value
set/add/replace 的附加数据:
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| Flags | +---------------+---------------+---------------+---------------+ 4| Expiration | +---------------+---------------+---------------+---------------+ Total 8 bytes
响应: - MUST 有 CAS - MUST NOT 有 extras - MUST NOT 有 key - MUST NOT 有 value
约束条件: - 如果 Data Version Check(CAS)非零,则只有当 item 存在且其 CAS 值与给定值相同时,请求的操作才允许成功。 - Add 在 item 已存在时必须失败。 - Replace 在 item 不存在时必须失败。 - Set 无论 item 存在与否都应无条件存储数据。
静默变更类命令只在失败时返回响应。成功被视为一般情况,在静默模式下会被抑制,但错误不允许被忽略。
示例
下图为一个 add 命令:key = “Hello”,value = “World”,flags = 0xdeadbeef,过期时间为两小时。
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| 0x80 | 0x02 | 0x00 | 0x05 | +---------------+---------------+---------------+---------------+ 4| 0x08 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 8| 0x00 | 0x00 | 0x00 | 0x12 | +---------------+---------------+---------------+---------------+ 12| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 16| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 20| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 24| 0xde | 0xad | 0xbe | 0xef | +---------------+---------------+---------------+---------------+ 28| 0x00 | 0x00 | 0x1c | 0x20 | +---------------+---------------+---------------+---------------+ 32| 0x48 ('H') | 0x65 ('e') | 0x6c ('l') | 0x6c ('l') | +---------------+---------------+---------------+---------------+ 36| 0x6f ('o') | 0x57 ('W') | 0x6f ('o') | 0x72 ('r') | +---------------+---------------+---------------+---------------+ 40| 0x6c ('l') | 0x64 ('d') | +---------------+---------------+
Total 42 bytes (24 byte header, 8 byte extras, 5 byte key and 5 byte value)
Field (offset) (value) Magic (0) : 0x80 Opcode (1) : 0x02 Key length (2,3) : 0x0005 Extra length (4) : 0x08 Data type (5) : 0x00 VBucket (6,7) : 0x0000 Total body (8-11) : 0x00000012 Opaque (12-15): 0x00000000 CAS (16-23): 0x0000000000000000 Extras : Flags (24-27): 0xdeadbeef Expiry (28-31): 0x00001c20 Key (32-36): The textual string "Hello" Value (37-41): The textual string "World"
操作结果通过状态码表示。如果命令成功,item 的 CAS 值会通过包的 CAS 字段返回:
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| 0x81 | 0x02 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 4| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 8| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 12| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 16| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 20| 0x00 | 0x00 | 0x00 | 0x01 | +---------------+---------------+---------------+---------------+
Total 24 bytes
Field (offset) (value) Magic (0) : 0x81 Opcode (1) : 0x02 Key length (2,3) : 0x0000 Extra length (4) : 0x00 Data type (5) : 0x00 Status (6,7) : 0x0000 Total body (8-11) : 0x00000000 Opaque (12-15): 0x00000000 CAS (16-23): 0x0000000000000001 Extras : None Key : None Value : None
Delete(删除)
请求: - MUST NOT 有 extras - MUST 有 key - MUST NOT 有 value
响应: - MUST NOT 有 extras - MUST NOT 有 key - MUST NOT 有 value
删除指定 key 对应的 item。
示例
下图为对 “Hello” 这个 item 的删除消息。
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| 0x80 | 0x04 | 0x00 | 0x05 | +---------------+---------------+---------------+---------------+ 4| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 8| 0x00 | 0x00 | 0x00 | 0x05 | +---------------+---------------+---------------+---------------+ 12| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 16| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 20| 0x00 | 0x00 | 0x00 | 0x00 | +---------------+---------------+---------------+---------------+ 24| 0x48 ('H') | 0x65 ('e') | 0x6c ('l') | 0x6c ('l') | +---------------+---------------+---------------+---------------+ 28| 0x6f ('o') | +---------------+
Total 29 bytes (24 byte header, 5 byte value)
Field (offset) (value) Magic (0) : 0x80 Opcode (1) : 0x04 Key length (2,3) : 0x0005 Extra length (4) : 0x00 Data type (5) : 0x00 VBucket (6,7) : 0x0000 Total body (8-11) : 0x00000005 Opaque (12-15): 0x00000000 CAS (16-23): 0x0000000000000000 Extras : None Key : The textual string "Hello" Value : None
操作结果通过状态码表示。
Increment、Decrement(增减)
请求: - MUST 有 extras - MUST 有 key - MUST NOT 有 value
incr/decr 的附加数据:
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| Amount to add / subtract | | | +---------------+---------------+---------------+---------------+ 8| Initial value | | | +---------------+---------------+---------------+---------------+ 16| Expiration | +---------------+---------------+---------------+---------------+ Total 20 bytes
响应: - MUST NOT 有 extras - MUST NOT 有 key - MUST 有 value
Byte/ 0 | 1 | 2 | 3 | / | | | | |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +---------------+---------------+---------------+---------------+ 0| 64-bit unsigned response. | | | +---------------+---------------+---------------+---------------+ Total 8 bytes
这两个命令会对请求的计数器加上或减去指定数值。如果想通过 add/set/replace 设置计数器的值,item 的数据必须是该值的 ASCII 表示,而不是 64 位整数的字节形式。
如果计数器不存在,会有两种情况之一发生: - 如果过期值全为 1(0xffffffff),操作将失败并返回 NOT_FOUND。 - 对于其他任何过期值,操作会成功:以给定的初始值为该 key 赋初值,并按给定的过期时间过期,flags 将被设为 0。
对计数器做 decrement 永远不会产生“负值”(也不会导致计数器“回绕”),而是把计数器设为 0。increment 则可能使计数