Memcache 原生不支援命名空間,但在大型應用中常需批次清除特定群組的快取。本文介紹利用版本號機制模擬命名空間,並針對高併發下的快取失效穿透問題,提供兩種經典解決策略。
核心概念:為每個邏輯群組建立一個「版本號鍵」,所有該群組的快取鍵皆包含此版本號。需清除群組時,僅需更新版本號,舊快取鍵自然失效,配合 Memcache LRU 機制自動淘汰。
class Action {
public function index() {
global $mc_wr;
// 取得或初始化命名空間版本號
$ns_key = $mc_wr->get("foo_namespace_key");
if ($ns_key === false) {
$mc_wr->set("foo_namespace_key", time());
$ns_key = time();
}
// 組合帶版本號的唯一快取鍵
$otherParms = 'select * from user LIMIT 1';
$my_key = "foo_{$ns_key}_" . md5($otherParms);
// 讀取快取
$val = $mc_wr->get($my_key);
if (!$val) {
$value = 'wangdekang_' . time();
// 設定 600 秒過期,並加入隨機秒數防止雪崩
$mc_wr->set($my_key, $value, 600, rand(0, 60));
}
echo $val;
}
public function clear_ns() {
global $mc_wr;
// 更新版本號,令該命名空間下所有快取瞬間失效
$mc_wr->set('foo_namespace_key', time());
}
}
高併發場景下,熱門快取過期瞬間,大量請求同時穿透至資料庫,造成系統過載。以下提供兩種互斥鎖策略:
快取未命中時,嘗試寫入短效互斥鍵(如 3 分鐘)。成功者負責回源資料庫並更新快取;失敗者短暫休眠後重試讀取快取。
if ($memcache->get($key) === null) {
// 3 分鐘互斥鍵過期,防止持有者異常導致死鎖
if ($memcache->add($key_mutex, 1, 180)) {
$value = $db->get($key);
$memcache->set($key, $value);
$memcache->delete($key_mutex);
} else {
usleep(50000); // 50ms
// 重試邏輯...
}
}
在快取值內部嵌入 timeout1(邏輯過期),設定比 Memcache 實際過期 timeout2 更短。讀取時若發現 timeout1 已過期,先延長 timeout1 並寫回快取,再回源資料庫更新真實資料。此法可將「穿透窗口」縮減至單一請求。
$v = $memcache->get($key);
if ($v === null) {
// 同方法一:互斥鎖回源
} else {
if ($v['timeout'] <= time()) {
// 邏輯過期,搶鎖延長並回源
if ($memcache->add($key_mutex, 1, 180)) {
$v['timeout'] += 180; // 延長邏輯過期
$memcache->set($key, $v, $real_timeout * 2); // 寫回延長後的快取
$fresh = $db->get($key); // 回源
$fresh['timeout'] = $real_timeout;
$memcache->set($key, $fresh, $real_timeout * 2); // 寫入新資料
$memcache->delete($key_mutex);
} else {
usleep(50000);
// 重試...
}
}
}
set() 最後一個參數加入隨機值,避免大量快取同一秒失效引發雪崩實務上建議根據業務併發量選擇:中小流量採方法一即可;核心高併發介面建議實作方法二,並搭配監控告警。