news 2026/10/7 20:14:29

twemproxy Memcached 协议支持详解:ASCII 命令矩阵、解析状态机与多键分片合并

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
twemproxy Memcached 协议支持详解:ASCII 命令矩阵、解析状态机与多键分片合并
  • 后端

【免费下载链接】twemproxy

A fast, light-weight proxy for memcached and redis

项目地址:https://gitcode.com/gh_mirrors/twe/twemproxy
点击查看免费下载

本指南以 twemproxy(nutcracker)仓库中的 notes/memcache.md 为骨架,系统梳理 twemproxy 对 Memcached 协议的支持范围与实现原理:仅实现 ASCII 文本协议、不包含二进制协议;完整覆盖存储、检索、删除、算术、touch、version 等命令的请求/响应格式,并深入 src/proto/nc_memcache.c 的逐字节状态机解析、多键get/gets的分片与响应合并逻辑,以及测试与配置层面的实战验证。读完本文,你将能准确判断 twemproxy 支持哪些 Memcached 命令、各命令的线上格式与返回值语义,并理解其"分片-转发-合并"的底层工作方式。

概览:只做 ASCII,不做 Binary

twemproxy 定位为 memcached 与 redis 的快速轻量代理(见 README.md),其 Memcached 协议实现遵循一条明确的边界:

  • 仅实现 Memcached ASCII 文本命令;
  • 二进制(Binary)协议目前不支持。

这与 src/proto/nc_memcache.c 中纯字符流逐字节解析的实现方式完全一致——解析器直接对\r\n分隔的文本行和<data>数据块做状态机处理,不存在任何二进制帧头解析。因此,凡是依赖 Memcached Binary 协议(如 SASL 认证、某些客户端库的默认二进制模式)的场景,需要将客户端切换为 ASCII 文本协议才能经由 twemproxy 访问后端。

请求命令支持矩阵

twemproxy 当前支持的 Memcached 请求命令共 17 种,下面按类别完整列出(沿用原文档的"支持状态 + 标准格式"表达,格式中的\r\n为行结束符)。

ASCII 存储命令(Storage Command)

命令支持?格式
set是set <key> <flags> <expiry> <datalen> [noreply]\r\n<data>\r\n
add是add <key> <flags> <expiry> <datalen> [noreply]\r\n<data>\r\n
replace是replace <key> <flags> <expiry> <datalen> [noreply]\r\n<data>\r\n
append是append <key> <flags> <expiry> <datalen> [noreply]\r\n<data>\r\n
prepend是prepend <key> <flags> <expiry> <datalen> [noreply]\r\n<data>\r\n
cas是cas <key> <flags> <expiry> <datalen> <cas> [noreply]\r\n<data>\r\n

其中各字段类型为:

  • <flags>:uint32_t,客户端自定义的数据相关标志位;
  • <expiry>:uint32_t,过期时间(秒);
  • <datalen>:uint32_t,数据块大小(字节);
  • <data>:uint8_t[],数据块本体;
  • <cas>:uint64_t,CAS 令牌。

从源码看,存储类命令在解析完成后进入专门的SW_VAL状态按<datalen>精确消费数据块(见 src/proto/nc_memcache.c),并允许<datalen>为 0(此时数据块为空)。memcache_storage()函数将set/add/replace/append/prepend/cas六类统一归为存储命令(src/proto/nc_memcache.c)。

ASCII 检索命令(Retrieval Command)

命令支持?格式
get是get <key> [<key>]+\r\n
gets是gets <key> [<key>]+\r\n

get支持一次请求携带多个 key,gets则在返回时附带 CAS 令牌。这两者是 twemproxy 中唯一允许携带多键的 Memcached 命令,也是唯一会被"分片(fragment)"处理的命令(详见下文)。

ASCII 删除命令(Delete)

命令支持?格式
delete是delete <key> [noreply]\r\n

ASCII 算术命令(Arithmetic Command)

命令支持?格式
incr是incr <key> <value> [noreply]\r\n
decr是decr <key> <value> [noreply]\r\n

其中<value>为uint64_t,表示增量/减量。

ASCII 其他命令(Misc Command)

