memcached 常见问题

现阶段正在研究memcached,心血来潮把memcached官方网站上的FAQ翻译了一把,但愿对想要使用memcached的同窗们有帮助。因为兄弟我对数据库不是很熟,有些关于数据库概念的没有翻译,有些可能直接翻错了,望你们指出。谢谢! php

1. 基本问题

1.1 什么是 memcached ?

memcached 是一个高性能的分布式内存的缓存系统。本质上它是通用的,但其目的是为了加速动态 web 应用程序,减轻数据库访问压力而设计的。 html

Danga Interfactive 开发了 memcached 用来提升 LiveJournal.com 网站的速度。这个网站由大批的 web 服务器和数据库服务器构成,以此能够为其一百万用户提供天天两千万次的动态页面访问量。 Memcached 大大下降了数据库的负担,加快了页面的加载时间,提升了资源利用效率,加快了缓存未命中状况下的数据库访问时间。 node

1.2 哪里能够获得 memcached ?

memcached 能够从它的官方网站下载: http://www.danga.com/memcached/download.bml . python

1.3 怎样安装 memchched ?

能够参照 yanwenhan 的 memcached 安装 : http://yanwenhan.iteye.com/blog/160891 . mysql

1.4 哪些环境能够运行 memcached ?

随便哪里,只要你有空闲的内存就能够! Memcached 跑在 linux , BSD , windows 上。它通常不多占用 CPU 资源,这样你就能够运行在任何有空闲内存的地方了。 linux

1.5 为何要运行 memcached ?

若是你有一个高流量的网站,大多数的访问会形成数据库高负荷的情况。这时 memcached 可以减轻你数据库的压力。 web

1.6 怎样访问 memcached ?

你通常都会用一个客户端类库去访问一个或多个 memcached 服务器。 算法

参考 http://www.socialtext.net/memcached/index.cgi?clients sql

或者 http://danga.com/memcached/apis.bml 数据库

上面的地址列出了现有的类库,他们是用 Perl , C , C# , PHP , Python , Java , Ruby 和 Postgresql 对存储过程以及触发器实现的。

你也能够本身开发客户端类库来实现 memecached 协议,相关协议文档:http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

1.7 怎样把 memcached 看成数据库使用?

若是你不想把 memcached 看成缓存使用,而想把它当作数据存储来使用,那你因该直接使用数据库。 MySQL 集群也能够提供相似 memcached 的功能(尽管不是那么容易安装)并支持 HA (高可靠性)数据存储。

1.8 我能枚举遍历 memcached 服务器内的存储项吗?

不能。 memcached 不支持也不会计划支持这一特性。由于这会相对减慢服务器的速度以及阻塞操做(比较memcached 正在执行的每样操做)。如上所述, memcached 是缓存而不是数据库。虽然 Tugela 是一个源于memcached 的系统,但它有点慢,由于它使用 memcached 的方式有点像数据库。

固然, memcached 彻底是一个软件,因此从某种角度说,最终的答案可能会是“能遍历 memcached ”,可是,枚举遍历数据项将会减慢和阻塞服务器。对于开发或测试服务器来讲,这不算是问题。但对 99.9% 真正的部署服务器来讲,回答是“不能”。

2. 集群构架问题

2.1 memcached 怎样工做?

memcached 的神奇之处在于它的两步骤哈希方法。它的行为让人以为它好像是一张巨大的,经过键值对查找的哈希表。给定一个键( key ),存储或取出任意数据。

当查找 memcached 时,客户端首先参照整个 memcached 服务器列表计算出键的哈希值。一旦它选择了一个memcached 服务器,客户端就发起请求,服务器根据键值在内部查找对应的数据项。

好比,咱们有客户端 1 , 2 , 3 和服务器 A , B , C :

客户端 1 想要设置键 "foo" 值 "barbaz" 。客户端 1 参照服务器列表( A , B , C )来哈希 "foo" ,根据 "foo" 的哈希值,最终选择了服务器 B 。而后客户端 1 直接链接服务器 B ,设置键 "foo" ,值 "barbaz" 。

下一步,客户端 2 想要获得键 "foo" 对应的值。客户端 2 运行与客户端 1 相同的客户端类库,而且使用相同的服务器列表( A , B , C )。计算获得键 "foo" 相同的哈希值,这样就知道了这个键在服务器 B 上。而后直接请求键 "foo" 获得相应的值 "barbaz" 。

不一样的客户端实如今 memcached 服务器内存储数据的方式不一样。一些客户端实现的哈希算法也不同,但服务器端老是相同。

