Memcached内部协议解析
2017-12-25 13:52:20 阿炯


Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap,其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。但是它并不提供冗余(例如,复制其hashmap条目);当某个服务器停止运行或崩溃了,所有存放在该实例上的键/值对都将丢失。

Memcached由Danga Interactive开发,作者为Anatoly Vorobey和Brad Fitzpatrick,用于提升LiveJournal.com访问速度的。LJ每秒动态页面访问量几千次,用户700万,Memcached将数据库负载大幅度降低,更好的分配资源,更快速访问。Memcached的并发编程模型是通过libevent库来提供的。可以在不损失高并发性能的情况下,移植到Linux,*BSD,Mac OS X,Solaris和Windows系统上,Memcached通过在内存中维护一个大型的Hash链表来实现高性能的内存对象缓存系统。

Memcached采用的是单进程多线程的编程模型。通常来讲,可开启的线程数量受制于主机的CPU核数。由于Memcached是基于libevent而开发的,那么Memcached内部的每一任务的处理都是基于事件(event)的。并且为了得到较高的处理性能,Memcached又在event之上增加了“单事件,多处理”的机制。也就是在单次事件中,可以处理多个客户请求。类似于Linux内核驱动中的数据接收与发送处理模型中的NAPI(New Application Programming Interface)的工作原理,这样可以获得很大的性能提升。
    
Memcached内部协议解析(ascii_prot)

Memcached的客户端和服务器之间通过TCP连接进行通信(UDP方式也是可以的,详细信息见本文最后的"UDP protocol"解析)。运行中的memcached服务器监听在一些(可配置的)端口上;客户端通过连接到该端口,可以向服务器发送命令,读取应答,最后在关闭连接。

Memcached服务器不必发送任何命令来结束会话,这个工作仅仅由客户端来执行,当它不再需要该连接的时候。注意,尽管如此,我们也是鼓励客户端软件缓存他们的连接的,而不建议对每一次的数据存、取都重新开启一个新的连接。这是因为memcached是专门为高效地处理大量(数百个,甚至在需要的时候会达到上千个)的并发连接而设计的。缓存连接将会消除建立TCP连接相关的开销(与服务器端准备一个新的连接时的开销相比,这是微不足道的)。

在memcache协议中有两种方式可以传送数据:文本行(text lines)和非结构化数据(unstructured data)。text lines用于在客户端和服务器之间传送命令(commands)和响应(responses)数据。Unstructured data将会被发送,当客户端想要存储或是索取数据的时候。服务器将向客户端传回和它从客户端收到的完全相同格式的非结构化数据(unstructured data),并且一字节流的方式传送。服务器并不关心unstructured data的字节序问题,并且也意识不到它们。对unstructured data中可以出现的字符种类没有任何限制;然而,对于读取该数据的一端(或者是客户端或者是服务器)将通过前面处理的文本行总是能知道待提交或接收的数据块的精确长度。

Text lines总是以"\r\n"作为数据块结束符号的。Unstructured data也以"\r\n"作为数据块结束符,即使在数据中出现了'\r'、'\n'或是其他的任何8-bit字符,同样也以它作为结束符。因此,当客户端从服务器索要数据时,它必须使用数据块的长度来定位数据块的结束位置,而不能依靠数据块后面跟随的'\r\n'。即使它确实是这么做了。

1. 键(keys)

存储在memcached中的数据是借助key的帮助而加以区分的。键(key)是一个文本串,它将用来唯一的确定客户端想要存储和索取的数据。目前,key的长度限制在250个字符之内(当然,正常来讲,客户端是不需要这么长的key的);key中是不允许包含控制字符或是空白(e.g. '\t'、'\n'、' ')字符的。

2. 命令(commands)

有三种类型的命令:存储命令、检索命令、其他命令(它们不涉及unstructured data)。

存储命令有六个:"set","add","replace","append","prepend","cas"。它们用来告诉服务器存储一些用key唯一标识的数据。客户端首先发送一个命令行,然后是一个数据块;之后客户端即可等待服务器的响应数据,用于查看操作成功(success)还是失败(failure)。

检索命令有两个:"get"和"gets"。它们要求服务器检索与一些key(在一个request中,可以有一个或多个key)相关联的数据。客户端发送一个包含一个或多个key的命令行到服务器,然后服务器把查找到的每一个项目(item),作为一个响应行发送到客户端;其中包含了该项目(item)的一些信息,而后是该项目的数据块内容;这种过程一直持续到服务器向客户端发送了一个"END"的响应时为止。