命令支持?格式
touch是touch <key> <expiry>[noreply]\r\n
gat计划中gat <expiry> <key>+\r\n
gats计划中gats <expiry> <key>+\r\n
quit是quit\r\n
flush_all否flush_all [<delay>] [noreply]\r\n
version是version\r\n
verbosity否verbosity <num> [noreply]\r\n
stats否stats\r\n
stats否stats <args>\r\n

需要特别留意"计划中/不支持"列:

  • gat/gats(get-and-touch)标注为Planned,当前版本尚未实现;
  • flush_all、verbosity、stats均标注No,twemproxy 不会转发这些命令,客户端直接使用时会得到错误响应。

命令识别的源码依据

在 src/proto/nc_memcache.c 的memcache_parse_req()中,命令名按字节长度 + 逐字符比对识别:3 字符匹配get/set/add/cas,4 字符匹配gets/incr/decr/quit,5 字符匹配touch,6 字符匹配append/delete,7 字符匹配prepend/replace/version。长度比对使用 src/proto/nc_proto.h 定义的str4cmp~str12cmp宏(小端平台上按 32 位整型一次比较 4 字节,减少逐字符比较开销)。所有消息类型统一在 src/nc_message.h 的MSG_TYPE_CODEC中声明,如MSG_REQ_MC_SET、MSG_REQ_MC_GETS、MSG_RSP_MC_VALUE等。

响应格式

twemproxy 会解析后端 Memcached 返回的 ASCII 响应并原样转发给客户端。以下按类别完整列出各类响应的文本格式。

错误响应(Error Responses)

ERROR\r\n CLIENT_ERROR [error]\r\n SERVER_ERROR [error]\r\n

语义:

  • ERROR:客户端发送了不存在的命令名;
  • CLIENT_ERROR:客户端命令不符合协议规范;
  • SERVER_ERROR:服务端处理命令时出错,导致无法完成。

对应地,src/proto/nc_memcache.c 在解析响应时识别ERROR、CLIENT_ERROR、SERVER_ERROR三种错误类型(MSG_RSP_MC_ERROR等),并进入SW_RUNTO_CRLF状态吞掉错误详情直到行尾。

存储命令响应(Storage Command Responses)

STORED\r\n NOT_STORED\r\n EXISTS\r\n NOT_FOUND\r\n

语义:

  • STORED:存储成功;
  • NOT_STORED:因add或replace的条件未满足而未存储;
  • EXISTS:执行cas时,目标条目在你上次获取之后已被修改;
  • NOT_FOUND:执行cas时,目标条目不存在。

删除命令响应(Delete Command Responses)

NOT_FOUND\r\n DELETED\r\n

检索命令响应(Retrieval Responses)

END\r\n VALUE <key> <flags> <datalen> [<cas>]\r\n<data>\r\nEND\r\n VALUE <key> <flags> <datalen> [<cas>]\r\n<data>\r\n[VALUE <key> <flags> <datalen> [<cas>]\r\n<data>]+\r\nEND\r\n

单键命中时返回一行VALUE ...加数据块再加END;多键命中时返回多个VALUE行,最后统一以END\r\n收尾。[<cas>]仅在gets时出现。twemproxy 的响应解析器(src/proto/nc_memcache.c)通过MSG_RSP_MC_VALUE状态链(SW_SPACES_BEFORE_KEY→SW_KEY→SW_FLAGS→SW_VLEN→SW_RUNTO_VAL→SW_VAL→SW_VAL_LF)逐字段解析并校验数据长度。

算术命令响应(Arithmetic Responses)

NOT_FOUND\r\n <value>\r\n

其中<value>为uint64_t,即incr/decr操作后的新键值。解析时以纯数字行进入SW_RSP_NUM状态识别为MSG_RSP_MC_NUM。

touch 命令响应(Touch Command Responses)

NOT_FOUND\r\n TOUCHED\r\n

统计响应(Statistics Response)

[STAT <name> <value>\r\n]+END\r\n

需要说明:stats命令本身在 twemproxy 侧标注为No(不支持转发),此处仅按 Memcached 协议标准列出其响应形态,供了解协议完整性。

其他响应(Misc Responses)

OK\r\n VERSION <version>\r\n

协议语义要点(Notes)