最后, memcached 自己被实现为基于事件的,非阻塞的服务器。这是一种用来解决 C10K 问题而且能够调整为饥饿方式( scale like crazy )的框架。

以上这些最大的好处是什么?

仔细阅读上述条目( memcached 怎样工做?)。在应付大系统时, memcached 最大的好处是其拥有的扩展能力。由于客户端作了哈希计算,咱们彻底能够把许多 memcached 节点加入到集群之中。集群的节点之间没有会致使过载的相互链接,也没有会引发向心聚爆( implode )的多点传送协议。 It Just Works. Run out of memory? Add a few more nodes. Run out of CPU? Add a few more nodes. Have some spare RAM here and there? Add nodes!

2.2 memcached 的缓存策略是什么?

memcached 的缓存结构是 LRU (最近最少使用)加上到期失效策略。当你在 memcached 内存储数据项时,你可能会声明它在缓存的失效时间。能够是永久或是存活到将来某一时段。若是 memcached 服务器用完分配的内存,失效的数据被首先替换,而后是最近未使用的数据。

2.3 memcached 的冗余是怎样实现的?

没有冗余!很惊讶! memcached 是你应用的缓存层。在设计上它没有任何的数据冗余的概念。若是一个节点丢失了它的数据,你能够从新从数据源获取全部数据。你的应用可以在丢失 memcached 实例的状况继续运行,这一点尤为要注意。 Don't write awful queries and expect memcached to be a fix-all! If you're worried about having too much of a spike in database usage during failure, you have some options. You can add more nodes (lessen impact of losing one), hotspares (take over IP address when down), etc.

2.4 memcached 是怎样应对失效转移的?

没有应对! :) 在 memcached 节点失效时,集群根本不作任何有关失败转移的事情。应对的行为彻底取决于用户。当节点失效时,下面有几种方案供你选择。

       2.4.1 忽略它!在失效节点恢复或被替换前,你有许多节点能够应对这个节点失效所带来的影响。

       2.4.2 从服务器列表中移除失效的节点。千万当心!默认状况下,客户端增长或删除服务器列表会让你的缓存失效!由于用来作哈希参照的服务器列表已经改变,大多数键可能会被哈希出不一样的值而被定为到不一样的服务器。能够在同一时间重启你全部的节点来恢复。

       2.4.3 能够用相同 IP 地址的节点替换失效的节点。这样能够防止哈希紊乱。

       2.4.4 使用一致的哈希算法来增长删除集群中的节点。参考其余的哈希算法。

2.5 怎样从 memcached 中导出和导入批量数据?

你不能这样作! memcached 是一个咱们称之为非阻塞服务器。 memcached 必定会十分仔细地考虑任何能致使服务器即刻暂停应答请求的功能。一般状况下,批量导入数据不会是你真正须要的!考虑一下若是你的数据在导出导入中有所改变的话,你处理的就是脏数据了。另外就是你怎样管理在数据导入期间过时的数据。

因此,导入导出功能并不像你一般想的那样有用。但有个场景它变得十分有用。若是你有大量的不变的数据,导入导出数据可能会有帮助。虽然这根本不是典型的应用场景,但它倒是常常发生,因此这个特性可能会在未来出现。

Steven Grimm 给了个很好的例子:

http://lists.danga.com/pipermail/memcached/2007-July/004802.html

2.6 memcached 的验证机制是怎样工做的?

没有验证机制! memcached 位于你应用的下层。彻底没有验证机制的部分缘由是客户端和服务器端轻量化。这样创建新的链接会很快,也没有服务器端的配置。

若是不想要严格控制访问,你可使用防火墙,或者能够经过 unix 的 domain socket 来为 memcached 监听。 

2.7 什么是 memcached 的线程?为何我要用它们?

线程规则!感谢 Steven Grimm 和 Facebook , memcached 1.2 以及更高的本版拥有线程的操做模式。在这儿我不会过多地涉及细节,由于我可能会弄错。线程系统容许 memcached 利用多核 CPU 以及在他们之间共享缓存。它应用一个十分简单的锁机制来控制某些值须要被更新时的同步问题。对比在一台物理机器上运行多个节点实例,线程模式可使多核 CPU 变的更加有效。

若是你没有出现重负荷的状况,你可能不用去配置线程。若是你用庞大的硬件运行一个庞大的 web 网站,你可能就会看见好处了。

2.8 在使用 memcached 时,我可能会碰到什么限制?

你可能会看到的最简单的限制是对键以及数据项的大小限制。键被限制在 250 字符以内。数据项不能超过 1M ,由于这是最大的块( slab )值。

