外观
Memcached二进制协议
协议结构
1. 通用消息头
二进制协议的所有消息都以一个24字节的固定头开始:
| 字段 | 偏移 | 大小 | 类型 | 说明 |
|---|---|---|---|---|
| Magic | 0 | 1 | uint8_t | 消息类型标识:请求=0x80,响应=0x81 |
| Opcode | 1 | 1 | uint8_t | 命令操作码 |
| Key Length | 2 | 2 | uint16_t | 键的长度(网络字节序) |
| Extra Length | 4 | 1 | uint8_t | 额外数据的长度 |
| Data Type | 5 | 1 | uint8_t | 数据类型,当前固定为0x00 |
| Reserved | 6 | 2 | uint16_t | 保留字段,必须为0 |
| Total Body Length | 8 | 4 | uint32_t | 总数据长度(网络字节序) |
| Opaque | 12 | 4 | uint32_t | 请求标识,响应中会原样返回 |
| CAS | 16 | 8 | uint64_t | 数据版本号,用于CAS操作 |
2. 消息体结构
消息体紧跟在消息头之后,结构如下:
[额外数据] [键] [值]- 额外数据:长度由Extra Length字段指定
- 键:长度由Key Length字段指定
- 值:长度 = Total Body Length - Extra Length - Key Length
命令操作码
1. 核心命令
| 操作码 | 十六进制 | 命令 | 说明 |
|---|---|---|---|
| GET | 0x00 | Get | 获取单个键 |
| SET | 0x01 | Set | 设置键值对 |
| ADD | 0x02 | Add | 仅当键不存在时设置 |
| REPLACE | 0x03 | Replace | 仅当键存在时设置 |
| DELETE | 0x04 | Delete | 删除键 |
| INCREMENT | 0x05 | Increment | 递增数值 |
| DECREMENT | 0x06 | Decrement | 递减数值 |
| QUIT | 0x07 | Quit | 关闭连接 |
| FLUSH | 0x08 | Flush | 清空缓存 |
| GETQ | 0x09 | Get Quiet | 安静模式获取,无响应 |
| NOOP | 0x0a | Noop | 无操作,用于心跳检测 |
| VERSION | 0x0b | Version | 获取版本信息 |
| GETK | 0x0c | Get with Key | 获取键值对并返回键 |
| GETKQ | 0x0d | Get with Key Quiet | 安静模式获取并返回键 |
| APPEND | 0x0e | Append | 追加数据到值末尾 |
| PREPEND | 0x0f | Prepend | prepend数据到值开头 |
2. 扩展命令
| 操作码 | 十六进制 | 命令 | 说明 |
|---|---|---|---|
| STAT | 0x10 | Stats | 获取统计信息 |
| SETQ | 0x11 | Set Quiet | 安静模式设置,无响应 |
| ADDQ | 0x12 | Add Quiet | 安静模式添加,无响应 |
| REPLACEQ | 0x13 | Replace Quiet | 安静模式替换,无响应 |
| DELETEQ | 0x14 | Delete Quiet | 安静模式删除,无响应 |
| INCREMENTQ | 0x15 | Increment Quiet | 安静模式递增,无响应 |
| DECREMENTQ | 0x16 | Decrement Quiet | 安静模式递减,无响应 |
| QUITQ | 0x17 | Quit Quiet | 安静模式退出,无响应 |
| FLUSHQ | 0x18 | Flush Quiet | 安静模式清空,无响应 |
| APPENDQ | 0x19 | Append Quiet | 安静模式追加,无响应 |
| PREPENDQ | 0x1a | Prepend Quiet | 安静模式 prepend,无响应 |
| TOUCH | 0x1c | Touch | 更新键的过期时间 |
| GAUGE | 0x21 | Gauge | 设置数值(类似SET) |
| GAUGEQ | 0x22 | Gauge Quiet | 安静模式设置数值,无响应 |
响应状态码
| 状态码 | 十六进制 | 描述 | ASCII等价 |
|---|---|---|---|
| SUCCESS | 0x00 | 操作成功 | - |
| KEY_NOT_FOUND | 0x01 | 键不存在 | NOT_FOUND |
| KEY_EXISTS | 0x02 | 键已存在 | EXISTS |
| VALUE_TOO_LARGE | 0x03 | 值过大 | VALUE |
| INVALID_ARGUMENTS | 0x04 | 参数无效 | ERROR |
| ITEM_NOT_STORED | 0x05 | 数据未存储 | NOT_STORED |
| INCR_DECR_ON_NON_NUMERIC | 0x06 | 对非数值进行增减操作 | CLIENT_ERROR |
| VBUCKET_BELONGS_TO_ANOTHER_SERVER | 0x07 | 桶属于其他服务器 | - |
| AUTHENTICATION_ERROR | 0x20 | 认证错误 | AUTH ERROR |
| AUTHENTICATION_CONTINUE | 0x21 | 认证继续 | AUTH CONTINUE |
| UNKNOWN_COMMAND | 0x81 | 未知命令 | ERROR |
| OUT_OF_MEMORY | 0x82 | 内存不足 | - |
命令示例
1. SET命令示例
请求格式:
| 字段 | 值 |
|---|---|
| Magic | 0x80 |
| Opcode | 0x01 (SET) |
| Key Length | 0x0003 (3) |
| Extra Length | 0x08 (8) |
| Data Type | 0x00 |
| Total Body Length | 0x0000000B (11) |
| Opaque | 0x00000001 (请求标识) |
| CAS | 0x0000000000000000 (无) |
| 额外数据 | [过期时间(4字节)][标志(4字节)] |
| 键 | "key" |
| 值 | "value" |
二进制表示:
80 01 00 03 08 00 00 00 00 00 00 0B 00 00 00 01 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 6B 65 79 76 61 6C 75 65响应格式:
| 字段 | 值 |
|---|---|
| Magic | 0x81 |
| Opcode | 0x01 (SET) |
| Key Length | 0x0000 (0) |
| Extra Length | 0x00 (0) |
| Data Type | 0x00 |
| Status | 0x0000 (SUCCESS) |
| Total Body Length | 0x00000000 (0) |
| Opaque | 0x00000001 (与请求相同) |
| CAS | 0x0000000000000001 (新的CAS值) |
二进制表示:
81 01 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 012. GET命令示例
请求格式:
| 字段 | 值 |
|---|---|
| Magic | 0x80 |
| Opcode | 0x00 (GET) |
| Key Length | 0x0003 (3) |
| Extra Length | 0x00 (0) |
| Data Type | 0x00 |
| Total Body Length | 0x00000003 (3) |
| Opaque | 0x00000002 (请求标识) |
| CAS | 0x0000000000000000 (无) |
| 键 | "key" |
二进制表示:
80 00 00 03 00 00 00 00 00 00 00 03 00 00 00 02 00 00 00 00 00 00 00 00
6B 65 79响应格式:
| 字段 | 值 |
|---|---|
| Magic | 0x81 |
| Opcode | 0x00 (GET) |
| Key Length | 0x0000 (0) |
| Extra Length | 0x04 (4) |
| Data Type | 0x00 |
| Status | 0x0000 (SUCCESS) |
| Total Body Length | 0x00000009 (9) |
| Opaque | 0x00000002 (与请求相同) |
| CAS | 0x0000000000000001 (当前CAS值) |
| 额外数据 | [标志(4字节)] |
| 值 | "value" |
二进制表示:
81 00 00 00 04 00 00 00 00 00 00 09 00 00 00 02 00 00 00 00 00 00 00 01
00 00 00 00 76 61 6C 75 65性能优势
相比ASCII协议,二进制协议具有以下性能优势:
- 固定长度头:减少了解析开销
- 高效的错误处理:使用状态码替代字符串错误信息
- 更紧凑的表示:减少了网络传输字节数
- 更好的类型安全:使用二进制数据类型,避免了字符串转换
- 支持更多命令:提供了更丰富的命令集
- 安静模式:支持无响应的命令,提高了吞吐量
客户端开发建议
1. 连接管理
- 使用连接池管理TCP连接
- 实现连接超时和重试机制
- 支持TCP_NODELAY选项,减少延迟
2. 命令批处理
- 支持命令批处理,减少网络往返次数
- 合理使用安静模式命令
- 注意命令顺序和依赖关系
3. 错误处理
- 正确处理各种响应状态码
- 实现适当的重试逻辑
- 监控和记录错误情况
4. 性能优化
- 使用高效的序列化/反序列化库
- 合理设置缓冲区大小
- 优化键的设计,减少键长度
- 避免频繁的小命令,使用批处理
二进制协议vs ASCII协议
| 特性 | 二进制协议 | ASCII协议 |
|---|---|---|
| 效率 | 高 | 中 |
| 错误处理 | 结构化状态码 | 字符串错误信息 |
| 命令集 | 丰富 | 基础 |
| 扩展性 | 好 | 差 |
| 调试友好 | 差 | 好 |
| 客户端支持 | 广泛 | 非常广泛 |
| 网络开销 | 小 | 大 |
工具和库
1. 客户端库
| 语言 | 支持二进制协议的库 |
|---|---|
| Java | SpyMemcached, Xmemcached |
| Python | pymemcache, python-memcached2 |
| PHP | php-memcached (扩展名) |
| C/C++ | libmemcached, memcached-client-library |
| Go | gomemcache, memcache |
| Node.js | memcached, mc |
2. 调试工具
- telnet:可以手动发送二进制命令,但需要手动构造二进制数据
- netcat:类似telnet,支持二进制数据传输
- wireshark:可以捕获和分析二进制协议流量
- memcached-tool:Memcached自带的工具,支持二进制协议
最佳实践
1. 选择合适的协议
- 对于性能要求高的场景,使用二进制协议
- 对于开发和调试阶段,使用ASCII协议
- 考虑客户端库的支持情况
2. 合理设计键值
- 键长度不宜过长,建议不超过250字节
- 值大小不宜过大,建议不超过1MB
- 考虑数据压缩,减少网络传输量
3. 监控和调优
- 监控协议层面的指标,如命令吞吐量、响应时间
- 分析网络流量,优化命令模式
- 根据业务特点调整协议参数
4. 安全考虑
- 结合传输层加密(TLS/SSL)
- 实现适当的认证机制
- 限制命令访问权限
常见问题(FAQ)
Q1: 所有Memcached服务器都支持二进制协议吗?
A1: 是的,从Memcached 1.4版本开始,所有官方Memcached服务器都支持二进制协议。
Q2: 二进制协议比ASCII协议快多少?
A2: 具体性能提升取决于多种因素,如命令类型、数据大小、网络环境等。一般来说,二进制协议比ASCII协议快10%-30%。
Q3: 如何调试二进制协议?
A3: 可以使用以下工具:
- Wireshark:捕获和分析网络流量
- 客户端库的调试日志
- 自定义调试工具,如编写简单的客户端程序打印发送和接收的二进制数据
Q4: 二进制协议支持CAS操作吗?
A4: 是的,二进制协议完全支持CAS(Check and Set)操作,通过消息头中的CAS字段实现。
Q5: 如何从ASCII协议迁移到二进制协议?
A5: 迁移步骤:
- 评估当前客户端库是否支持二进制协议
- 如果不支持,选择支持二进制协议的客户端库
- 修改客户端代码,使用二进制协议API
- 进行充分的测试,确保功能和性能正常
- 逐步上线,监控系统表现