原文档总结的 Memcached 语义细节,在配置 twemproxy 后端时同样适用,逐条列出如下:

  • set无条件创建映射,无论条目是否存在;
  • add仅当映射不存在时才添加;
  • replace仅当映射存在时才替换;
  • append与prepend会忽略 flags 和 expiry 值;
  • noreply指示服务端即使出错也不发送回复;
  • decr到 0 则结果为 0,incr超过UINT64_MAX则回绕为 0;
  • key 最大长度为250 字符(该常量在 src/proto/nc_memcache.c 定义为MEMCACHE_MAX_KEY_LENGTH 250,解析时超出即报错,见 src/proto/nc_memcache.c);
  • expiry为 0 表示条目永不过期(但仍可能因缓存淘汰被驱逐);
  • 非零expiry要么是 Unix 时间(自 1970-01-01 起的秒数),要么是相对当前时间的秒数偏移(小于60 × 60 × 24 × 30秒 = 30 天);
  • expiry 以服务端时间为准,而非客户端时间;
  • <datalen>可以为 0,此时<data>数据块为空。

noreply 的解析实现

noreply选项在存储、算术、删除、touch 命令中均可选。解析器在SW_RUNTO_CRLF状态遇到n时进入SW_NOREPLY,校验后续恰好是 7 字符的noreply并置r->noreply = 1(src/proto/nc_memcache.c)。存储类命令在 noreply 之后仍需继续读取<data>数据块。

多键 get/gets 的分片与响应合并

这是 twemproxy 处理 Memcached 请求时最核心、也最能体现其分片代理价值的一环。

何时分片

memcache_should_fragment()(src/proto/nc_memcache.c)规定:只有get/gets且携带多个 key 时才分片。原因有二:

  1. 分片需要额外分配数组跟踪"哪个 key 去了哪台后端",代价更高,因此单键请求绝不分片、直接路由到一台后端;
  2. 多键get的各个 key 可能被哈希到不同后端,必须按后端拆分后并发请求,再合并响应。

分片过程

memcache_fragment_retrieval()(src/proto/nc_memcache.c)按以下步骤构造子请求:

  1. 为每台后端建立对应的子消息sub_msg;
  2. 遍历请求中的每个 key,用msg_backend_idx()依据哈希/分布算法决定其归属后端,并把 key 追加到该后端的子消息中;
  3. 依据原命令类型为每个子消息前置get(4 字节)或gets(5 字节)头,并追加\r\n结尾;
  4. 记录frag_id、frag_owner用于后续合并,并把子消息串入碎片队列。

由此,形如get k1 k2 k3的多键请求会被拆成若干get k1\r\n、get k2 k3\r\n等单后端请求并行发出。

响应合并

  • pre-coalesce(src/proto/nc_memcache.c):每当收到一个碎片响应时执行。对VALUE/END类型响应,会裁掉每台后端返回的END\r\n标记(只保留VALUE行),避免合并时出现多余的 END;对异常响应则标记请求错误并返回SERVER_ERROR。
  • post-coalesce(src/proto/nc_memcache.c):当所有碎片响应到齐后执行,遍历每个 key 对应的子响应,通过memcache_copy_bulk()(src/proto/nc_memcache.c)把各VALUE <key> <flags> <len> [<cas>]\r\n<data>\r\n块(mbuf 层面直接搬移/切分,接近零拷贝)复制到合并响应中,最后统一追加一个END\r\n收尾,返回给客户端。

这套"分片请求 → 并行转发 → 裁剪 END → 合并 VALUE → 补 END"的流程,让客户端可以像访问单机 Memcached 一样发送多键get/gets,而实际数据被透明地分布到多台后端。

实战:配置 Memcached 池并验证

用配置声明 Memcached 池

twemproxy 的每个池通过 conf/nutcracker.yml 中的 YAML 块定义。池默认即为 Memcached 协议,只有显式写redis: true才会启用 Redis 协议(该字段在 src/nc_conf.c 中被默认置为CONF_DEFAULT_REDIS,即默认关闭)。例如 conf/nutcracker.yml 中的gamma、delta、omega池没有写redis: true,其监听端口对接的1121x正是 Memcached 常用端口;而 conf/nutcracker.leaf.yml 中的leaf池同样面向11212/11213两台 Memcached,并使用fnv1a_64哈希与ketama一致性分布:

leaf: listen: 127.0.0.1:22121 hash: fnv1a_64 distribution: ketama auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:11212:1 - 127.0.0.1:11213:1

启动方式(详见 README.md):

