當多個進程或線程同時嘗試讀取緩存未命中並寫入緩存時,可能出現多個進程同時執行相同的慢查詢或複雜計算,造成資源浪費。這種情況被稱為緩存競爭條件。
例如:
if (!apcu_exists('my_cache_key')) {
$data = get_data_from_db(); // 複雜查詢
apcu_store('my_cache_key', $data);
}
echo apcu_fetch('my_cache_key');
在高並發環境下,多個請求同時發現緩存不存在,會同時執行數據庫查詢,造成性能問題。
apcu_cas的全稱是“Compare And Swap”,它是一種原子操作,用於比較緩存值是否等於期望值,如果相等則替換為新值。這個操作能避免多個請求同時修改緩存帶來的競爭問題。
函數原型:
bool apcu_cas(string $key, mixed $old, mixed $new)
$key :緩存鍵
$old :期望的舊值
$new :將要替換的新值
返回true表示替換成功, false表示舊值不匹配,替換失敗。
我們通過設定一個“鎖”標誌位實現互斥訪問緩存。具體思路:
讀取緩存,如果存在直接返回。
如果緩存不存在,嘗試設置一個“鎖”標誌,表示緩存正在生成。
設置“鎖”失敗,說明其他進程已在生成緩存,等待或重試。
設置成功後,執行慢查詢,生成數據。
將數據寫入緩存,釋放“鎖”標誌。
返回數據。
function getCacheData() {
$cacheKey = 'my_cache_key';
$lockKey = 'my_cache_key_lock';
// 1. 先嘗試讀取緩存
$data = apcu_fetch($cacheKey, $success);
if ($success) {
return $data;
}
// 2. 嘗試通過apcu_cas設置鎖,防止多個請求同時生成緩存
// 先尝试設置鎖标志位为false(初始狀態)
apcu_add($lockKey, false);
// 期望鎖為false,嘗試換成true(上鎖)
if (!apcu_cas($lockKey, false, true)) {
// 說明其他請求已獲得鎖,等待緩存生成
// 可以簡單sleep或者循環等待
usleep(100000); // 等待100毫秒
return getCacheData(); // 遞歸重試
}
// 3. 獲得鎖,執行慢查詢
$data = get_data_from_db();
// 4. 寫緩存
apcu_store($cacheKey, $data);
// 5. 釋放鎖(設置鎖为false)
apcu_store($lockKey, false);
return $data;
}
function get_data_from_db() {
// 模擬慢查詢
sleep(1);
return ['time' => time(), 'data' => 'sample'];
}
以上代碼中, apcu_cas保證了“鎖”的原子切換,避免多個請求同時執行慢查詢,避免緩存競爭。
緩存競爭條件是高並發緩存場景中的常見問題。
apcu_cas是實現原子操作的利器,可以實現高效鎖機制。
通過“鎖”機制,保證只有一個請求能執行慢查詢並寫緩存,其他請求等待或重試。
該方法適合單機環境的APCu緩存,分佈式環境可考慮更複雜的鎖方案。
掌握apcu_cas的使用,可以讓你的PHP緩存機制更加健壯、高效,顯著避免緩存擊穿導致的性能瓶頸。
$data = apcu_fetch($cacheKey, $success);
if ($success) {
return $data;
}
apcu_add($lockKey, false);
if (!apcu_cas($lockKey, false, true)) {
usleep(100000);
return getCacheData();
}
$data = get_data_from_db();
apcu_store($cacheKey, $data);
apcu_store($lockKey, false);
return $data;
}
function get_data_from_db() {
sleep(1);
return ['time' => time(), 'data' => 'sample'];
}
?>
</code>