在高并发环境下,保持 Redis 缓存和 MySQL 数据库的数据一致性是一个复杂但至关重要的任务。下面是对这一问题的详细讲解,并结合 PHP 代码示例来展示如何解决这些一致性问题。
Redis 缓存和 MySQL 数据库的主要挑战在于:
示例代码:
function getFromCacheOrDb($key) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 从缓存中读取数据
$cachedData = $redis->get($key);
if ($cachedData !== false) {
return $cachedData;
}
// 缓存未命中,从数据库中查询
$data = getDataFromDb($key);
if ($data) {
$redis->setex($key, 3600, $data); // 缓存 1 小时
}
return $data;
}
function updateData($key, $newData) {
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// 更新数据库
$stmt = $db->prepare("UPDATE table_name SET value = :value WHERE key = :key");
$stmt->execute([':value' => $newData, ':key' => $key]);
// 删除缓存
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->del($key);
}
优点:
缺点:
延迟双删策略是针对 Cache Aside 模式的改进,主要用于防止缓存和数据库的更新顺序导致数据不一致问题:
示例代码:
function updateDataWithDelayDelete($key, $newData) {
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// 更新数据库
$stmt = $db->prepare("UPDATE table_name SET value = :value WHERE key = :key");
$stmt->execute([':value' => $newData, ':key' => $key]);
// 删除缓存
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->del($key);
// 延迟再次删除缓存
usleep(500 * 1000); // 延迟 500ms
$redis->del($key);
}
优点:
缺点:
分布式锁可以确保在高并发情况下,只有一个线程可以进行数据和缓存的更新操作。Redis 提供了实现分布式锁的能力。
示例代码:
function updateDataWithLock($key, $newData) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = "lock:" . $key;
if ($redis->set($lockKey, 1, ['nx', 'ex' => 5])) { // 获取锁,超时 5 秒
try {
// 更新数据库
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $db->prepare("UPDATE table_name SET value = :value WHERE key = :key");
$stmt->execute([':value' => $newData, ':key' => $key]);
// 删除缓存
$redis->del($key);
} finally {
// 释放锁
$redis->del($lockKey);
}
} else {
// 获取不到锁,稍后重试
return false;
}
return true;
}
优点:
缺点:
使用消息队列来处理数据更新,使得写操作与缓存更新异步进行。这种方式可以解耦数据写入和缓存更新的操作。
示例流程:
优点:
缺点:
在高并发场景下,确保 Redis 缓存和 MySQL 数据库的一致性需要结合多种策略:
根据实际业务需求和并发压力选择合适的策略,并在生产环境中进行充分的测试和优化,以确保系统的稳定性和数据一致性。