$ src/nutcracker -c conf/nutcracker.leaf.yml -d # 指定配置文件并守护进程化 $ src/nutcracker -t -c conf/nutcracker.leaf.yml # 仅校验配置语法

用 telnet 手工验证协议

由于是 ASCII 协议,可直接用 telnet 或 nc 手工敲命令验证(这正是原文档"ASCII 协议更易调试"观点的实践):

$ telnet 127.0.0.1 22121 Trying 127.0.0.1... Connected to 127.0.0.1. set foo 0 0 3\r\n bar\r\n STORED\r\n get foo\r\n VALUE foo 0 3\r\n bar\r\n END\r\n incr counter 5\r\n 5\r\n version\r\n VERSION 1.4.x\r\n quit\r\n

自动化测试佐证

仓库的测试目录 tests/test_memcache/ 提供了可运行的验证脚本,例如 tests/test_memcache/test_gets.py:

  • 起两个 Memcached 后端(127.0.0.1:2200/2201)与一个 nutcracker 实例(监听127.0.0.1:4100,is_redis=False);
  • 用 python-memcache 客户端连接 nutcracker,覆盖set/get/incr/delete基础用例;
  • test_mget_mset验证多键get_multi/set_multi经 twemproxy 分片合并后的正确性;
  • test_mget_mset_large将键数量从 179 递增到T_LARGE(默认 1000),覆盖大数量多键请求;
  • test_mget_mset_key_not_exists验证部分 key 不存在(返回空)时多键get_multi行为正确。

这说明多键分片合并不仅是设计文档中的规划,更是有自动化测试保障的既定能力。

调试思路与未来方向

原文档在 Notes 末尾记录了两点技术考量,在此一并转述:

  1. ASCII 协议更易调试:相比二进制协议,ASCII 文本协议在线上形态直观,可以用strace、tcpdump抓包观察线上的字节流,也可以用 telnet、netcat、socat 直接构造 Memcached 请求与响应来做实验——这也是上文手工验证方法的理论来源。
  2. meta-text 协议的演进规划:twemproxy 计划在 meta-text 协议被标记为稳定、且使用它的 Memcached 服务端经过数个版本发布之后,再支持这一更高效的协议。也就是说,在本文所述的 ASCII 支持之外,meta-text 属于文档明确记载的、面向未来的扩展方向。

小结

围绕 notes/memcache.md,本文完整还原了 twemproxy 的 Memcached 协议支持全貌:仅 ASCII、共支持 17 种命令(存储 6、检索 2、删除 1、算术 2、其他 6),其中gat/gats计划中、flush_all/verbosity/stats不支持;各类请求与响应的文本格式与字段类型、noreply、250 字符 key 上限、30 天内的相对过期时间等语义细节均已列出;并进一步结合 src/proto/nc_memcache.c 的状态机解析与多键分片合并实现、conf/nutcracker.yml 的 Memcached 池配置示例及 tests/test_memcache/test_gets.py 的测试用例,形成了从"命令格式"到"实现原理"再到"可运行验证"的完整链路。若需对照 Redis 协议侧的能力,可继续阅读 notes/redis.md。

  • 后端

【免费下载链接】twemproxy

A fast, light-weight proxy for memcached and redis

项目地址:https://gitcode.com/gh_mirrors/twe/twemproxy
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 20:14:19

古诗怎么背不忘?用艾宾浩斯遗忘曲线复习

孩子背古诗「背了忘、忘了背」&#xff0c;问题大多不在「记性差」&#xff0c;而在「没复习」——或者说&#xff0c;复习的时机不对。人脑遗忘有规律&#xff1a;刚学完忘得最快&#xff0c;之后逐渐变慢。这就是艾宾浩斯遗忘曲线。按这个规律安排复习&#xff0c;在快忘的时…

作者头像 李华
网站建设 2026/10/7 20:12:38

​四足角色自动绑定怎样选择工具?标准站姿与骨骼检查

四足角色首先要满足标准站姿和结构清楚这两个前提。若角色有额外肢体、夸张姿势或抽象结构&#xff0c;应先做小样&#xff0c;并预留人工绑定方案。Tripo的Rigging功能可以为符合要求的静态角色生成骨骼&#xff0c;随后通过Bone Display检查骨骼&#xff0c;并从动作库选择动…

作者头像 李华