當前位置: 首頁> 最新文章列表> 如何用apcu_cas函數避免緩存競爭條件

如何用apcu_cas函數避免緩存競爭條件

gitbox 2025-05-29

什麼是緩存競爭條件?

當多個進程或線程同時嘗試讀取緩存未命中並寫入緩存時,可能出現多個進程同時執行相同的慢查詢或複雜計算,造成資源浪費。這種情況被稱為緩存競爭條件。

例如:

 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函數簡介

apcu_cas的全稱是“Compare And Swap”,它是一種原子操作,用於比較緩存值是否等於期望值,如果相等則替換為新值。這個操作能避免多個請求同時修改緩存帶來的競爭問題。

函數原型:

 bool apcu_cas(string $key, mixed $old, mixed $new)
  • $key :緩存鍵

  • $old :期望的舊值

  • $new :將要替換的新值

  • 返回true表示替換成功, false表示舊值不匹配,替換失敗。


如何利用apcu_cas避免競爭條件?

我們通過設定一個“鎖”標誌位實現互斥訪問緩存。具體思路:

  1. 讀取緩存,如果存在直接返回。

  2. 如果緩存不存在,嘗試設置一個“鎖”標誌,表示緩存正在生成。

  3. 設置“鎖”失敗,說明其他進程已在生成緩存,等待或重試。

  4. 設置成功後,執行慢查詢,生成數據。

  5. 將數據寫入緩存,釋放“鎖”標誌。

  6. 返回數據。


示例代碼演示

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緩存機制更加健壯、高效,顯著避免緩存擊穿導致的性能瓶頸。


<code> <?php function getCacheData() { $cacheKey = 'my_cache_key'; $lockKey = 'my_cache_key_lock';
 $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>