当多个进程或线程同时尝试读取缓存未命中并写入缓存时,可能出现多个进程同时执行相同的慢查询或复杂计算,造成资源浪费。这种情况被称为缓存竞争条件。
例如:
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>