2.9 我能在多个服务器上使用不一样大小的缓存吗? memcached 会有效地利用大内存的服务器吗?

memcached 的哈希算法决定了键存储在哪台服务器上,它不会去考虑服务器的内存大小。 But a workaround may be to run multiple memcached instances on your server with more memory with each instance using the same size cache as all your other servers.

2.10 什么是二进制协议?我应该关注吗?

这个问题的最佳信息在邮件列表上: http://lists.danga.com/pipermail/memcached/2007-July/004636.html

写这篇文章的时候,这个问题还没被解决,没有客户端被发布。

二进制协议是一种高效的,可信赖的 c/s 协议用来加速 CPU 时间。从 Facebook 的测试来看,解析 ASCII 协议是memcached 对 CPU 时间的最大消耗。因此为何不去提升它呢? :)

2.11 memcached 的内存分配是怎样工做的?为何不用 malloc 或 free 呢?为何要用 slabs ?

事实上,这是编译时的选项。默认状况下 memcached 用内部的 slab 做为分配器的。你真的很须要使用内建的slab 分配器。一开始, memcached 的确用 malloc/free 分配全部东西。然而,这并不能同操做系统的内存管理器很好地工做。你的操做系统花在查找连续内存块用以 malloc() 的时间超出了 memcached 自己的操做运行时间。

slab 分配器就是用来解决这个问题的。在 memcached 内部以块为单位来分配和重用内存。由于内存被分为不一样大小的 slab ,若是你的数据项没有彻底符合服务器选择的 slab 的大小,这的确会致使浪费内存。 Steven Grimm已经在这方面作了至关有效的改进。

一些对 slab 的改进和折中方案在邮件列表中: http://lists.danga.com/pipermail/memcached/2006-May/002163.html

http://lists.danga.com/pipermail/memcached/2007-March/003753.html

若是你试图用 malloc/free 分配内存,你能够在构建中定义 'USE_SYSTEM_MALLIC' 。它可能没有通过很好测试,因此不用期望能获得开发者的支持。

3. 性能问题

3.1 为何 memcached 没有个人数据库快?

在一对一的比较中, memcached 可能没有你的 SQL 查询快。然而,这并非它的目标。 memcached 的目标是可伸缩性。随着链接和请求的增长, memcached 将会表现出比单独的数据库解决方案更好的性能。在决定memcached 不适合你的应用以前,请将你的代码放在高并发的环境中测试。

4. 客户端类库

4.1 我能用不一样的客户端获得相同的数据吗?

技术上是能够的,可是有些问题你可能会碰到:

       4.1.1 不一样的类库可能采用不一样的方式序列号数据,好比, Perl 的 Cache::Memcached 会用 Storable 序列化复杂结构对象。其余语言的客户端极可能不能读出这些格式的数据,你可能会考虑用简单的 String 格式序列化对象,这须要外部类库好比 JSON 和 XML 。

       4.1.2 一样,你的数据从某个客户端过来的时候可能被压缩了,但另外一个客户端却没有压缩。

       4.1.3 不一样的类库可能采用不一样的哈希算法。若是你正在链接多个服务器,你的键极可能被不一样语言的客户端相应地哈希后并存储。不一样语言的客户端可能采用不一样的策略来选择服务器进行存储,因此对同一个键来讲, Perl的客户端可能选择服务器 A ,而 Python 客户端选择服务器 B 。 Perl 的 API 还容许你对不一样的服务器设置不一样的权重,这也多是形成这一问题的因素。

4.2 什么是一致的哈希客户端?

参考 http://www.last.fm/user/RJ/journal/2007/04/10/392555

5. 哈希 / 键分布

5.1 数据项到期失效,何时失效的数据项会从缓存中删除?

memcached 使用懒失效。当客户端请求数据项时, memcached 在返回数据前会检查失效时间来肯定数据项是否已经失效。

一样地,当添加一个新的数据项时,若是缓存已经满了, memcached 就会先替换失效的数据项,而后才是缓存中最少使用的数据项。

6. 命名空间

6.1 memcached 不支持命名空间。然而有几种选择能够模仿他们。

6.1.1 用键的前缀模仿命名空间,在键以前加入有意义的前缀。

6.1.2 用命名空间删除数据项

尽管 memcached 不支持使用任何类型的通配符或命名空间来完成删除操做,但有一些技巧模拟他们。

// 在 PHP 中使用一个叫 foo 的命名空间:
$ns_key = $memcache->get("foo_namespace_key");

