一、背景
一个计算机应用系统,如果存在对数据的存取,都离不开缓存,因为好的缓存,可以极大的提高应用系统的性能。
memcached就是一个开源的缓存系统,它开始是为网站缓存而开发的,它基于key-value这一通用的数据结构,采用C++开发。
二、事件通知机制
memcached基于libevent,因此在安装时,必须先安装libevent。对libevent,笔者目前未作深入研究,现引用libevent官网上的介绍吧。
“libevent的API提供了这样一种功能:当特定事件(基于文件描述符、信号或通常的时间事件)发生超时后,调用回调函数。
目前,libevent支持/dev/poll, kqueue(2), event ports, POSIX select(2), Windows select(), poll(2)和epoll(4),它内部的事件机制与外部暴露出来功能的是相互独立的,因此可以方便升级。libevent可以用于多线程,能够在Linux, BSD, Mac OS X, Solaris, Windows等平台上编译并运行。
另外,libevent提供了一套复杂的框架,可以支持带缓冲的network IO, sockets, filters, rate-limiting, SSL, zero-copy file transmission, and IOCP。libevent支持DNS, HTTP等"协议以及一个小型化的RPC框架。"
三、存储机制
快速保存,快速取出是缓存最主要的功能,因此缓存好坏的关键是存储机制。
memcached所有的数据都保存在内存中,重启或掉电之后,数据会消失,当数据存储到达限额后,会按照LRU(Least Recently Used)算法自动删除不使用的缓存,因此不要将memcached用于需要持久化存储场合。
我们知道,频繁的分配与释放大小不同的内存,会使内存产生很多碎片,影响内存的后续分配,因此,为提高内存的使用效率,memcached采用slab的方式,分配内存时,为一个slab class分配一个较大的内存块,一个slab中,在划分若个同样大小的chunk,如下图所示。
图中,表示了4个slab,slab class1中chunk的大小是88字节,当有小于88字节的内容需要存储时,存到slab class1中,同样,当有88-112字节的内容要存储时,保存到slab class2中。这样做的好处是减少了内存碎片,当slab class不释放时,可以重复利用已分配的内存,坏处当然是内存的空间利用效率可能不高。
为提高内存的空间利用效率,在系统启动时,可以指定一个叫Growth Factor的参数,该参数将影响各slab class的内存差异,即Growth Factor=class2中chunk的空间/class1中chunk的空间=class3中chunk的空间/class2中chunk的空间=...,说白了,就是各slab class中chunk的增长速度。
四、通讯协议
比较奇怪的是,memcached不使用复杂的协议,而使用一种近乎原始的基于文本行的协议,因此即使用telnet也能对memcached进行操作。在memcached的安装包中,有一个文件"protocol.txt",该文档描述了与memcached交互的协议。
有三种命令:
1.存储命令
存储命令有6个"set", "add", "replace", "append","prepend" ,"cas",它的格式如下
<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]\r\n
以上命令行后,紧接着就是数据块
<data block>\r\n
数据块是可以包含非显示字符,其大小在前面的命令行中指定。
发完数据后,客户端将得到返回值(如果需要),如下:
STORED\r\n :成功
NOT_STORED\r\n:有错误
EXISTS\r\n:已存在(cas)
NOT_FOUND\r\n:未找到(cas)
2.读取命令
读取命令有2个“get" , "gets",命令如下:
get <key>*\r\n
gets <key>*\r\n
其中<key>*表示key可以多个,各key之间用空格隔开。
客户端发出此命令后,服务器端将返回如下格式的数据:
VALUE <key> <flags> <bytes> [<cas unique>]\r\n
<data block>\r\n
其中data block的长度由bytes指定,如果客户端一次读取多个值(即get命令中有多个key),以上的格式也重复多次,服务器端发送完value后,再发送
END\r\n
来表示所有数据发送完毕,读取命令结束。
3.其他命令
其他命令包括删除、自增、状态等,如下表所示。
命令 | 格式 | 意义 |
Deletion | 发起: delete <key> [noreply]\r\n 返回: DELETED\r\n NOT_FOUND\r\n | 删除 |
Increment/Decrement | 发起: incr <key> <value> [noreply]\r\n decr <key> <value> [noreply]\r\n 返回: NOT_FOUND\r\n <value>\r\n | 增加或减少存储的值(该值必须是一个unsigned 64bit integer),该功能可用于提供一个唯一数。 |
Touch | 发起: touch <key> <exptime> [noreply]\r\n 返回: TOUCHED\r\n NOT_FOUND\r\n | 该命令用来更新数据的过期时间(不致数据因长时间未访问而过期)。 |
Slabs Reassign | 发起: slabs reassign <source class> <dest class>\r\n 返回: OK BUSY [message] BADCLASS [message] NOSPARE [message] NOTFULL [message] UNSAFE [message] SAME [message] | 当一个运行的slab实例达到它的限制,用来再分配内存用的。 |
Slabs Automove | 发起: slabs automove <0|1> 返回: | 是否允许后台有一个线程移动slab |
Statistics | 发起格式: stats <args>\r\n 返回格式: STAT <name> <value>\r\n...END\r\n args可以为 settings, items, sizes, slab。 | 状态显示命令,返回服务器的的一些状态数据。 |
Other commands | version\r\n quit\r\n | |
五、客户端
memcached实际上是一个服务器端程序,它本身没有提供客户端接口,因此如果应用memcached,我们还需要找一个客户端程序。从上面的协议可以看出,自己开发一个客户端并不特别困难,当然,也可以利用开源的,一个比较有名的是libmemcached,关于libmemcached的使用,可以另写一篇文章了,在此略去。
六、分布式应用
memcached宣称是一个分布式系统,其实是一种伪分布式,因为各节点既不是对等关系,也不是主从关系,它们之间并没用通讯,所以不能算一个严格的分布式系统。它的分布式是由客户端来体现的,即上面介绍的两段式Hash中的第一段,如果各客户端的算法都将某个key固定映射到某台机器上,就认为该机器缓存了该key的内容,这样,各key会映射到若干机器上,memcached的分布式就是如此体现的。
如此的分布应用,马上就会出现一个问题:增加或删掉一个节点(机器)怎么办?的确,对memcached来说,它的需求是:
1.不能保证每次都能得到缓存值(第一次当然不能保证,需要从真正的数据源中取,随后的第n次也不能保证);
2.客户端的Hash算法是特殊的。
参考文章中介绍了一种hash算法,可以有效地解决Hash算法的问题。在libmemcached客户端中,提供了consistent hashing选项,来保证当节点增加或失效时,cache的命中率不致下降太多。
附:
memcached的启动与检测命令
- 启动memcached举例:/usr/local/memcached/bin/memcached -d -c 10240 -m 1024 -u root
- 获取运行状态:echo stats | nc localhost 11211(可以查看出pid) 或使用ps -ef|grep memcached
- 停止memcached:kill -9 pid (-9表示强制杀死,pid 为进程的进程标识符)
- 可用telnet来连接测试是否安装启动成功。
- 运行stats: 查询memcached状态
主要启动选项:
-d 选项是启动一个守护进程,
-m 是分配给Memcache使用的内存数量,单位是MB,这里是1024MB,默认是64MB
-u 是运行Memcache的用户,这里是root
-l 是监听的服务器IP地址,默认应该是本机
-p 是设置Memcache监听的端口,默认是11211,最好是1024以上的端口
-c 选项是最大运行的并发连接数,默认是1024,这里设置了10240,按照你服务器的负载量来设定
-P 是设置保存Memcache的pid文件位置
-h 打印帮助信息
-v 输出警告和错误信息
-vv 打印客户端的请求和返回信息