对于其他所有的命令,它们都不涉及到unstructured data。客户端发送一个命令行,然后等待(依赖于命令)一个或是多个响应(以最后一行的"END"作为结束)。

一个命令行总是以这个命令的名字作为开始,后面跟着一系列由空格分割的参数列表。命令的名字是小写的,并且是大小写敏感的。

3. 到期时间(Expiration times)

一些命令会涉及到客户端发送到服务器的各种到期时间(相对于一个项目-itme-或是一种操作)。在这种情况下,到期时间的实际值或者是Unix时间(Unix time)(一个32位数,起始于1970年1月1号的秒数)或者起始于现在的秒数。在后面的一种情况中,这个数字不能超过60*60*24*30(一个月(30天)内的秒数)。如果客户端发送的到期时间超过了这个数值的话,那么服务器将认为它是Unix时间,而不是起始于现在的时间。

4. 错误描述字符串(Error strings)

服务器对客户端发送过来的每一个命令可能会回应一个错误描述字符串。错误描述字符串有一下三种类型:
"ERROR\r\n"
意味着客户端发送过来了一个不存在的命令名字

"CLIENT_ERROR \r\n"
意味着在客户端的输入中有一些错误,i.e. 输入以某种方式不符合协议。是一个人可以读取的错误描述字符串

"SERVER_ERROR "
意味着服务器发生了一些阻止命令执行的错误。是一个友好的可以读取的错误描述字符串。

一旦服务器发生了错误,那么它将不能继续为客户端提供服务,服务器将会在发送完该错误信息后主动关闭该连接。这也是服务器主动关闭一个客户端连接的唯一情况。在下面对个别命令的详细描述过程中,将不再对错误输出行进行详细的解释,但是客户端必须有允许它们的可能性。

5. 存储命令(storage commands)

首先,客户端发送的命令行应该具有如下格式:
[noreply]\r\n
cas [noreply]\r\n

可以是"set","add","replace","append","prepend"
 "set"是指存储该数据
 "add"是指仅当服务器没有存储与该key相关的数据时,才存储该数据
 "replace"是指仅当服务器已经存有与该key相关的数据时,才存储该数据
 "append"是指在已经存在的与该key相关的数据后面追加数据
 "prepend"是指在已经存在的与该key相关的数据前面追加数据

 "append"和"prepend"命令不接受或者参数。它们更新已经存在的数据部分,并且忽略新的flag和exptime设置

 "cas"是一个检查和设置操作命令,是指仅当自从上次取回数据之后再没有任何数据更新的时候,存储该数据是指与客户端要求存储数据相对应的key

是一个任意的16-bit的无符号整数(以十进制传送)。它与数据一同被存储在服务器端,并且当客户端检索数据时,在同数据一起被传回。客户端可能会将该数值作为一个位域存储特殊数据信息;这个域对服务器来讲是不透明的。需要注意的是,在memcached-1.2.1或更高版本中,flags可能是一个32-bits的整形数,但是你必须要能够严格地处理它,以便能和旧版本中的16-bits相兼容

是指到期时间。如果它是0,那么对应的item将永远不会过期(即使在为其他item准备空间时,它有可能被删除)。如果它是非0值(或者是Unix时间或者是从现在开始起的偏移时间),那么服务器将能保证在到期时间(以服务器时间作为度量标准)到达后,客户端将不能检索这些项目(item)。

只是后面数据块的大小,单位是字节,但是它不包含'\r\n'分割符。当后面跟随的是一个空数据块的时候,大小可以是0

是指对于一个已经存在的entry的唯一的一个64-bits数值。"gets"命令可以返回这个值,并且客户端可以使用这个值进行"cas"命令对数据的更新

"noreply"是一个可选参数,用于通知服务器不必对刚才的命令作出回应。注意:如果请求行格式不正确,那么服务器将不能可靠地解析"noreply"选项。在这种情况下,服务器可能会发送一个错误到客户端,并且将不再读取客户端的任何数据。客户端应该仅仅构建有效的请求

跟随在上面一行数据之后的将是下面的数据块:
\r\n

是指长度为字节,可以包含任何8-bits的数据chunk(块)