// if not set, initialize it
if($ns_key===false) $memcache->set("foo_namespace_key", rand(1, 10000));
$my_key = "foo_".$ns_key."_12345";

// 清除命名空间:
$memcache->increment("foo_namespace_key");

7. 应用设计

7.1 在设计应用时,针对缓存有哪些东西是我应该考虑的?

7.1.1 缓存简单的查询结果

查询缓存存储了给定查询语句对应的整个结果集。它最合适缓存那些常常被用到,但不会改变的 SQL 语句,好比载入特定的过滤内容。

$key = md5('SELECT * FROM rest_of_sql_statement_goes_here');

if ($memcache->get($key)) {
    return $memcache->get($key);`
} else {
    // Run the query and transform the result data into your final dataset form`
    $result = $query_results_mangled_into_most_likely_an_array`
    $memcache->set($key, $result, TRUE, 86400); // Store the result of the query for a day`
    return $result;`
}

记住,若是查询语句对应的结果集改变,该结果集不会展示出来。这种方法不老是有用,但它确实让工做变得比较快。

7.1.2 缓存简单的基于行的查询结果

基于行的缓存会检查缓存数据的标识符列表,那些在缓存中的行能够直接取出,不在缓存中的行将会从数据库中取出并以他们本身的键缓存他们,最后加入到最终的数据集中返回。随着时间的过去,大多数数据都会被缓存,这也意味着相比与数据库,查询语句会更多地从 memcached 中获得数据行。若是数据是至关静态的,咱们能够设置一个较长的缓存时间。基于行的缓存模式对下面这种搜索状况特别有用:数据集自己很大或是数据集是从多张表中获得,而数据集取决于查询的输入参数可是查询的结果集之间的有重复部分。

好比,若是你有用户 A , B , C , D , E 的数据集。

你去点击一张显示用户 A , B , E 信息的页面。首先, memcached 获得 3 个不一样的键,每一个对应一个用户去缓存中查找,所有未命中。而后就到数据库中用 SQL 查询获得 3 个用户的数据行,并缓存他们。

如今,你又去点击另外一张显示显示 C , D , E 信息的页面。当你去查找 memcached 时, C , D 的数据并无被命中,但咱们命中了 E 的数据。而后从数据库获得 C , D 的行数据,缓存在 memcached 中。

至此之后,不管这些用户信息怎样地排列组合,任何关于 A , B , C , D , E 信息的页面均可以从 memcached获得数据了。

Action flood control

Flood control is the process of throttling user activity, usually for load management. We first try to add a memcache key that uniquely identifies a user and times out after a given interval. If that succeeds, there is no identical key, and thus the user should be allowed to do the action. If the add fails, the user is still in the flood control interval, so shouldn't be allowed to continue their action. If all else fails and the key cannot be added or retrieved, something's wonky with memcache and it's up to you to decide whether to allow action or not (suggested yes to prevent long term memcache issues from stopping all actions).

So, if user A makes a comment in thread 7, and you don't want them to be able to comment again for another 60 seconds:

'add' a key (eg) 'noflood:A:7' into memcached. If you get a SUCCESS, the user may post. If you get a NOT_STORED (but not an error!), the key still exists and the user should be warned.

Note you may also try fetching a key and doing incr/decr on it if a user should only be allowed to perform an action a certain number of times before being throttled.

7.1.3 缓存的不是 SQL 数据

当你第一次缓存你手头除了 SQL 之外的其余结果集时,你可能并无在乎。是的,你能够也应该存储其余的数据。

若是你正在构建一张显示用户信息的页面,你可能获得一段关于用户的信息(姓名,生日,家庭住址,简介)。而后你可能会将 XML 格式的简介信息转化为 HTML 格式,或作其余的一些工做。相比单独存储这些属性,你可能更愿意存储通过渲染的数据块。那时你就能够简单地取出被预处理后的 HTML 直接填充在页面中,这样节省了宝贵的 CPU 时间。

7.2 使用分层的缓存

不少时候你可使用局部缓存。咱们知道 memcached 能够高速处理大量的缓存数据,可是有时你仍是要考虑维护多层的缓存结构。

Peter Zaitsev 已经写了有关本地运行 PHP 的 APC 和本地运行 memcached 的速度比较,使用二者的好处请参考

http://www.mysqlperformanceblog.com/2006/08/09/cache-performance-comparison/

http://www.mysqlperformanceblog.com/2006/09/27/apc-or-memcached/

通常比只有少许数据(产品分类,链接信息,服务器状态变量,应用配置变量),这些信息几乎每页都会访问。缓存他们来让他们尽量接近处理器是有意义的 , 这能够帮助减小生成页面的时间,而且在 memcached 失效的状况下能够增长可靠性。

7.3 当你的数据更新时更新你的缓存

你能够作的一个很重要的提升,它能够确保你的缓存无缝地集成到你的应用中去,它就是在数据库数据更新时同步缓存的数据。

用户 A 编辑了他的用户信息。当他保存信息到数据库时,你可能须要更新缓存中的数据,或是简单地删除老的用户信息。若是你立刻更新数据,你要防止从数据库读取那些刚刚更新过的数据。当用户习惯性地从新载入他们的用户信息来确认是否修改为功时,数据将从缓存中直接取出,这时他们得到了最新的数据。

这很了不得,由于没有用户想看过时的数据,不是吗?

7.4 条件竞争和陈旧的数据

当你设计须要缓存数据的应用时,怎样处理条件竞争和陈旧的数据变的很重要。

假设你缓存了显示在边条( sidebar )上最近 5 条评论,你决定每一分钟刷新一次数据。然而,你忘记了边条每秒被刷新 50 次 ! 所以,一旦 60 秒过去,当即就会有 10 多个进程执行相同的 SQL 查询来重装缓存内的数据。每当缓存内容失效时,就会致使一堆的 SQL 查询操做。

更糟的是,你可能有多个进程在更新相同的数据,其中有一个进程更新了错误的数据。这时你就有陈旧的过时的数据要处理了。

组装和重组缓存中的数据时,须要提醒的是检查 memcached ,获取 SQL ,缓存数据,这些操做根本不在一个原子内。

How to prevent clobbering updates, stampeding requests

So how does one prevent clobbering your own updates or stampeding during a cache miss? The easiest answer is to avoid the problem. Don't set caches to expire, and update them via cron, or as data is updated. This does not eliminate the possibility of a stampede, but removes it from becoming the norm.

Some great ideas from the mailing list also underline another approach:

If you want to avoid a stampede if key A expires for its common case (a timeout, for example). Since this is caused by a race condition between the cache miss, and the amount of time it takes to re-fetch and update the cache, you can try shortening the window.

First, set the cache item expire time way out in the future. Then, you embed the "real" timeout serialized with the value. For example you would set the item to timeout in 24 hours, but the embedded timeout might be five minutes in the future.

Then, when you get from the cache and examine the timeout and find it expired, immediately edit the embedded timeout to a time in the future and re-store the data as is. Finally, fetch from the DB and update the cache with the latest value. This does not eliminate, but drastically reduces the amount of time where a stampede can occur.

A decent python example can be found here: http://www.djangosnippets.org/snippets/155/

If you have a lot of data excelling at causing this problem, you might also consider using MySQL Cluster for it, or a tiered caching approach

Another (pretty cool!) idea is to use Gearman, as noted on the mailing list:http://lists.danga.com/pipermail/memcached/2007-July/004858.html

7.5 模拟带锁的添加命令

若是你实在须要锁,你能够经过“添加”命令模仿锁的功能。尽管在未命中的状况下它不是那么有用,但若是你用它缓存日常的数据(应用服务器池的元数据)那仍是有用的。

好比,你要更新键 A 。

       7.5.1 添加一个 "lock:A" 的键,这个键有一个持续几秒的过时时间(足够长以使你能完成计算和更新,也不要很长,由于若是锁进程挂了,这个键不会当即释放)

       7.5.2 若是添加操做成功了,你就拥有了锁:

              从缓存获取键 A 的数据。

              在客户端更改数据。

              更新缓存键 A 的数据。

              删除键 "lock:A" ,若是你不须要当即再次更新,就让它存活直到失效。

     7.5.3 若是添加操做失败,说明有人获取了锁。这时让应用作些合适的事,好比返回老数据,等待后重试,或是其余的。

以上这些操做相似 MySQL 将 GET_LOCK 的 timeout 值设置成 0 。没有办法在 memcached 中经过互斥锁模拟GET_LOCK() 的 timeout 操做。

7.6 预热你的缓存

若是你有一个很高访问率的站点,而且你正想加入故障恢复功能或是其余全新的功能,你最终可能会碰到空缓存的问题。一开始缓存是空的,而后一大群人点击你的站点,在填充缓存的过程当中,你的数据库可能会承受不住压力。为了解决这一问题,你能够试试任何可行的方法来 " 温暖 " 你的数据库。

你能够写一些脚原本缓存通用的页面。你也能够写一个命令行工具来填充缓存。两种方法均可能对你有帮助。你能够在高峰时刻在缓存里填充一些内容。

相关文章
相关标签/搜索