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!
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.
memcached 的哈希算法决定了键存储在哪台服务器上,它不会去考虑服务器的内存大小。 But a workaround may be to run multiple memcached instances on your server with more memory with each instance using the same>
什么是二进制协议?我应该关注吗?
这个问题的最佳信息在邮件列表上: http://lists.danga.com/pipermail/memcached/2007-July/004636.html
写这篇文章的时候,这个问题还没被解决,没有客户端被发布。
用命名空间删除数据项
尽管 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");
应用设计
在设计应用时,针对缓存有哪些东西是我应该考虑的?
缓存简单的查询结果
查询缓存存储了给定查询语句对应的整个结果集。它最合适缓存那些经常被用到,但不会改变的 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;`
}
记住,如果查询语句对应的结果集改变,该结果集不会展现出来。这种方法不总是有用,但它确实让工作变得比较快。
缓存简单的基于行的查询结果
基于行的缓存会检查缓存数据的标识符列表,那些在缓存中的行可以直接取出,不在缓存中的行将会从数据库中取出并以他们自己的键缓存他们,最后加入到最终的数据集中返回。随着时间的过去,大多数数据都会被缓存,这也意味着相比与数据库,查询语句会更多地从 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>
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.
缓存的不是 SQL 数据
当你第一次缓存你手头除了 SQL 以外的其他结果集时,你可能并没有在意。是的,你可以也应该存储其他的数据。
如果你正在构建一张显示用户信息的页面,你可能得到一段关于用户的信息(姓名,生日,家庭住址,简介)。然后你可能会将 XML 格式的简介信息转化为 HTML 格式,或做其他的一些工作。相比单独存储这些属性,你可能更愿意存储经过渲染的数据块。那时你就可以简单地取出被预处理后的 HTML 直接填充在页面中,这样节省了宝贵的 CPU 时间。
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>
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!)>
模拟带锁的添加命令
如果你实在需要锁,你可以通过“添加”命令模仿锁的功能。尽管在未命中的情况下它不是那么有用,但如果你用它缓存平常的数据(应用服务器池的元数据)那还是有用的。
比如,你要更新键 A 。
1. 添加一个 "lock:A" 的键,这个键有一个持续几秒的过期时间(足够长以使你能完成计算和更新,也不要很长,因为如果锁进程挂了,这个键不会立即释放)
2. 如果添加操作成功了,你就拥有了锁:
从缓存获取键 A 的数据。
在客户端更改数据。
更新缓存键 A 的数据。
删除键 "lock:A" ,如果你不需要立即再次更新,就让它存活直到失效。
3. 如果添加操作失败,说明有人获取了锁。这时让应用做些合适的事,比如返回老数据,等待后重试,或是其他的。
以上这些操作类似 MySQL 将 GET_LOCK 的 timeout 值设置成 0 。没有办法在 memcached 中通过互斥锁模拟 GET_LOCK() 的 timeout 操作。
预热你的缓存