在发送了以上命令和数据块之后,客户端将等待如下格式的应答数据:
 "STORED\r\n",说明操作已成功
 "NOT_STORED\r\n",说明数据没有被存储,但不是因为出现了错误。这种情况通常是指"add"或"replace"命令没有遇到合适的条件。
 "EXISTS\r\n",说明你使用"cas"命令试图存储的item,在你上次存取之后已经被修改过了
 "NOT_FOUND\r\n",说明你使用"cas"命令试图存储的item不存在。

6. 检索命令(Retrieval command)

检索命令"get"和"gets"操作格式如下:
get *\r\n
gets *\r\n

 *是指一个或多个由空格分割的key串

该命令意味着客户端希望接受0个或是多个items,每一个item将作为文本行格式接受,并且后跟一个数据块。当所有items都传送完毕后,服务器将单独发送一个
"END\r\n"
作为响应的结束。

从服务器响应的每一个item,其格式如下所示:
VALUE []\r\n
\r\n

 是与将要被发送的item相关联的键
 是被存储命令设置的flags值
 是指后面跟随的数据块的长度,但不包括结束符\r\n
 是一个用于确定一个特定item的64-bits的唯一整数
 是该item的数据

如果客户端检索请求中的一些keys没有被服务器传回的话,那么说明没有存储与这些key相关联的item(因为他们可能从来没被存储或是存储了但是在为其他的items准备空间时而被删除了或是超时了或者是被一个客户端确定地删除了)。

7. 删除命令(Deletion)

"deletion"允许明确地删除一些items,格式如下:
delete [noreply]\r\n

是指客户端想要服务器删除项目(item)的键
 "noreply"是可选参数,用于通知服务器不需要对此作出回复。

服务器对此命令的应答行具有如下形式:
 "DELETED\r\n"用于指示删除成功
 "NOT_FOUND\r\n"用于指示没有找到与此key相关的item

若想了解如何立刻使已经存在的所有项目失效,请看"flush_all"命令。

8. 递增(Increment)/递减(Decrement)操作

"incr"和"decr"命令用于就地改变一些item的数据(data),增减或减少它们。这个item的数据(data)是一个十进制表示的64-bit无符号整数。如果当前数据不符合这种表示,那么incr/decr命令将返回一个错误(如果它是0,memcached <= 1.2.6把它作为虚假值来处理)。另外,incr/decr命令要能成功的工作,那么他们所操作的item必须已经存在。这些命令不会把不存在的key当作存在的0来处理;相反,他们将宣告失败。

命令格式如下,他们由客户端发送:
incr [noreply]\r\n
or
decr [noreply]\r\n

客户端想要改变项目的关联键
它是客户端要为某一个item增加或减少的数量
 "noreply"是可选参数,用于通知服务器不必为该操作回复消息。

服务器将对该命令回复如下的数据:
 "NOT_FOUND"用于指示拥有该value值的item没有找到
 \r\n 其中,是incr或decr命令被成功执行后的item数据的新value值

注意,当客户端试图减小该数值到0一下的时候,decr命令将发生下溢出,此时这个新value值将是0。在incr命令中发生上溢时,将用64-bit的掩码将其调整到合法的数据范围之内。

另外,还要注意当增加一个数值而导致长度丢失的时候,将不能保证增加它返回的长度。这个数值可能在最后填充空白,但这纯粹是实施优化,所以你不你能依赖于它。

9. 统计命令(statistics)

"stats"命令用于向服务器请求有关它维护的和系统内部其他的数据。其格式有两种形式:无参数和有参数的。

无参数格式:
stats\r\n
它将导致服务器会传一般目的的统计信息和设置,具体件下文。

有参数格式:
stats \r\n
依赖于这些参数,各种各样的系统内部数据将被回传回来。参数的类型和被发送的数据将不在这个版本的协议里进行阐述,它受制于memcached开发者所作的改变。

10. 通用目的的状态统计

依据接收到的无参数的"stats"命令,服务器将会回传数行如下格式的数据:
STAT \r\n

服务器将以 END\r\n 作为数据传送结束标志。

在每一行的统计信息中,待统计数据的名称,是其数据。下面的图列表(英文的)中的每一项都是"stats"命令将要返回的数据信息。其中,"32u"表示32-bits无符号整数,"64u"表示64-bits无符号整数,"32u.32u"表示两个被'.'隔开的32-bits无符号整数,它代表了一个浮点数。

