分布式K/V存储方案-Memcached
2010-08-14 23:29:32 阿炯

在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载。如果说缓存是解决这个问题的好办法,那么此时Memcached或许是你想要的。它是用C语言开发并以BSD协议授权。memcached是一个高性能的内存缓存对象系统,其实质为一个键值对的hashmap索引,其事件处理和网络通信均是基于libevent。memcached通常作为C/S模型中的S,也就是服务器端,客户端通过命令来操作缓存数据。


Memcached是什么
在阐述这个问题之前,我们首先要清楚它“不是什么”。很多人把它当作和SharedMemory那种形式的存储载体来使用,虽然memcached 使用了同样的“Key=>Value”方式组织数据,但是它和共享内存、APC等本地缓存有非常大的区别。Memcached是分布式的,也就是说它不是本地的。它基于网络连接(当然它也可以使用localhost)方式完成服务,本身它是一个独立于应用的程序或守护进程(Daemon方式)。

Memcached 使用libevent库实现网络连接服务,理论上可以处理无限多的连接,但是它和Apache不同,它更多的时候是面向稳定的持续连接的,所以它实际的并发能力是有限制的。在保守情况下memcached的最大同时连接数为200,这和Linux线程能力有关系,这个数值是可以调整的。Memcached内存使用方式也和APC不同,APC是基于共享内存和MMAP的,memcachd有自己的内存分配算法和管理方式,它和共享内存没有关系,也没有共享内存的限制;通常情况下,每个memcached进程可以管理2GB的内存空间,如果需要更多的空间,可以增加进程数。

Memcached能缓存什么
通过在内存里维护一个统一的巨大的hash表,Memcached能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。

Memcached快么
非常快。Memcached使用了libevent(如果可以的话,在linux下使用epoll)来均衡任何数量的打开链接,使用非阻塞的网络I/O,对内部对象实现引用计数(因此,针对多样的客户端,对象可以处在多样的状态),使用自己的页块分配器和哈希表,因此虚拟内存不会产生碎片并且虚拟内存分配的时间复杂度可以保证为O(1)。

Danga Interactive为提升Danga Interactive的速度研发了Memcached。目前LiveJournal.com每天已经在向一百万用户提供多达两千万次的页面访问,而这些是由一个由web服务器和数据库服务器组成的集群完成的。Memcached几乎完全放弃了任何数据都从数据库读取的方式,同时它还缩短了用户查看页面的速度、更好的资源分配方式,以及Memcache失效时对数据库的访问速度。

Memcached的特点
Memcached的缓存是一种分布式的,可以让不同主机上的多个用户同时访问, 因此解决了共享内存只能单机应用的局限,更不会出现使用数据库做类似事情的时候,磁盘开销和阻塞的发生。
协议简单:使用基于文本行的协议,二进制协议使用比较少。
基于内存存储:数据存储在内存中,所以读取速度很快。
事件处理:基于libevent开发,所以可以应对C10问题。
不互相通信的分布式:多台memcached服务器之间不互相通信,由客户端实现分布式算法,所以通常客户端使用一致性hash策略,通常拥有快隔离,慢恢复的特性。


使用Memcached进行内存缓存
通常的网页缓存方式有动态缓存和静态缓存等几种,在ASP.NET中已经可以实现对页面局部进行缓存,而使用memcached的缓存比 ASP.NET的局部缓存更加灵活,可以缓存任意的对象,不管是否在页面上输出。而memcached最大的优点是可以分布式的部署,这对于大规模应用来 说也是必不可少的要求。LiveJournal.com使用了memcached在前端进行缓存,取得了良好的效果,而像wikipedia,sourceforge等也采用了或即将采用memcached作为缓存工具,memcached可以大规模网站应用发挥巨大的作用。


如何使用memcached server
在服务端运行:
# ./memcached -d -m 2048 -l 10.0.0.40 -p 11211

这将会启动一个占用2G内存的进程,并打开11211端口用于接收请求。由于32位系统只能处理4G内存的寻址,所以在大于4G内存使用PAE的32位服务器上可以运行2-3个进程,并在不同端口进行监听。

启动:
简单的启动命令如下,设置-l和-p分别用来设置ip和监听的端口。-vv是输出一些运行信息。
$memcached -l yourIP -p 1888 -vv

-vv指示输出更多的调试信息。


为什么不使用共享内存
最初的缓存做法是在线程内对对象进行缓存,但这样进程间就无法共享缓存,命中率非常低,导致缓存效率极低。后来出现了共享内存的缓存,多个进程或者线程共享同一块缓存,但毕竟还是只能局限在一台机器上,多台机器做相同的缓存同样是一种资源的浪费,而且命中率也比较低。Memcached Server和Clients共同工作,实现跨服务器分布式的全局的缓存。并且可以与Web Server共同工作,Web Server对CPU要求高,对内存要求低,Memcached Server对CPU要求低,对内存要求高,可以搭配使用。


