在用memcache , 不可否认,memcache 在实际应用中的表现非常出色。不过也许正是因为其对速度和性能的要求过于严格,导致 memcache 的可操作性不是那么强。PHP 在使用的时候,也只能有限的使用 add、delete、replace、flush 等几个简单的方法。
memcache 官方是不建议我们手动去删除缓冲内容的, 因为官方的 api 中有关删除的方法只有 delete 和flush,前者是在知道 key 的情况下删除一个对象的值,后面是清除服务器上所有对象。
不过很多情况下,我们需要从服务器上删除一部分对象,又不希望把整个服务器上的对象都清空,官方的 FAQ 上提供了一个“伪命名空间(Simulating Namespaces )” 的作法。
- <?php
- //生成一个用来保存 namespace 的 key
- $ns_key = $memcache->get("product_namespace_key");
- //如果 key 不存在,则创建
- if ($ns_key === false) $memcache->set("product_namespace_key", time());
- //根据 namespace_key 生成真正的 key
- $my_key = $ns_key . md5($sql);
- // 获取当前key下的缓存
- $val = $memcache->get($my_key);
- if (!$val) {
- $memcache->set($my_key, $dbValue, 600);
- }
- //需要删除整个namespace里的对象的时候,这样处理即可:
- $memcache->increment("product_namespace_key");
- ?>
memcache缓存失效问题
在大并发的场合,当cache失效时,大量并发同时取不到cache,会同一瞬间去访问db并回设cache,可能会给系统带来潜在的超负荷风险。解决方法:
在load db之前先add一个mutex key, mutex key add成功之后再去做加载db, 如果add失败则sleep之后重试读取原cache数据。为了防止死锁,mutex key也需要设置过期时间。伪代码如下
- if (memcache.get(key) == null) {
- // 3 min timeout to avoid mutex holder crash
- if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
- value = db.get(key);
- memcache.set(key, value);
- memcache.delete(key_mutex);
- } else {
- sleep(50);
- retry();
- }
- }
方法二
在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。伪代码如下
- v = memcache.get(key);
- if (v == null) { //不存在
- if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
- value = db.get(key);
- memcache.set(key, value);
- memcache.delete(key_mutex);
- } else {
- sleep(50);
- retry();
- }
- } else {
- if (v.timeout <= now()) { //过期
- if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
- // extend the timeout for other threads
- v.timeout += 3 * 60 * 1000;
- memcache.set(key, v, KEY_TIMEOUT * 2);
- // load the latest value from db
- v = db.get(key);
- v.timeout = KEY_TIMEOUT;
- memcache.set(key, value, KEY_TIMEOUT * 2);
- memcache.delete(key_mutex);
- } else {
- sleep(50);
- retry();
- }
- }
- }