图列表 暂略

11. 设置统计信息(Setting statistics)

这节所描述的统计信息,将来有可能会发生变化,请以源码包中的protocol.txt中的为准。带有参数的"stats"设置命令,将会返回设置的详细信息。

暂略。

12. 项目统计命令(Item statistics)

这节所描述的统计信息,将来有可能会发生变化,请以源码包中的protocol.txt中的为准。带有参数"item"的"stats"命令,将返回每个slab class中item的容量信息。数据的格式如下:
STAT item:: \r\n

服务器将以 END\r\n 结束上述数据的传送。

下面的不知该如何解读,特附原文如下:
The slabclass aligns with class ids used by the "stats slabs" command. Where "stats slabs" describes size and memory usage, "stats items" shows higher level information.

13. 其他命令(Other commands)

"flush_all"是一个可选的数字参数的命令。它总是执行成功,并且服务器会返回一个"OK\r\n"作为应答(除非提供了最后一个参数"noreply")。它的作用是立即使已经存在的所有items失效(默认方式),或者是在一定的超时时间之后失效。项目失效之后,服务器将不会为客户端的检索命令(除非又在原来失效的键值的地方存放了新的数据)返回任何数据。flush_all命令实际上并不会释放被已经存在的items占用的内存空间。对flush_all命令最确切的定义如下:
It causes all items whose update time is earlier than the time at which flush_all was set to be executed to ignored for retrieval purposes.

为"flush_all"命令设置延时的目的是在你有一个memcached服务器池,并且你需要刷新所有内容,那么这就允许你可以不再同一时刻(这样的话,可能会导致一个所有客户端突然需要重新创建内容(或许这些内容在memcached守护进程中也能被找到)而使数据库出现一个负载高峰)重置所有memcached服务器的选项。

延迟选项允许你在重置项目时可以有一定的时间间隔,比如10秒(可以通过第一次传递一个0,第二次传递一个10,第三次传递一个20,等等来实现)。

"version"是一个无参命令,格式如下:
version\r\n

服务器对其的回应格式如下:
"VERSION \r\n",其中是一个用于描述memcached服务器版本的字符串。

"verbosity"是一个带有数字化参数的命令。它总是执行成功,并且服务器通过发送"OK\r\n"作为应答(除非"noreply"作为最后一个参数被提供)。它的作用就是用来设置输出日志的等级水平的。

"quit"也是一个无参数的命令,格式如下:
quit\r\n

当收到该命令时,服务器将关闭连接。但是,当一个连接不再需要的时候,客户端会简单的将其关闭,而不会发送任何命令。

14. UDP协议(UDP protocol)

对于安装了足够多数量的客户端,并且会产生超大量的TCP连接的环境,memcached就会出现扩展困难的问题,所以这里也提供了一种基于UDP的接口。UDP接口不保证成功交付,因此因该被使用于不要求成功的操作中;通常把一个丢失了的或是不完整响应的"get"命令当作缓存不命中来对待。

每一个UDP数据报包含一个简单的桢头,后面跟随着格式如同上面对TCP协议描述时相同的数据。在当前的实现中,请求必须被包含在一个简单的UDP数据报中,但是响应可以分散在多个数据报中。(唯一相同的是包含多个键值的"get"和"set"命令的跨越多个数据报文的请求,无论如何,它们都是适合使用TCP可靠传输的原因)

桢头长8个字节,如下(所有值都是16-bits的网络序整数,高字节优先):
0-1 请求ID
2-3 序列号
4-5 在这个消息中的数据报总数
6-7 留作以后使用,值必须是0

请求ID是由客户端提供的。通常它从一个随机的种子数值开始单调递增,但是客户端可以随意使用它喜欢的任何数值作为请求ID。服务器的响应将会包含与收到的请求中的相同的ID。当有来自同一个服务器的多个悬而未决的数据报时,客户端可以使用该请求ID去区分响应和请求;任何带有不可识别请求ID的数据报将都可能是对早些时候请求的延迟响应,那么此时应该将其丢弃。

序列号的范围是从0到n-1,这里的n是该消息中的数据报总数。客户端需要使用序列号将响应中的数据报负载串联起来;重组后得到的数据和使用TCP传输来的数据具有相同的格式(包括结束序列\r\n)。

<完整版 - 结束>

Memcached内部协议解析(binary_prot)

本文源自:cukdd