---------------------------------------------------------------
memcached 是一套分布式的快取系统,当初是 Danga Interactive 为了 LiveJournal所发展的,但目前被许多软件(如MediaWiki)所使用。memcached 的客户端使用TCP链接与服务器通讯(UDP接口也同样有效,参考后文的“UDP协议”)。一个运行中的memcached服务器监视一些(可设置)端口。客户端连接这些端口,发送命令到服务器,读取回应,最后关闭连接,结束会话不需要发送任何命令。当不再需memcached服务时,要客户端可以在任何时候关闭连接。需要注意的是,鼓励客户端缓存这些连接,而不是每次需要存取数据时都重新打开连接。这是因为memcached 被特意设计成及时开启很多连接也能够高效的工作(数百个、上千个如果需要的话)。缓存这些连接,可以消除建立连接所带来的开销(/*/相对而言,在服务器端建立一个新连接的准备工作所带来的开销,可以忽略不计)。

在memcache协议中发送的数据分两种:'文本行' 和 '自由数据',文本行被用于来自客户端的命令和服务器的回应,自由数据用于客户端从服务器端存取数据时。同样服务器会以字节流的方式传回自由数据,/* /服务器不用关心自由数据的字节顺序。自由数据的特征没有任何限制,但是通过前文提到的文本行,这项数据的接受者(服务器或客户端),便能够精确地获知所发送的数据库的长度。

memcached 缺乏认证以及安全管制,这代表应该将 memcached 服务器放置在防火墙后。它的 API 使用三十二位元的循环冗余校验(CRC-32)计算键值后,将资料分散在不同的机器上。当表格满了以后,接下来新增的资料会以 LRU 机制替换掉。由于 memcached 通常只是当作快取系统使用,所以使用 memcached 的应用程式在写回较慢的系统时(像是后端的数据库)需要额外的程式码更新 memcached 内的资料,memcached 具有多种语言的客户端开发包,包括:Perl/PHP/JAVA/C/Python/Ruby/C#/MySQL/

Memcached适合什么场合
在很多时候,被滥用了,这当然少不了对它的抱怨。我经常在论坛上看见有人发贴,类似于“如何提高效率”,回复是“用memcached”,至于怎么用,用在哪里,用来干什么一句没有。memcached不是万能的,它也不是适用在所有场合。

Memcached 是“分布式”的内存对象缓存系统,那么就是说,那些不需要“分布”的,不需要共享的,或者干脆规模小到只有一台服务器的应用,memcached不会带来任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源,即使是UNIX本地连接也一样。在我之前的测试数据中显示,memcached本地读写速度要比直接PHP内存数组慢几十倍,而APC、共享内存方式都和直接数组差不多。可见如果只是本地级缓存,使用memcached是非常不划算的。

Memcached 在很多时候都是作为数据库前端cache使用的,因为它比数据库少了很多 SQL解析、磁盘操作等开销;而且它是使用内存来管理数据的,所以它可以提供比直接读取数据库更好的性能,在大型系统中,访问同样的数据是很频繁的,memcached可以大大降低数据库压力,使系统执行效率提升。另外,memcached也经常作为服务器之间数据共享的存储媒介,例如在SSO系统中保存系统单点登陆状态的数据就可以保存在memcached 中,被多个应用共享。

需要注意的是,memcached使用内存管理数据,所以它是易失的,当服务器重启,或者memcached进程中止,数据便会丢失,所以 memcached不能用来持久保存数据。很多人的错误理解,它的性能非常好,好到了内存和硬盘的对比程度,其实memcached使用内存并不会得到成百上千的读写速度提高,它的实际瓶颈在于网络连接,它和使用磁盘的数据库系统相比,好处在于它本身非常“轻”,因为没有过多的开销和直接的读写方式,它可以轻松应付非常大的数据交换量,所以经常会出现两条千兆网络带宽都满负荷了,memcached进程本身并不占用多少CPU资源的情况。

Memcached的内存管理方式
Memcached有一个很有特色的内存管理方式,为了提高效率,它使用预申请和分组的方式管理内存空间,而并不是每次需要写入数据的时候去malloc,删除数据的时候free一个指针,Memcached使用slab->chunk的组织方式管理内存。

Memcached启动命令行参数详解

"A":是否运行客户端使用shutdown命令,默认是不允许的。该选项将允许。客户端的shutdown命令会将memcached进程杀死。该选项会将settings.shutdown_command赋值为true。
"a":unix socket的权限位信息(访问掩码)。该选项的参数赋值给settings.access。
"U:":大写U。memcached监听的UDP端口值,默认端口为11211。该选项的参数赋值给settings.udpport。
"p:":小写p,memcached监听的tcp端口。默认端口为11211, 该选项的参数赋值给settings.port。
"s:":小写s。unix socket监听的socket路径。该选项的参数赋值给settings.socketpath。
"m:":小写m。memcached能够使用的最大内存值,默认是64MB。参数单位为MB。该参数赋值给settings.maxbytes。
"M":大写M。默认情况下,当memcached的内存使用完后,将进行LRU机制淘汰item以腾出空间。如果使用本选项那么将关闭LRU功能。当然关闭LRU不代表不能存储新数据。如果memcached里面存有过期失效的item,那么就可以存储新数据。否则将无法存储。该选项将settings.evict_to_free赋值为0。
"c:":小写c。每个线程最多允许多少个客户端同时在线(这个值不等价于listen函数的第二个参数),该选项和后面的b选项有所不同。 默认值为1024个。该选项参数赋值给settings.maxconns。
"h":显示帮助信息。
"i":显示memcached和libevent的版权信息。
"k":小写k。将memcached使用到的内存锁定在内存中,不准OS把memcached的内存移动到虚拟内存。因为当OS把memcached的内存移动到虚拟内存可能会导致页错误,降低memcached的响应时间。
"v":小写v。输出memcached运行时的一些信息。-v -vv -vvv输出的信息依次增加。该选项会增加settings.verbose的值。
"l:":小写L。memcached绑定的ip地址。如果不设置这个选项,那么memcached将使用INADDR_ANY。如果想指定多个IP地址,那么该选项的参数可以由多个ip组成,ip之间用逗号分隔。也可以多次使用这个选项,此时端口应该尾随ip而不是单独用-p选项指定。例如-l 127.0.0.1:8888,192.168.1.112:9999 或者 -l 127.0.0.1:8888 -l 192.168.1.112:9999该选项参数将赋值给settings.inter。
"d":以守护进程的形式运行memcached。
"r":将core文件大小设置为不受限制。
"R:":worker线程连续为某个客户端执行命令的最大命令数。该选项的参数赋值给settings.reqs_per_event。
"u:":小写u。当以root用户启动memcached的时候需要指定memcached的所属用户,其他用户启动memcached不需要此选项。
"P:":大写p。该选项的参数指明memcached的pid保存文件。要和-d选项配合使用。注意运行的用户是否有权限写对应的文件。
"f:":item的扩容因子。默认值为1.25。该选项的参数值可以是小数但必须大于1.0。该选项参数将赋值给settings.factor。
"n:":设置最小的item(key+value+flags)能存储多少字节的数据。该选项参数赋值给settings.chunk_size。
"t:":该选项的参数用于指定worker线程的个数,不建议超过64个。如果不设置该选项默认有4个线程。该参数会赋值给settings.num_threads。
"D:":参数字符作为前缀和ID的分隔符。使用了该选项才会自动收集状态信息。也可以在启动memcached后,客户端使用stats detail on命令开启,此时默认的分隔符为冒号":"。该选项参数会赋值为settings.prefix_delimiter,并将settings.detail_enabled赋值为1。
"L":如果OS允许的话,那么向OS申请更大的内存页。OS的默认内存页为4KB。大的内存页可以有效降低页表的大小,提高效率。此选项会使得memcached预先先OS全部所需的申请内存。当然这些内存尽量是用大内存页分配的。
"C:" :大写C。memcached默认是使用CAS的,本选项是禁用CAS。本选项会将settings.use_cas赋值为false。
"b:":listen函数的第二个参数。该选项的参数赋值给settings.backlog。如果不设置该选项,那么默认为1024。该选项和前面的c选项有所不同。
"B:":memcached支持文本协议和二进制协议。该选项的参数用于指定使用的协议。默认情况下是根据客户端的命令而自动判断(也叫协商),参数只能取auto、binary、ascii这三个字符串值。将参数将赋值给settings.binding_protocol。
"I:":大写i。slab分配器中,每一个页的大小。这个选项的参数是一个数值表示页的大小。默认单位是B也可以在数值后面带K或者M(大小写都行),表示KB和MB。页的大小小于1KB或者大于128MB都是不允许的。不推荐使用该选项。本选项参数会赋值给settings.item_size_max。
"S":大写S。打开sasl安全协议。会将settings.sasl赋值为true。
"F":禁止客户端的flush_all命令。默认是允许客户端的flush_all命令的。该选项将settings.flush_enabled赋值为false。
"o:":小写o。启动扩展选项,有下面几个子选项可以设置。这个选项是用来优化的。

程序内置的的选项
maxconns_fast:如果连接数超过了最大同时在线数(由-c选项指定),立即关闭新连接上的客户端。该选项将settings.maxconns_fast赋值为true。
hashpower:哈希表的长度是2^n。可以通过选项hashpower设置指数n的初始值。如果不设置将取默认值16。该选项必须有参数,参数取值范围只能为[12, 64]。本选项参数值赋值给settings.hashpower_init。
slab_reassign: 该选项没有参数。用于调节不同类型的item所占的内存。不同类型是指大小不同。某一类item已经很少使用了,但仍占用着内存。可以通过开启slab_reassign调度内存,减少这一类item的内存。如果使用了本选项,settings.slab_reassign赋值为true。
slab_automove:依赖于slab_reassign。用于主动检测是否需要进行内存调度。该选项的参数是可选的。参数的取值范围只能为0、1、2。参数2是不建议的。本选项参数赋值给settings.slab_automove。如果本选项没有参数,那么settings.slab_automove赋值为1。
hash_algorithm:用于指定哈希算法。该选项必须带有参数。并且参数只能是字符串jenkins或者murmur3。
tail_repair_time:用于检测是否有item被已死线程所引用。一般不会出现这种情况,所以默认不开启这种检测。如果需要开启这种检测,那么需要使用本选项。本选项需要一个参数,参数值必须不小于10。该参数赋值给settings.tail_repair_time。
lru_crawler:本选项用于启动LRU爬虫线程。该选项不需要参数。本选项会导致settings.lru_crawler赋值为true。
lru_crawler_sleep:LRU爬虫线程工作时的休眠间隔。本选项需要一个参数作为休眠时间,单位为微秒,取值范围是[0, 1000000]。该参数赋值给settings.lru_crawler_sleep。
lru_crawler_tocrawl:LRU爬虫检查每条LRU队列中的多少个item。该选项带有一个参数。参数会赋值给settings.lru_crawler_tocrawl。


---------------------------------------------------------------
Memcached FAQ

o memcached是怎么工作的?
o memcached最大的优势是什么?
o memcached和MySQL的query cache相比,有什么优缺点?
o memcached和服务器的local cache(比如PHP的APC、mmap文件等)相比,有什么优缺点?
o memcached的cache机制是怎样的?
o memcached如何实现冗余机制?       
o memcached如何处理容错的?
o 如何将memcached中item批量导入导出?
o 但是我确实需要把memcached中的item都dump出来,确实需要把数据load到memcached中,怎么办?
o memcached是如何做身份验证的?
o 如何使用memcached的多线程是什么?如何使用它们?
o memcached能接受的key的最大长度是多少?(250bytes)
o memcached对item的过期时间有什么限制?(为什么有30天的限制?)
o memcached最大能存储多大的单个item?(1M byte)
o 为什么单个item的大小被限制在1M byte之内?
o 为了让memcached更有效地使用服务器的内存,可以在各个服务器上配置大小不等的缓存空间吗?
o 什么是binary协议?它值得关注吗?
o memcached是如何分配内存的?为什么不用malloc/free!?究竟为什么使用slab呢?
o memcached能保证数据存储的原子性吗?

MEMCACHE 性能优化

Memcache是什么?
Memcache是一个自由和开放源代码、高性能、分配的内存对象缓存系统。用于加速动态web应用程序,减轻数据库负载。它可以应对任意多个连接,使用非阻塞的网络IO。由于它的工作机制是在内存中开辟一块空间,然后建立一个HashTable,Memcached自管理这 些HashTable。

Memcached又是什么?
Memcached是Memcache系统的主程序文件,以守护程序方式运行于一个或多个服务器中,随时接受客户端的连接操作,使用共享内存存取数据。那PHP中的Memcache是什么?php中的所讲的memcache是用于连接Memecached的php支持扩展之一(可用phpinfo查看),类似mbstring,eAccelerator。

简单的说
Memcache是总的缓存系统项目名称,容易和PHP中的Memcache混淆。我们常提到Memcache其实是PHP中的Memcache,即PHP的Memcached扩展支持。我们常提到Memcached是服务端主程序文件,服务端安装程序。为了让你的程序飞起来,必须安装memcached服务端程序和PHP的Memcached扩展,所以如果您要使用Memcache来缓存系统,memcache和memcached两样我们都需要安装。

memcached是怎么工作的?
Memcached的神奇来自两阶段哈希(two-stage hash)。Memcached就像一个巨大的、存储了很多<key,value>对的哈希表。通过key,可以存储或查询任意的数据。

客户端可以把数据存储在多台memcached上。当查询数据时,客户端首先参考节点列表计算出key的哈希值(阶段一哈希),进而选中一个节点;客户端将请求发送给选中的节点,然后memcached节点通过一个内部的哈希算法(阶段二哈希),查找真正的数据 (item)。

举个列子,假设有3个客户端1, 2, 3,3台memcached A, B, C:
Client 1想把数据”barbaz”以key “foo”存储。Client 1首先参考节点列表(A, B, C),计算key “foo”的哈希值,假设memcached B被选中。接着,Client 1直接connect到memcached B,通过key “foo”把数据”barbaz”存储进去。Client 2使用与Client 1相同的客户端库(意味着阶段一的哈希算法相同),也拥有同样的memcached列表(A, B, C)。于是,经过相同的哈希计算(阶段一),Client 2计算出key “foo”在memcached B上,然后它直接请求memcached B,得到数据”barbaz”。各种客户端在memcached中数据的存储形式是不同的(perl Storable, php serialize, Java hibernate, JSON等)。一些客户端实现的哈希算法也不一样。但是,memcached服务器端的行为总是一致的。

最后,从实现的角度看,memcached是一个非阻塞的、基于事件的服务器程序。这种架构可以很好地解决C10K problem,并具有极佳的可扩展性。可以参考A Story of Caching,这篇文章简单解释了客户端与memcached是如何交互的。

memcached最大的优势是什么?
请仔细阅读上面的问题(即memcached是如何工作的)。Memcached最大的好处就是它带来了极佳的水平可扩展性,特别是在一个巨大的系统中。由于客户端自己做了一次哈希,那么我们很容易增加大量memcached到集群中。memcached之间没有相互通信, 因此不会增加 memcached的负载;没有多播协议,不会网络通信量爆炸(implode)。memcached的集群很好用。内存不够了?增加几台 memcached吧;CPU不够用了?再增加几台吧;有多余的内存?在增加几台吧,不要浪费了。

基于memcached的基本原则,可以相当轻松地构建出不同类型的缓存架构。除了这篇FAQ,在其他地方很容易找到详细资料的。看看下面的几个问题吧,它们在memcached、服务器的local cache和MySQL的query cache之间做了比较。这几个问题会让您有更全面的认识。

memcached和MySQL的query cache相比,有什么优缺点?
把 memcached引入应用中,还是需要不少工作量的。MySQL有个使用方便的query cache,可以自动地缓存SQL查询的结果,被缓存的SQL查询可以被反复地快速执行。Memcached与之相比,怎么样呢?MySQL的query cache是集中式的,连接到该query cache的MySQL服务器都会受益。
* 当您修改表时,MySQL的query cache会立刻被刷新(flush)。存储一个memcached item只需要很少的时间,但是当写操作很频繁时,MySQL的query cache会经常让所有缓存数据都失效。

* 在多核CPU上,MySQL的query cache会遇到扩展问题(scalability issues)。在多核CPU上,query cache会增加一个全局锁(global lock), 由于需要刷新更多的缓存数据,速度会变得更慢。

* 在MySQL的query cache中,我们是不能存储任意的数据的(只能是SQL查询结果)。而利用memcached,我们可以搭建出各种高效的缓存。比如,可以执行多个独立 的查询,构建出一个用户对象(user object),然后将用户对象缓存到memcached中。而query cache是SQL语句级别的,不可能做到这一点。在小的网站中,query cache会有所帮助,但随着网站规模的增加,query cache的弊将大于利。

* query cache能够利用的内存容量受到MySQL服务器空闲内存空间的限制。给数据库服务器增加更多的内存来缓存数据,固然是很好的。但是,有了 memcached,只要您有空闲的内存,都可以用来增加memcached集群的规模,然后您就可以缓存更多的数据。

memcached和服务器的local cache(比如PHP的APC、mmap文件等)相比,有什么优缺点?
首先,local cache有许多与上面(query cache)相同的问题。local cache能够利用的内存容量受到(单台)服务器空闲内存空间的限制。不过,local cache有一点比memcached和query cache都要好,那就是它不但可以存储任意的数据,而且没有网络存取的延迟。

* local cache的数据查询更快。考虑把highly common的数据放在local cache中吧。如果每个页面都需要加载一些数量较少的数据,考虑把它们放在local cached吧。

* local cache缺少集体失效(group invalidation)的特性。在memcached集群中,删除或更新一个key会让所有的观察者觉察到。但是在local cache中, 我们只能通知所有的服务器刷新cache(很慢,不具扩展性),或者仅仅依赖缓存超时失效机制。

* local cache面临着严重的内存限制,这一点上面已经提到。

memcached的cache机制是怎样的?
Memcached主要的cache机制是LRU(最近最少用)算法+超时失效。当您存数据到memcached中,可以指定该数据在缓存中可以呆多久Which is forever, or some time in the future。如果memcached的内存不够用了,过期的slabs会优先被替换,接着就轮到最老的未被使用的slabs。

memcached如何实现冗余机制?
不实现!我们对这个问题感到很惊讶。Memcached应该是应用的缓存层。它的设计本身就不带有任何冗余机制。如果一个memcached节点失去了所有数据,您应该可以从数据源(比如数据库)再次获取到数据。您应该特别注意,您的应用应该可以容忍节点的失效。不要写一些糟糕的查询代码,寄希望于 memcached来保证一切!如果您担心节点失效会大大加重数据库的负担,那么您可以采取一些办法。比如您可以增加更多的节点(来减少丢失一个节点的影响),热备节点(在其他节点down了的时候接管IP),等等。

memcached如何处理容错的?
不处理!:) 在memcached节点失效的情况下,集群没有必要做任何容错处理。如果发生了节点失效,应对的措施完全取决于用户。节点失效时,下面列出几种方案供您选择:

* 忽略它! 在失效节点被恢复或替换之前,还有很多其他节点可以应对节点失效带来的影响。

* 把失效的节点从节点列表中移除。做这个操作千万要小心!在默认情况下(余数式哈希算法),客户端添加或移除节点,会导致所有的缓存数据不可用!因为哈希参照的节点列表变化了[哈希值无法对应特定服务器也变化了],大部分key会因为哈希值的改变而被映射到(与原来)不同的节点上。

* 启动热备节点,接管失效节点所占用的IP。这样可以防止哈希紊乱(hashing chaos)。

* 如果希望添加和移除节点,而不影响原先的哈希结果,可以使用一致性哈希算法(consistent hashing)。可以百度一下一致性哈希算法。支持一致性哈希的客户端已经很成熟,而且被广泛使用。去尝试一下吧!

* 两次哈希(reshing)。当客户端存取数据时,如果发现一个节点down了,就再做一次哈希(哈希算法与前一次不同),重新选择另一个节点(需要注意的时,客户端并没有把down的节点从节点列表中移除,下次还是有可能先哈希到它)。如果某个节点时好时坏,两次哈希的方法就有风险了,好的节点和坏的节 点上都可能存在脏数据(stale data)。

如何将memcached中item批量导入导出?

您不应该这样做!Memcached是一个非阻塞的服务器。任何可能导致memcached暂停或瞬时拒绝服务的操作都应该值得深思熟虑。向 memcached中批量导入数据往往不是您真正想要的!想象看,如果缓存数据在导出导入之间发生了变化,您就需要处理脏数据了;如果缓存数据在导出导入 之间过期了,您又怎么处理这些数据呢?

因此,批量导出导入数据并不像您想象中的那么有用。不过在一个场景倒是很有用。如果您有大量的从不变化的数据,并且希望缓存很快热(warm)起来,批量导入缓存数据是很有帮助的。虽然这个场景并不典型,但却经常发生,因此我们会考虑在将来实现批量导出导入的功能。

Steven Grimm,一如既往地,在邮件列表中给出了另一个很好的例子

但是我确实需要把memcached中的item批量导出导入,怎么办??
好吧好吧。如果您需要批量导出导入,最可能的原因一般是重新生成缓存数据需要消耗很长的时间,或者数据库坏了让您饱受痛苦。

如果一个memcached节点down了让您很痛苦,那么您还会陷入其他很多麻烦。您的系统太脆弱了。您需要做一些优化 工作。比如处理”惊群”问题(比如 memcached节点都失效了,反复的查询让您的数据库不堪重负…这个问题在FAQ的其他提到过),或者优化不好的查询。记住,Memcached 并不是您逃避优化查询的借口。

如果您的麻烦仅仅是重新生成缓存数据需要消耗很长时间(15秒到超过5分钟),您可以考虑重新使用数据库。这里给出一些提示:

* 使用MogileFS(或者CouchDB等类似的软件)在存储item。把item计算出来并dump到磁盘上。 MogileFS可以很方便地覆写item,并提供快速地访问。.您甚至可以把MogileFS中的item缓存在memcached中,这样可以加快读 取速度。 MogileFS+Memcached的组合可以加快缓存不命中时的响应速度,提高网站的可用性。

* 重新使用MySQL。 MySQL的 InnoDB主键查询的速度非常快。如果大部分缓存数据都可以放到VARCHAR字段中,那么主键查询的性能将更好。从memcached中按key查询几乎等价于MySQL的主键查询:将key 哈希到64-bit的整数,然后将数据存储到MySQL中。您可以把原始(不做哈希)的key存储都普通的字段中,然后建立二级索引来加快查询…key被动地失效,批量删除失效的key,等等。

上面的方法都可以引入memcached,在重启memcached的时候仍然提供很好的性能。由于不需要当心”hot”的item被 memcached LRU算法突然淘汰,用户再也不用花几分钟来等待重新生成缓存数据(当缓存数据突然从内存中消失时),因此上面的方法可以全面提高性能。

关于这些方法的细节,详见博客

memcached是如何做身份验证的?
没有身份认证机制!memcached是运行在应用下层的软件(身份验证应该是应用上层的职责)。memcached的客户端和服务器端之所以是轻量级的,部分原因就是完全没有实现身份验证机制。这样memcached可以很快地创建新连接,服务器端也无需任何配置。

如果您希望限制访问,您可以使用防火墙,或者让memcached监听unix domain socket。

memcached的多线程是什么,如何使用它们?
线程就是定律(threads rule)!在Steven Grimm和Facebook的努力下,memcached 1.2及更高版本拥有了多线程模式。多线程模式允许memcached能够充分利用多个CPU,并在CPU之间共享所有的缓存数据。memcached使用一种简单的锁机制来保证数据更新操作的互斥。相比在同一个物理机器上运行多个memcached实例,这种方式能够更有效地处理multi gets。

如果系统负载并不重,也许不需要启用多线程工作模式。如果在运行一个拥有大规模硬件的、庞大的网站,将会看到多线程的好处。

简单地总结一下:命令解析(memcached在这里花了大部分时间)可以运行在多线程模式下。memcached内部对数据的操作是基于很多全局锁的(因此这部分工作不是多线程的)。未来对多线程模式的改进,将移除大量的全局锁,提高memcached在负载极高的场景下的性能。

memcached能接受的key的最大长度是多少?
key 的最大长度是250个字符。需要注意的是,250是memcached服务器端内部的限制,如果您使用的客户端支持”key的前缀”或类似特性,那么 key(前缀+原始key)的最大长度是可以超过250个字符的。我们推荐使用使用较短的key,因为可以节省内存和带宽。

memcached对item的过期时间有什么限制?
过期时间最大可以达到30天。memcached把传入的过期时间(时间段)解释成时间点后,一旦到了这个时间点,memcached就把item置为失效状态,这是一个简单但obscure的机制。

memcached最大能存储多大的单个item?
1MB。如果你的数据大于1MB,可以考虑在客户端压缩或拆分到多个key中。

为什么单个item的大小被限制在1M byte之内?
啊…这是一个大家经常问的问题!

简单的回答:因为内存分配器的算法就是这样的。

详细的回答:Memcached的内存存储引擎(引擎将来可插拔…),使用slabs来管理内存。内存被分成大小不等的slabs chunks(先分成大小相等的slabs,然后每个slab被分成大小相等chunks,不同slab的chunk大小是不相等的)。chunk的大小 依次从一个最小数开始,按某个因子增长,直到达到最大的可能值。

如果最小值为400B,最大值是1MB,因子是1.20,各个slab的chunk的大小依次是:slab1 - 400B slab2 - 480B slab3 - 576B …

MEMCACHE 性能优化

slab中chunk越大,它和前面的slab之间的间隙就越大。因此最大值越大,内存利用率越低。Memcached必须为每个slab预先分配内存,因此如果设置了较小的因子和较大的最大值,会需要更多的内存。还有其他原因使得您不要这样向memcached中存取很大的数据…不要尝试把巨大的网页放到mencached中。把这样大的数据结构load和unpack到内存中需要花费很长的时间,从而导致您的网站性能反而不好。

如果确实需要存储大于1MB的数据,可以修改slabs.c:POWER_BLOCK的值,然后重新编译memcached;或者使用低效的malloc/free。其它的建议包括数据库、MogileFS等。

可以在不同的memcached节点上使用大小不等的缓存空间吗,这么做之后,memcached能够更有效地使用内存吗?
Memcache 客户端仅根据哈希算法来决定将某个key存储在哪个节点上,而不考虑节点的内存大小。因此,您可以在不同的节点上使用大小不等的缓存。但是一般都是这样做 的:拥有较多内存的节点上可以运行多个memcached实例,每个实例使用的内存跟其他节点上的实例相同。

什么是二进制协议,我该关注吗?
关于二进制最好的信息当然是二进制协议规范

二进制协议尝试为端提供一个更有效的、可靠的协议,减少客户端/服务器端因处理协议而产生的CPU时间。根据Facebook的测试,解析ASCII协议是memcached中消耗CPU时间最多的环节。所以我们为什么不改进ASCII协议呢?

在这个邮件列表的thread中可以找到一些旧的信息

memcached的内存分配器是如何工作的?为什么不适用malloc/free?为何要使用slabs?
实际上,这是一个编译时选项。默认会使用内部的slab分配器。您确实确实应该使用内建的slab分配器。最早的时候,memcached只使用 malloc/free来管理内存。然而这种方式不能与OS的内存管理以前很好地工作。反复地malloc/free造成了内存碎片,OS最终花费大量的时间去查找连续的内存块来满足malloc的请求,而不是运行memcached进程。如果您不同意,当然可以使用malloc!只是不要在邮件列表中抱怨啊:)

slab分配器就是为了解决这个问题而生的。内存被分配并划分成chunks,一直被重复使用。因为内存被划分成大小不等的 slabs,如果item的大小与被选择存放它的slab不是很合适的话,就会浪费一些内存。Steven Grimm正在这方面已经做出了有效的改进。

邮件列表中有一些关于slab的改进(power of n 还是 power of 2)和权衡方案:http://lists.danga.com/pipermail/memcached/2006-May/002163.htmlhttp://lists.danga.com/pipermail/memcached/2007-March/003753.html

如果您想使用malloc/free,看看它们工作地怎么样,您可以在构建过程中定义USE_SYSTEM_MALLOC。这个特性没有经过很好的测试,所以太不可能得到开发者的支持。

memcached是原子的吗?
当然!好吧,让我们来明确一下:所有的被发送到memcached的单个命令是完全原子的。如果您针对同一份数据同时发送了一个set命令和一个get命令,它们不会影响对方。它们将被串行化、先后执行。即使在多线程模式,所有的命令都是原子的,除非程序有bug。命令序列不是原子的。如果您通过get命令获取了一个item,修改了它,然后想把它set回memcached,我们不保证这个item没有被其他进程(process,未必是操作系统中的进程)操作过。在并发的情况下,您也可能覆写了一个被其他进程set的item。

memcached 1.2.5以及更高版本,提供了gets和cas命令,它们可以解决上面的问题。如果您使用gets命令查询某个key的item,memcached会给您返回该item当前值的唯一标识。如果您覆写了这个item并想把它写回到memcached中,您可以通过cas命令把那个唯一标识一起发送给 memcached。如果该item存放在memcached中的唯一标识与您提供的一致,您的写操作将会成功。如果另一个进程在这期间也修改了这个 item,那么该item存放在memcached中的唯一标识将会改变,您的写操作就会失败。

通常,基于memcached中item的值来修改item,是一件棘手的事情。除非您很清楚自己在做什么,否则请不要做这样的事情。


简化后的中文参数说明

-p <num> 监听的TCP端口(默认: 11211)
-U <num> 监听的UDP端口(默认: 11211, 0表示不监听)
-s <file> 用于监听的UNIX套接字路径(禁用网络支持)
-a <mask> UNIX套接字访问掩码,八进制数字(默认:0700)
-l <ip_addr> 监听的IP地址。(默认:INADDR_ANY,所有地址)
-d 作为守护进程来运行。
-r 最大核心文件限制。
-u <username> 设定进程所属用户。(只有root用户可以使用这个参数)
-m <num> 单个数据项的最大可用内存,以MB为单位。(默认:64MB)
-M 内存用光时报错。(不会删除数据)
-c <num> 最大并发连接数。(默认:1024)
-k 锁定所有内存页。注意你可以锁定的内存上限。试图分配更多内存会失败的,所以留意启动守护进程时所用的用户可分配的内存上限。(不是前面的 -u <username> 参数;在sh下,使用命令"ulimit -S -l NUM_KB"来设置。)
-v 提示信息(在事件循环中打印错误/警告信息。)
-vv 详细信息(还打印客户端命令/响应)
-vvv 超详细信息(还打印内部状态的变化)
-h 打印这个帮助信息并退出。
-i 打印memcached和libevent的许可。
-P <file> 保存进程ID到指定文件,只有在使用 -d 选项的时候才有意义。
-f <factor> 块大小增长因子。(默认:1.25)
-n <bytes> 分配给key+value+flags的最小空间(默认:48)
-L 尝试使用大内存页(如果可用的话)。提高内存页尺寸可以减少"页表缓冲(TLB)"丢失次数,提高运行效率。为了从操作系统获得大内存页,memcached会把全部数据项分配到一个大区块。
-D <char> 使用 <char> 作为前缀和ID的分隔符。这个用于按前缀获得状态报告。默认是":"(冒号)。如果指定了这个参数,则状态收集会自动开启;如果没指定,则需要用命令"stats detail on"来开启。
-t <num> 使用的线程数(默认:4)
-R 每个连接可处理的最大请求数。
-C 禁用CAS。
-b 设置后台日志队列的长度(默认:1024)
-B 绑定协议 - 可能值:ascii,binary,auto(默认)
-I 重写每个数据页尺寸。调整数据项最大尺寸。

操作命令介绍

First, the client sends a command line which looks like this: <command name> <key> <flags> <exptime> <bytes> [noreply]\r\n cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]\r\n

<command name>是"set", "add", "replace", "append" or "prepend"
"set" :"存储这个数据",一般是更新已有的缓存,也可以用于新增。
"add" :新增缓存,缓存中不存在新增的KEY。
"replace":替换现有的缓存,缓存中一定已经存储KEY "Append":在现有的缓存数据后添加缓存数据。
"prepend":在现有的缓存数据前添加缓存数据 "Cas":check and set操作,存储缓存,前提是在check后没有其它人修改过数据,用于多客户端同时设置相同的KEY时的原子操作。
"key":缓存的KEY
"flags":最开始是16位的无符号整数,现在的版本一般是32位。用户客户端存储自定义标记数据,客户端自定义这个数据如何存储,例如我经过压缩存储标记为1,那么这个返回的时候flag会是1,然后就知道是经过压缩的,那么客户端库会反压缩给应用使用。一种标记作用而已。具体如何处理,需要客户端处理。
"exptime":缓存过期时间。0表示不自动失效,可以是Unix time或当前服务器时间的偏移量(秒为单位),如果你想设置当前时间后1分钟过期,则此参数为60。 设置秒数:从设定开始数,第n秒后失效。
时间戳, 到指定的时间戳后失效。
"bytes":缓存数据的长度 "cas unique":unique 64-bit value of an existing entry,cas操作的时候回传的值,用于服务器端判断缓存是否改变。
"noreply":服务器不响应处理结果。 After this line, the client sends the data block: <data block>\r\n //\r\n结束 After sending the command line and the data blockm the client awaits the reply, which may be:
"STORED\r\n":表示存储成功。
"NOT_STORED\r\n":表示未存储,但并不是错误。如:对已经有的KEY使用add。
"EXISTS\r\n":表示使用cas命令设置数据未成功,在你最后一次获取数据后,数据已经被其它人修改。
"NOT_FOUND\r\n":表示使用cas存储数据时候,key不存储。


Memcached stats 命令

Memcached stats 命令用于返回统计信息例如 PID(进程号)、版本号、连接数等。有一系列的获取服务器信息的命令,这部分命令都是以stats开头的。

常用的命令:
stats
显示服务器信息、统计数据等

stats reset
清空统计数据

stats cachedump slab_id limit_num
显示某个slab中的前limit_num个key列表,显示格式如下
ITEM key_name [value_length b; expire_time|access_time s],其中,memcached 1.2.2及以前版本显示的是访问时间(timestamp),1.2.4以上版本,包括1.2.4显示过期时间(timestamp),如果是永不过期的key,expire_time会显示为服务器启动的时间

stats cachedump 7 2
ITEM copy_test1 [250 b; 1207795754 s]
ITEM copy_test [248 b; 1207793649 s]
注意:不要试图通过此命令导出Memcache服务中某个slab的所有Key列表,该命令默认只返回1M的内存数据。

stats slabs
显示各个slab的信息,包括chunk的大小、数目、使用情况等

stats items
显示各个slab中item的数目和最老item的年龄(最后一次访问距离现在的秒数)

stats detail [on|off|dump]
设置或者显示详细操作记录

参数为on,打开详细操作记录
参数为off,关闭详细操作记录
参数为dump,显示详细操作记录(每一个键值get、set、hit、del的次数)

服务器STATS信息
参数    描述
pid    Memcached服务器进程ID
uptime    服务器自启动以来已运行秒数
time    服务器当前Unix时间戳
version    memcache版本
pointer_size    操作系统指针大小
rusage_user    进程累计用户时间
rusage_system    进程累计系统时间
curr_connections    当前打开的连接数量
total_connections    Memcached运行以来连接总数
connection_structures    Memcached分配的连接结构数量
cmd_get    get命令请求次数
cmd_set    set命令请求次数
cmd_flush    flush命令请求次数
get_hits    get命令命中次数
get_misses    get命令未命中次数
delete_misses    delete命令未命中次数
delete_hits    delete命令命中次数
incr_misses    incr命令未命中次数
incr_hits    incr命令命中次数
decr_misses    decr命令未命中次数
decr_hits    decr命令命中次数
cas_misses    cas命令未命中次数
cas_hits    cas命令命中次数
cas_badval    使用擦拭次数
auth_cmds    认证命令处理的次数
auth_errors    认证失败数目
bytes_read    读取总字节数
bytes_written    发送总字节数
limit_maxbytes    分配的内存总大小(字节)
accepting_conns    服务器是否达到过最大连接(0/1)
listen_disabled_num    失效的监听数
threads    当前线程数
conn_yields    连接操作主动放弃数目
bytes    当前存储item所占用的字节数
curr_items    当前存储的item数据总数
total_items    启动以来存储item的数据总数
evictions    LRU释放的对象数目。为了给新的数据项目释放空间从缓存移除对象的数目,根据LRU算法移除的以及过期的对象
reclaimed    已过期的数据条目来存储新数据的数目


服务器SETTINGS信息
参数    描述
maxbytes    最大字节数限制(0无限制)
maxconns    允许最大连接数
tcpport    TCP端口
udpport    UDP端口    
inter    IP地址
verbosity    日志(0=none,1=som,2=lots)
oldest    最老对象过期时间
evictions    是否禁用LRU(on/off)
domain_socket    Socket域名
umask    创建Socket的掩码
growth_factor    增长因子    
chunk_size    chunk大小(key+value+flags)
num_threads    线程数(默认4,可通过-t参数设置)
stat_key_prefix    stats分隔符
detail_enabled    显示stats细节信息(yes/no)
reqs_per_event    最大IO吞吐量(每event)
cas_enabled    是否启用CAS(yes/no,-C禁用)
tcp_backlog    TCP监控日志
binding_protocol    绑定协议
auth_enabled_sasl    是否启用SASL验证(yes/no)
item_size_max    数据最大尺寸


最新版本:1.4
该版本主要侧重于线程可伸缩性以及性能方面的提升,主要内容包括:
* 把cache_lock深度整合到item_alloc中
* 尽可能多的使用项目分区锁(item partitioned lock)
* 从item_alloc中移除深度搜索
* 将hash调用从cache_lock中移出
* 在主要缓存锁(main cache lock)中使用自旋锁(spinlocks)
* 允许以root权限进行测试
* 从asciiprot热路径中删除不常见的分支

最新版本:1.5
这个版本使1.4.39  中的 选项 - o modern 成为默认选项。如果平台支持的话,它还增加了--long-options (see --help|-h),以及修复了一些小bug。这次更新主要是修复 bug,没有添加新特性。值得关注的是,该版本修复了一个来自 2006 年的 bug,该bug会导致一个实例中存储超过 32 亿个项目导致的崩溃。1.5.6是一个 bug 修复版本。同时由于前段时间因被曝出有攻击者通过设置 memcached 的最大值,欺骗 UDP 数据包发起请求,利用 Memcached 发送的大量庞大的 UDP 响应数据包进行一些攻击行为,该版本已默认禁用 UDP 协议。带来了 seccomp 和 extstore 的 bug 修复,以及 ARM 和32位系统的 extstore 平台可移植性已经大大提高。现在 extstore 对 ARMv8 有 CRC32 硬件支持,并且适用于 32 位系统和许多 ARM 平台。完整更新内容请查看发行说明。Memcached 1.5.13 发布了,新版主要更新内容是对 TLS 的支持。新版更新亮点如下:TLS for memcached 的基本实现、升级 Get And Touch 文档、新功能、支持 TLS!详情见发行公告

最新版本:1.6
Memcached 1.6.4 发布了,这是一个 bug 修复版本,主要针对编译/构建/兼容程序,同时修复了使用分块条目时在可重启模式下的一些故障。
estart: 修复删除分块条目的问题
ascii auth: 修复等待数据时的 CPU 占用
extstore: 修复一些 valgrind 错误
修复 -D_FORTIFY_SOURCE = 2 未定义行为,Windows 中的 t/64bit.t 测试失败
修复 Windows 中的生成警告,添加构建选项以禁用 UNIX socket 功能
修复 OSX/cygwin extstore 读取,protocol.txt 中的打印错误
build: sasl 在 FreeBSD 上构建修复
testapp: 使用 -flto = auto 修复故障
更多信息请参考更新说明

1.6.20 现已于2023年5月中旬发布,此版本主要是代理模式的大量修复和更新、优化 extstore 磁盘刷新,以及对元协议的更改和较小的修复。
核心:不再停止 SIGINT/SIGTERM 上的线程,从信号处理程序中删除 printf,修复 len < 8 的标记侦听器,给线程唯一的名字
代理:迭代修改后的请求处理,现在相对于 resp 生命周期的记录时间,修复 SIGHUP 重新加载期间等待中的崩溃,修复后端连接的生命周期
代理:IO 线程性能改进,添加 mcp.AWAIT_BACKGROUND,修复数据块错误上的 lua 注册表损坏,添加 proxy_await_active 统计
代理:修复部分响应读取处理,修复刷新部分写入,添加更多后端失败消息,修复 mcp.log_reqnil res 时的崩溃
代理:将调试符号添加到 lua 构建,修复后端被 gc 时的崩溃,从使用中删除 libevent 许可证,使用 clang-15 构建的修复:
元:删除 meta_response_old 启动选项,允许 mg 没有标志 + 在 EN 上反映 O/k,元算术命令有多余的空间,从元响应中删除多余的空格
代理:修复后端连接初始化的错误,添加 mcp.await FASTGOOD 标志,修复日志时间戳,修复 clang 错误的函数原型
有关代理 API 的更新,请参阅 wiki 页面。
更新公告详见此处

1.6.23 现已于2024年1月中旬发布,此版本仅影响 proxy code。请求调度 API 已重做,有关 API 的完整文档请参阅此处。官方提醒,有关此版本中新的 API 虽然已经做了大量工作来验证代码,但这仍然是一个很大的变化。建议用户在部署前仔细测试。

以前的 mcp.await 和 pool(request) 调用表单已被积极弃用,相关代码将在 memcached 的下一个版本中删除。在再次升级之前,用户需要将配置脚本移至新的 API。此举是为了简化内部代码并解除旧 API 所阻碍的性能和稳定性功能。以前在默认情况下,代理使用单个后台线程向后端服务器发出 IO。这通常会阻止扩展到超过 4 个 CPU 核心,但会减少使用的 TCP 套接字数量并增加到后端的管道传输。从此版本开始,默认情况是直接从工作线程发出后端 IO。这种行为可以在全局或每个池的基础上进行调整。

项目团队的目标是稳定代理,从现在开始专注于代码清理和较小的更改。还计划在下一个版本中直接在 memcached 中加入新的 routelib,以提高易用性。
proxy:添加 mcp.backend_use_iothread(bool)+ 错误修复、lua API 版本 2、mcp.time_[real|mono]_millis()
Proxy API version 2:
通过允许对每个请求重复使用预先计算来提高性能
通过避免在请求时分配、防止 GC 使用来提高性能
允许递归函数调用
让未来的 API 扩展更加容易
针对发出的每个后端请求的自定义回调

详情可查看更新说明


项目主页:http://memcached.org/

该文章最后由 阿炯 于 2024-01-11 17:43:54 更新,目前是第 6 版。