Memcached 常见的问题
2017-07-27 08:58 tlnshuju 阅读(792) 评论(0) 编辑 收藏 举报memcached是怎么工作的?
Memcached的奇妙来自两阶段哈希(two-stage hash)。Memcached就像一个巨大的、存储了非常多<key,value>对的哈希表。
通过key,能够存储或查询随意的数据。
client能够把数据存储在多台memcached上。
当查询数据时。client首先參考节点列表计算出key的哈希值(阶段一哈希),进而选中一个节点。client将请求发送给选中的节点,然后memcached节点通过一个内部的哈希算法(阶段二哈希)。查找真正的数据(item)。
举个列子,如果有3个client1, 2, 3。3台memcached A, B, C:
Client 1想把数据"barbaz"以key "foo"存储。Client 1首先參考节点列表(A, B, C),计算key "foo"的哈希值。如果memcached B被选中。
接着,Client1直接connect到memcached B。通过key"foo"把数据"barbaz"存储进去。 Client 2使用与Client 1同样的client库(意味着阶段一的哈希算法同样),也拥有同样的memcached列表(A,B, C)。
于是。经过同样的哈希计算(阶段一),Client 2计算出key "foo"在memcached B上。然后它直接请求memcached B。得到数据"barbaz"。
各种client在memcached中数据的存储形式是不同的(perl Storable, php serialize,java hibernate, JSON等)。一些client实现的哈希算法也不一样。可是,memcachedserver端的行为总是一致的。
最后,从实现的角度看。memcached是一个非堵塞的、基于事件的server程序。这样的架构能够非常好地解决C10K problem 。并具有极佳的可扩展性。
能够參考A Story of Caching ,这篇文章简单解释了client与memcached是怎样交互的。
memcached最大的优势是什么?
请细致阅读上面的问题(即memcached是怎样工作的)。Memcached最大的优点就是它带来了极佳的水平可扩展性,特别是在一个巨大的系统中。因为client自己做了一次哈希。那么我们非常easy添加大量memcached到集群中。memcached之间没有相互通信,因此不会添加 memcached的负载;没有多播协议,不会网络通信量爆炸(implode)。
memcached的集群非常好用。内存不够了?添加几台memcached吧。CPU不够用了?再添加几台吧;有多余的内存?在添加几台吧,不要浪费了。
基于memcached的基本原则,能够相当轻松地构建出不同类型的缓存架构。除了这篇FAQ,在其它地方非常easy找到具体资料的。
看看以下的几个问题吧,它们在memcached、server的local cache和MySQL的query cache之间做了比較。
这几个问题会让您有更全面的认识。
memcached和MySQL的querycache相比,有什么优缺点?
把memcached引入应用中,还是须要不少工作量的。
MySQL有个使用方便的query cache。能够自己主动地缓存SQL查询的结果,被缓存的SQL查询能够被重复地高速运行。Memcached与之相比,怎么样呢?MySQL的query cache是集中式的。连接到该query cache的MySQLserver都会受益。
- 当您改动表时。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能够利用的内存容量受到MySQLserver空暇内存空间的限制。给数据库server添加很多其它的内存来缓存数据,固然是非常好的。可是,有了memcached。仅仅要您有空暇的内存。都能够用来添加memcached集群的规模,然后您就能够缓存很多其它的数据。
memcached和server的local cache(比方PHP的APC、mmap文件等)相比。有什么优缺点?
首先,local cache有很多与上面(query cache)同样的问题。
local cache可以利用的内存容量受到(单台)server空暇内存空间的限制。只是,local cache有一点比memcached和querycache都要好。那就是它不但可以存储随意的数据,并且没有网络存取的延迟。
- local cache的数据查询更快。考虑把highly
common的数据放在local cache中吧。
假设每一个页面都须要载入一些数量较少的数据。考虑把它们放在local cached吧。
- local cache缺少集体失效(group
invalidation)的特性。在memcached集群中,删除或更新一个key会让全部的观察者觉察到。
可是在local cache中, 我们只能通知全部的server刷新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节点失效的情况下,集群没有必要做不论什么容错处理。假设发生了节点失效。应对的措施全然取决于用户。节点失效时,以下列出几种方案供您选择:
- 忽略它。 在失效节点被恢复或替换之前。还有非常多其它节点能够应对节点失效带来的影响。
- 把失效的节点从节点列表中移除。
做这个操作千万要小心!在默认情况下(余数式哈希算法),client加入或移除节点。会导致全部的缓存数据不可用!
由于哈希參照的节点列表变化了,大部分key会由于哈希值的改变而被映射到(与原来)不同的节点上。
- 启动热备节点,接管失效节点所占用的IP。
这样能够防止哈希紊乱(hashing chaos)。
- 假设希望加入和移除节点,而不影响原先的哈希结果,能够使用一致性哈希算法(consistent hashing)。
您能够百度一下一致性哈希算法。支持一致性哈希的client已经非常成熟,并且被广泛使用。去尝试一下吧!
- 两次哈希(reshing)。当client存取数据时,假设发现一个节点down了,就再做一次哈希(哈希算法与前一次不同)。又一次选择还有一个节点(须要注意的时。client并没有把down的节点从节点列表中移除,下次还是有可能先哈希到它)。假设某个节点时好时坏,两次哈希的方法就有风险了,好的节点和坏的节点上都可能存在脏数据(stale data)。
怎样将memcached中item批量导入导出?
您不应该这样做!
Memcached是一个非堵塞的server。不论什么可能导致memcached暂停或瞬时拒绝服务的操作都应该值得深思熟虑。
向memcached中批量导入数据往往不是您真正想要的。想象看,假设缓存数据在导出导入之间发生了变化。您就须要处理脏数据了;假设缓存数据在导出导入之间过期了,您又怎么处理这些数据呢?
因此,批量导出导入数据并不像您想象中的那么实用。只是在一个场景倒是非常实用。假设您有大量的从不变化的数据。而且希望缓存非常快热(warm)起来,批量导入缓存数据是非常有帮助的。尽管这个场景并不典型,但却常常发生,因此我们会考虑在将来实现批量导出导入的功能。
Steven Grimm。一如既往地,。在邮件列表中给出了还有一个非常好的样例:http://lists.danga.com/pipermail/memcached/2007-July/004802.html 。
可是我确实须要把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算法突然淘汰,用户再也不用花几分钟来等待又一次生成缓存数据(当缓存数据突然从内存中消失时),因此上面的方法能够全面提高性能。
关于这些方法的细节,详见博客:http://dormando.livejournal.com/495593.html 。
memcached是怎样做身份验证的?
没有身份认证机制!
memcached是执行在应用下层的软件(身份验证应该是应用上层的职责)。memcached的client和server端之所以是轻量级的,部分原因就是全然没有实现身份验证机制。这样。memcached能够非常快地创建新连接,server端也无需不论什么配置。
假设您希望限制訪问,您能够使用防火墙。或者让memcached监听unix domain socket。
memcached的多线程是什么?怎样使用它们?
线程就是定律(threads rule)!
在Steven Grimm和Facebook的努力下。memcached 1.2及更高版本号拥有了多线程模式。多线程模式同意memcached可以充分利用多个CPU,并在CPU之间共享全部的缓存数据。memcached使用一种简单的锁机制来保证数据更新操作的相互排斥。相比在同一个物理机器上执行多个memcached实例,这样的方式可以更有效地处理multi gets。
假设您的系统负载并不重,或许您不须要启用多线程工作模式。假设您在执行一个拥有大规模硬件的、庞大的站点,您将会看到多线程的优点。
很多其它信息请參见:http://code.sixapart.com/svn/memcached/trunk/server/doc/threads.txt。
简单地总结一下:命令解析(memcached在这里花了大部分时间)能够执行在多线程模式下。memcached内部对数据的操作是基于非常多全局锁的(因此这部分工作不是多线程的)。未来对多线程模式的改进,将移除大量的全局锁,提高memcached在负载极高的场景下的性能。
memcached能接受的key的最大长度是多少?
key的最大长度是250个字符。须要注意的是,250是memcachedserver端内部的限制,假设您使用的client支持"key的前缀"或类似特性。那么key(前缀+原始key)的最大长度是能够超过250个字符的。我们推荐使用使用较短的key。由于能够节省内存和带宽。
memcached对item的过期时间有什么限制?
过期时间最大能够达到30天。memcached把传入的过期时间(时间段)解释成时间点后,一旦到了这个时间点。memcached就把item置为失效状态。这是一个简单但obscure的机制。
memcached最大能存储多大的单个item?
1MB。假设你的数据大于1MB,能够考虑在client压缩或拆分到多个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 ...
slab中chunk越大,它和前面的slab之间的间隙就越大。因此,最大值越大,内存利用率越低。Memcached必须为每一个slab预先分配内存。因此假设设置了较小的因子和较大的最大值,会须要很多其它的内存。
还有其它原因使得您不要这样向memcached中存取非常大的数据...不要尝试把巨大的网页放到mencached中。把这样大的数据结构load和unpack到内存中须要花费非常长的时间。从而导致您的站点性能反而不好。
假设您确实须要存储大于1MB的数据。你能够改动slabs.c:POWER_BLOCK的值,然后又一次编译memcached;或者使用低效的malloc/free。其它的建议包含数据库、MogileFS等。
我可以在不同的memcached节点上使用大小不等的缓存空间吗?这么做之后。memcached可以更有效地使用内存吗?
Memcacheclient仅依据哈希算法来决定将某个key存储在哪个节点上。而不考虑节点的内存大小。因此,您能够在不同的节点上使用大小不等的缓存。
可是一般都是这样做的:拥有较多内存的节点上能够执行多个memcached实例。每一个实例使用的内存跟其它节点上的实例同样。
什么是二进制协议。我该关注吗?
关于二进制最好的信息当然是二进制协议规范:http://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol 。
二进制协议尝试为端提供一个更有效的、可靠的协议。降低client/server端因处理协议而产生的CPU时间。
依据Facebook的測试,解析ASCII协议是memcached中消耗CPU时间最多的环节。
所以,我们为什么不改进ASCII协议呢?
在这个邮件列表的thread中能够找到一些旧的信息:http://lists.danga.com/pipermail/memcached/2007-July/004636.html 。
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。这个特性没有经过非常好的測试。所以太不可能得到开发人员的支持。
很多其它信息:http://code.sixapart.com/svn/memcached/trunk/server/doc/memory_management.txt 。
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。是一件棘手的事情。除非您非常清楚自己在做什么,否则请不要做这种事情。