协议
---------
Memcache客户端通过TCP连接与服务器进行通信(UDP接口也是可用的,在下文的"UDP协议"部分有详细描述)。Memcache服务器在一个(可配置的)端口上监听;客户端连接该端口,发送命令到服务器,读取响应,最后关闭连接。
没有必要发送命令来终止会话。如果不需要该连接,客户端任何时候都可以将其关闭。但即便是这样,客户端最好还是缓存连接,而不要在它们要存取数据时再打开连接。这是由于memcache被特意设计成可以处理大量(数百个,需要的话可以上千个)打开的连接。缓存这些连接会减少建立TCP连接所带来的开销(相对于此,在服务器端建立一个新连接的准备工作所带来的开销,可以忽略不计).
在Memcache协议中发送的数据分两种:文本行和非结构数据(unstructured data)。由客户端发送的命令和服务器端的响应使用的是文本行。非结构数据用于客户端存储数据的时候。服务器会将接收到的非结构数据, 以字节流的形式原样返回。服务器并不关心在非结构数据中的字节序的问题。对于非结构数据中的字符没有任何限制,但数据的读取方(客户端或服务器)需要通过一个文本行数据,得知正在传输的数据块的精确长度。
文本行总是以/r/n结束,非结构数据也总以/r/n结束, 但/r, /n或者其它的8位字符也可能出现在数据中,所以当客户端从服务器读取数据时,必须使用所提供数据长度来决定数据何时结束,而不是基于/r/n在数据块的结束处,虽然确实是如此。
键(Keys)
----------
在Memcache中存储的数据是以键来标识的。键是一个全局唯一的文本串标识,客户端通过经来存取所需的数据。当前,键的长度限制是250个字符(当然,通常来说客户端并不需要使用那么长的键)。键不参包含控制字符和空格。
命令(Commands)
----------
存储命令(有6个: "set", "add" ,"replace", "append", "prepend" 和 "cas")要求服务器存储以键标识的数据。客户端发送一个命令行,然后是一个数据块;之后客户端等待一个表示成功/失败的响应行。
提取命令(有两个:"get"和"gets") 要求服务器提取一个键集(一次请求一个或多个键)的数据。客户端发送一个包含所有请求键的命令行。对于每一个数据项,服务器查找到后,发送一个响应行对客户端,内含该数据项的信息,后跟该数据项的数据,直到服务器发送了一个"END"响应行,表示整个过程的结束。
命令行总是以命令行开头,后跟以空格分隔的参数(如果有的话)。命令行是小写的并且大小写敏感。
过期时限(Expiration times)
----------
客户端发送给服务器的某些命令可以带有(一个数据项或一个客户端请求操作的)过期时限信息。在这些情况下,发送的实际值可以是Unix时间(自1970年1月1日至今的秒数,32位值),或是从当前起的秒数。在后一种情况下,秒数不能超过60*60*24*30(30天的秒数)。如果客户端发送的数值超过此值,服务器会认为它是一个Unix时间值,而不是距当前时间的偏移值。
出错字符串(Error strings)
------------
客户端发送的每一个命令,服务器可能会发送一个出错字符串作为响应。这些出错字符串有三种类型:
-- "ERROR/r/n"
表示客户端发送了一个不存在的命令
-- "CLIENT_ERROR <error>/r/n"
表示客户端的输入行出现了某些错误,例如,输入行没有遵从协议。<error>是一个人可读的错误描述。
-- "SERVER_ERROR <error>/r/n"
表示服务器的一些错误导致服务器不能执行该命令。<error>是一个人可读的出错描述字符串。如果出现了使服务器无法继续为客户端服务的服务器错误(这一般不应发生),服务器在发送完错误字符串后会关闭连接。这是服务器端关闭客户端连接的唯一情况。
在以下的每个命令描述中,错误行不会再次提及,但客户端必须考虑到它们可能会出现。
存储命令
--------------
首先,客户端发送一个类似这样的命令行:
<command name> <key> <flags> <exptime> <bytes> [noreply]/r/n
cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]/r/n
--<command> 有 "set ", "add ", "replace", "append" 和 "prepend"
"set" 表示存储这个数据
"add" 表示存储这个数据,但只在服务器没有存储该Key的数据的情况下。
"replace" 表示存储这个数据,但只在服务器存储了该Key的数据的情况下。
"append"表示为一个已存在的Key的数据在尾部追加该部分的数据。
"prepend"表示为一个已存在的Key的数据在头部添加该部分的数据。
append和prepend命令不接受flag和exptime。
它们更新已存数据,而忽略新的flag和exptime设置。
cas是一个检查并设置的操作,意思是"存储这个数据,但只有在自我上次取得该数据以来没有人再更新该数据的情况下才存储"。
--<key> 客户端要求存储的数据键值
--<flags> 是一个可选的16位无符串整数(以十进制形式写入),服务器会在取回该数据项时一同原样返回。客户端可以将其用作一个位存储域来存储一些数据特征信息。这个域对服务器是透明的。注意在memcached 1.2.1和更高版本,flags可以是32位的,而不是16位,不过你或许会限制使用16位来兼容旧版本。
--<exptime> 是过期时间。如果设置为0, 则该数据项永不过期(尽管如此,它还是可能从缓存中被删除掉,以为其它数据项腾出空间)。如果它不为0(或者是Unix 时间,或者是相当于当前时间的偏移值), 它可以保证在过期时间到后,客户端不会存储到该数据(用服务器时间度量)。
--<bytes> 是后跟数据的字节长度,没有计算作为分隔符的/r/n. <bytes>可能为0 (在这种情况下,它后跟一个空的数据块)。
--<cas unique> 是一个与已存数据条目相关的全局唯一的64位数。客户端应该使用"gets"命令返回的该值来进行"cas"更新操作。
--"noreply" 可选项指示服务器不要回送响应。注意:如果请求行格式错误,服务器不一定能可靠地解析"noreply"选项。在此种情况下,它可能会发送错误信息给客户端,如果客户端没有读取该信息的话会带来问题。客户端应该只构造合法的请求。
在此行后,客户端发送数据块。
<data block>/r/n
- <data block>是一个任意的8位字节数据块,长度为前面一行所指定的数据长度<bytes>。
在发送了该命令行和数据块后,客户端等待可能的响应:
- "STORED/r/n", 指示成功
- "NOT_STORED/r/n"指示数据没有被存储,但不是出错。这通常是表示"add"或"replace"的条件没有满足。
- "EXIST/r/n"指示使用"cas"命令所要更新的数据自你上次取过后已经过修改。
-"NO_FOUND/r/n"指示你用"cas"命令要修改的数据并不存在。
数据提取命令:
----------------
数据提取命令有"get"和"gets",如:
get <key>*/r/n
get <eky>*/r/n
-<key>*表示一个或多个由空格分隔的key串
在这个命令后,客户端期待零个或多个数据项,每个接受到的数据项是一个文本行,后跟数据块。当所有数据项传送完后,服务器发送一个"END/r/n"指示响应的终止。
每一个由服务器发送的数据项如下所示:
VALUE <key> <flags> <bytes> [<cas unique>]/r/n
<data block>/r/n
- <key> 数据项的Key值
- <flags> 是由存储命令设置的flags
- <bytes>是后跟数据块的长度,没有包括作为分隔符的/r/n
- <cas unique>是一个64位整数,唯一标识了一个特定的数据项。
- <data block> 数据项的数据
如果有某些在请求中出现的Key没有服务器没有在数据项列表中回送,表示服务器没有这些Key的数据项(因为它们没有被存储过,或者删除以为其它数据项腾出空间,或者过期了,又或者被客户端的delete 操作删除了)。
删除
----------
delete命令允许显式删除数据项:
delete <key> [noreply]/r/n
- <key> 客户端要求服务器删除的数据项的key值
- "norply" 可选参数指示服务器不要回送响应。参见存储命令中对格式错误请求的说明。
这个命令的响应行可能是以下的一个:
--"DELETED/r/n" 指示成功
-- "NOT_FOUND/r/n" 指示Key所指的数据项不存在。
参见下文的"flush_all"命令,该命令使所有数据项立即无效。
递增/递减
-----------
incr和decr命令用来原地改变某些数据项,递增或递减其值。数据项的数据被看作是64位的无符号十进制整数。如果当前的数据值不能转换为这样一种表示,则incr/decr命令返回一个错误(memcached <= 1.2.6将这种伪值(bogus value)看成是0, 这会导致混乱)。另外,这个数据项必须已存在使得 incr/decr能工作。这些命令不会假设一个不存在的key数值为0,而是会失败。
客户端发送的命令行如下:
incr <key> <value> [noreply]/r/n
或
desc <key> <value> [noreply]/r/n
- <key> 客户端想改变的数据项的key值
- <value> 客户端要对数据项递增/递减的值。它是一个64位的无符号十进制整数。
- "norply" 可选参数指示服务器不要回送响应。参见存储命令中对格式错误请求的说明。
响应可能是以下之一:
- "NOT_FOUND/r/n" 指示这个数据项找不到
-"<value>/r/n", 其中<value>是这个数据项在经过递增/递减操作后的新值
注意如果"desr"命令捕获到下溢异常:如果客户端尝试将一个值减少到小于0, 新值将会是0. "incr"命令的上溢异常会经64位回绕(wrap around the 64 bit mark)。【译者注:即 (原值+value) & 0xffffffffffffffff】.
同样要注意的是减少一个数值使得其实际长度减少了,但并不保证其返回长度值的减少。这个数值可能会在尾部加上空格,但这纯属一个实现上的优化手段,所以你不能依赖它。
统计
-----------
"stats" 命令用于查询服务器维护的一些统计数据和内部数据。它有两种形式。不用参数:
stats/r/n
它使服务器输出常规统计和设置,下文中有说明。 另一种形式带有参数:
stats <args>/r/n
根据 <args> ,服务器会返回不同的内部数据。参数的类型和发送的数据没有在这个版本的协议中说明,这有利于memcache开发者(对代码)进行更改。
常规统计
-------------
当接收到不带参数的"stats"命令后, 服务器发送若干行数据,如下所示:
STAT <name> <value>/r/n
服务器用以下行作为终结:
END/r/n
在每一行统计数据中, <name>是统计项的名称,<value>是统计值。在以下的列表中有对"stats"命令响应的所有统计名称,和这个统计项的类型,以及这个值的意义。
在下面的类型列中,"32u" 表示32位无符号整数,"64u"表示64位无符号整数,"32u.32.u"表示两个由分号分隔的32位无符号整数(当作是浮点数)。
名称 | 类型 | 含义 |
pid | 32u | 服务器进程的id |
uptime | 32u | 自服务器启动以来的秒数 |
time | 32u | 服务器上的UNIX时间 |
version | string | 服务器的版本 |
pointer_size | 32 | 主机操作系统的默认指针长度(一般是32或64) |
rusage_user | 32u.32u | 进程用户空间的累计时长(秒:微秒) |
rusage_system | 32u.32u | 进程内核空间的累计时长 (秒:微秒) |
curr_items | 32u | 当前存储的数据项数目 |
total_items | 32u | 自服务器启动以来存储的数据项总数(译者注:其实就是成功执行存储命令(STORED)的计数) |
bytes | 64u | 当前用于存储数据项所用的字节数 |
curr_connections | 32u | 打开的连接数 |
total_connections | 32u | 自服务器启动以来的打开的连接数 |
connection_structures | 32u | 服务器分配的连接结构的数目 |
cmd_get | 64u | 提取命令的累计数目 |
cmd_set | 64u | 存储命令的累计数目(译者注:也包括NOT_STORED的) |
get_hits | 64u | 请求并找到的数据项的数目 |
get_missed | 64u | 请求但找不到的数据项的数目 |
delete_missed | 64u | 删除无效key的请求数 |
delete_hits | 64u | 使得数据项删除的删除请求数目 |
incr_missed | 64u | 没有找到key的incr请求数 |
incr_hits | 64u | 成功的incr请求数 |
decr_misses | 64u | 没有找到key的decr请求数 |
decr_hits | 64u | 成功的decr请求数 |
cas_misses | 64u | 没有找到key的CAS请求数 |
cas_hits | 64u | 成功CAS请求数 |
cas_badval | 64u | key存在但CAS值不匹配的CAS请求的数目 |
auth_cmds | 64u | 认证命令处理的次数,包括成功和失败 |
auth_errors | 64u | 身份认证失败的数目 |
evictions | 64u | 从缓存中移除数据项来为新数据项腾出空间的数目 |
reclaimed | 64u | 利用一个过期数据条目来存储新数据条目的数目 |
bytes_read | 64u | 本服务器从网络中读取的字节总数 |
bytes_written | 64u | 本服务器发送到网络的字节总数 |
limit_maxbytes | 32u | 本服务器允许使用的内存字节数 |
threads | 64u | 请求的工作线程数(参见 doc/threads.txt) |
conn_yields | 64u | 由于达到-R选项指定数目的限制,一个连接的操作主动放弃让给另一个连接的数目 |
设置项统计
----------------
警告:这部分所描述的统计项以后可能会有变更。
带有"settings" 的 "stats"命令返回memcached的设置细节。这基本上就是由进程命令行的选项所组成的。
注意这里不保证统计项的次序,列表也不一定是详尽的。除此之外,它的返回结果与其它的stats命令是一样的。
名称 | 类型 | 含义 |
maxbytes | size_t | 缓存中的最大字节数 |
maxconns | 32 | 最大的并发客户端数 |
tcpport | 32 | TCP监听端口 |
udpport | 32 | UDP监听端口 |
inter | string | 监听地址 |
verbosity | 32 | 0=无, 1=部分, 2=很多 |
oldest | 32u | 最老对象的时长 |
evictions | on/off | 当为off时,LRU清除策略禁用 |
domain_socket | string | Unix socket的文件路径 |
umask | 32 (oct) | Unix socket 的屏蔽字 |
growth_factor | float | Chunk大小的增长因子 |
chunk_size | 32 | 为 key+ value + flag分配的最小空间 |
num_threads | 32 | 线程数(包括分派线程) |
stat_key_prefix | char | 统计前缀分隔符 |
detail_enabled | bool | 如果为真,启用统计细节 |
reqs_per_event | 32 | 在一个事件处理里的最大IO请求数 |
cas_enabled | bool | 当为假时,CAS会被禁用 |
tcp_backlog | 32 | TCP 监听backlog值 |
auth_enabled_sasl | yes/no | 是否使用SASL |
数据项统计
----------------
警告: 这部分所描述的统计项以后可能会有变更。
带有“items”选项的"stats"命令返回每个slab class的item存储信息。返回的数据格式:
STAT items:<slabclass>:<stat> <value>/r/n
服务器使用以下行终止这个列表:
END/r/n
<slabclass>和 "stats slabs"命令使用的类id是一致的。"stats slabs"描述内存的大小和使用率, "stats items"描述更上一层的信息。
以下的项的值被定义为
名称 | 含义 |
number | 当前存储在这个class中的item数.过期的item不会自动排除 |
age | 在LRU中最老的item |
evicted | 不得不从LRU中移除未过期item的次数 |
evicted_nonzero | 过期时间设置不为0,但不得不从LRU中称除该未过期的item的次数 |
evicted_time | 自最后一次清除过期item起所经历的秒数。用这个来判断数据被移除的最近时间 |
outofmemory | slab class为新item分配空间失败的次数。这意味着你运行时带上了-M或者移除操作失败 |
tailrepairs | 自解决slab 引用泄漏问题的次数。如果这个计数器增长很多,请将你的情况告诉开发者 |
reclaimed | 用一个过期的数据条目存储新一个数据条目的次数 |
注意仅仅会显示存在的slab的信息,因此对于空的缓存会返回空集。
Item大小统计
----------------------
警告: 这部分所描述的统计项以后可能会有变更。
带有"sizes"选项的"stats"命令返回缓存中全部数据的一般大小和数目。
警告:这个命令会对缓存上锁!它会遍历每一个数据项并检查其大小。尽管这个操作很快,但如果你有很多数据项,你会阻止其它的请求几秒。
数据的返回格式如下所示:
<size> <count>/r/n
服务器以下面的行来终止列表:
END/r/n
"size"是数据项的大约大小,在32字节内
"count"是在该32字节范围的数据项的数目。
这使我们了解到对于所有的item,是否都有一个相应32字节长度内的slab class。你可以以此来决定如何调整slab增长因子来减少内存开销。例如:如果大部分的item都小于200字节,在小容量的范围中产生更多的class可以使得item在slab中的分配更紧凑。
Slab 统计
------------------
警告: 这部分所描述的统计项以后可能会有变更。
带有"slabs"选项的"stats"命令返回在运行期memcache建立的每一个slab的信息。这包括每个slab的信息和一些总计信息。返回数据的格式为:
STAT <slabclass>:<stat> <value>/r/n
STAT <stat> <value>/r/n
服务器以下面的行终止列表:
END/r/n
名称 | 含义 |
chunk_size | 每一个chunk占用的空间。一个item会使用一个合适大小的chunk来存储 |
chunk_per_page | 一个页中有多少个chunk.一个页的大小默认是小于等于1M。Slab在页中分配,然后分割成chunk |
total_pages | 为该slab class分配的页数 |
total_chunks | 为该slab class分配的chunk数 |
get_hits | 在该class中数据提取请求(命中)的总数 |
cmd_set | 在该class 中数据存储请求的总数 |
delete_hits | 在该class 中成功删除数据的总数 |
incr_hits | 在该class中递增操作的总数 |
decr_hits | 在该class中递减操作的总数 |
cas_hits | 在该class中CAS命令更新的总数 |
cas_badval | 在该class中因不匹配的CAS id而失败而导致更新失败 的CAS命令的总数 |
used_chunks | 有多少个chunk用于分配item |
free_chunks | 还没有用于item分配,或因删除变空闲的chunk数目 |
free_chunks_end | 在最近分配的Page尾部空闲chunk的数目 |
mem_requested | 在该slab 中申请分配的内存字节数 |
active_slabs | slab class分配的总数 |
total_malloced | 分配的slab page内存量 |
Item存储于与大于等于其大小的slab中。mem_request显示在一个slab中所有内存请求的总大小(total_chunks * chunk_size)-mem_requestd 显示该slab浪费的内存数。如果你看到有大量的浪费,考虑调slab 增长因子。
其它命令
----------------
"flush_all" 命令带有一个可选的数目参数。它总是成功,服务器返回"OK/r/n"作为响应(除非在最后一个参数中指定了"noreply")。它的作用是使得所有现存的item立即无效(默认)或者在一个指定的时间后无效。在失效操作后,不会有item作为响应返回给数据提取命令(除非它在flush_all操作后又在相同的key中存储了一次)。flush_all实际上并没有释放item占用的所有的内存,它们会逐渐被用于存储新的item。flush_all最精确的定义如下:它使得所有最后更新时间小于flush_all指定的时间的item在提取操作中被忽略。
带延迟的flush_all的目的在于,当你设置了一个memcache服务池,而且要清除所有内容,你可以选择不要同时重置所有的memcache服务(例如这会使所有客户端原本从memcache中取得的数据,现在要由数据库重新生成而导致数据库负担的突然加重)。
这个延迟选项允许你以10秒的间隔来重置它们(例如,第一个10秒后,第二个20秒后,第三个......)
"version"命令没有参数:
version/r/n
在响应中,服务器返回:
"VERSION <version>/r/n", 其中<version>该服务器的版本字符串。
"verbosity"是一个带一个数目参数的命令。它总是成功,服务器返回"OK/r/n"作为响应(除非在最后一个参数指定了noreply).它的作用是设置日志输出的显示级别。
"quit"命令没有任何参数:
quit/r/n
当收到该命令时, 服务器端关闭连接。但客户端也可以在它不需要该连接时将其简单地关闭,不用发送任何命令。
UDP协议
------------------
对于客户端的数目非常多的情况下,TCP连接数限制了扩展性。也有一个基于UDP的接口。UDP接口不提供可靠传输, 所以只应用于不需要保证成功的操作中;典型的情况是在"get"请求时,一个丢失或不完整的响应会简单地被认为是缓存不命中。
每一个UDP数据报包含一个简单的帧头,后跟与上述TCP协议一样格式的数据。在当前的实现中,请求必须在一个UDP数据报中,但响应可以跨多个数据报(一般仅用于在多个key的"get"和"set"请求中。考虑到可靠性,这两种请求其实还是用TCP传输比较合适)。
帧头是8个字节的长度,如下所示(所有的值都是16位整数,网络字节序,高位在前)
0-1 请求ID
2-3 序列号
4-5 该消息包含的数据报数目
6-7 保留字段,必须为0
请求ID由客户端提供,典型是使用一个基于某个随机数的自增序列,但客户端可以自由决定请求ID。服务器的响应中会包含与请求相同的ID。客户端使用请求ID来区分不同来自同一个服务器的对不同请求的响应。那些带有不能识别的请求ID的响应可能是对之前请求的延迟响应,应该丢弃。
序列号是范围是从0到n-1, 这里的n是该消息的数据报总数。客户端应该将数据报的负载部分以序列号顺序进行连接。合并字节流的结果与TCP协议(包含结束符/r/n)的格式一致。