php解决缓存击穿的问题

打印 上一主题 下一主题

主题 656|帖子 656|积分 1968

缓存击穿是指缓存中没有的数据,而查询非常频繁的数据,导致大量的请求落到了数据库上,因此很容易导致数据库连接数暴增,甚至导致宕机。
下面是 PHP 解决缓存击穿问题的一般解决方案:
  1. // 获取 Key
  2. $key = 'my_key';
  3. // 根据 Key 从 Redis 中获取数据
  4. $data = $redis->get($key);
  5. // 如果数据不存在,尝试从DB中获取数据
  6. if (!$data) {
  7.     // 尝试获取缓存锁
  8.     $lockKey  = 'my_redis_lock';
  9.     $locked   = $redis->set($lockKey, 1, array('nx', 'ex'=>10));
  10.     // 如果获得缓存锁,再从DB中获取
  11.     if($locked) {
  12.         $data = '查询数据库的结果';
  13.         // 将查询结果写入 Redis 缓存中,并设置永久过期
  14.         $redis->setex($key, 0, $data);
  15.         // 删除锁
  16.         $redis->del($lockKey);
  17.     }
  18.     // 如果获取锁失败,则说明另一个进程已经重新写入了缓存,使用之前写入的数据即可
  19.     else {
  20.         // 设置一个默认的过期时间,以避免缓存被永久锁定
  21.         $redis->setex($key, 60, 'default value');
  22.         // 等待一段时间之后,重新获取缓存
  23.         sleep(1);
  24.         $data = $redis->get($key);
  25.     }
  26. }
复制代码
该代码逻辑如下:

1.根据指定 Key 从 Redis 中获取数据;
2.如果数据不存在,设置一个缓存互斥锁,防止大量并发访问 DB,再次从 DB/ 获取数据;
3.如果获取到缓存锁,则从 DB 中获取数据,并将数据写入 Redis 中;
4.如果获取不到缓存锁,则说明其他进程已经获取到了缓存锁,等待一段时间后重试,重新获取数据即可。
解决缓存击穿的方法有很多,以上只是其中一种通用的 PHP 解决方法,实际应用中需要结合具体情况选择合适的解决方案。
以下是基于 Redis 分布式锁和补偿任务的代码示例:
  1. // 获取 Key
  2. $key = 'my_key';
  3. // 根据 Key 从 Redis 中获取数据
  4. $data = $redis->get($key);
  5. if (!$data) {
  6.     // 尝试获取分布式锁
  7.     $lockKey = 'my_redis_lock';
  8.     $locked = false; // 是否已经获得锁
  9.     // 尝试获取锁,并最多等待 3 秒钟
  10.     for ($i = 0; $i < 3; $i++) {
  11.         $locked = $redis->set($lockKey, 1, array('nx', 'ex' => 10));
  12.         if ($locked) {
  13.             break;
  14.         }
  15.         sleep(1);
  16.     }
  17.     if ($locked) {
  18.         // 获取数据库数据
  19.         $data = '查询数据库的结果';
  20.         // 将查询结果写入 Redis 缓存中,并设置永久过期
  21.         $redis->setex($key, 0, $data);
  22.         // 删除锁
  23.         $redis->del($lockKey);
  24.     } else {
  25.         // 使用补偿任务异步处理缓存
  26.         $taskData = array(
  27.             'key' => $key,
  28.         );
  29.         $result = $redis->lpush('my_redis_queue', json_encode($taskData));
  30.         if (!$result) {
  31.             // 记录错误日志
  32.             error_log('Failed to add task to the task queue!');
  33.         }
  34.         // 返回默认值,避免使用错误的数据
  35.         $data = 'default value';
  36.     }
  37. }
  38. // 返回数据
  39. echo $data;
复制代码
补偿任务相关的代码如下:
  1. // 任务处理函数,将查询结果写入缓存或数据库
  2. function processTask($redis, $taskData) {
  3.     $key = $taskData['key'];
  4.     $data = '查询数据库的结果';
  5.     // 将查询结果写入 Redis 缓存中,并设置永久过期
  6.     $redis->setex($key, 0, $data);
  7. }
  8. // 拉取任务队列中的任务,并处理任务
  9. function processTaskQueue($redis) {
  10.     while (true) {
  11.         // 从任务队列中阻塞获取任务,最多等待 10 秒钟
  12.         $taskJson = $redis->brpop('my_redis_queue', 10);
  13.         if (!$taskJson) {
  14.             // 没有获取到任务,则结束循环
  15.             break;
  16.         }
  17.         // 解析任务数据
  18.         $taskData = json_decode($taskJson[1], true);
  19.         // 处理任务
  20.         processTask($redis, $taskData);
  21.     }
  22. }
  23. // 启动后台任务处理进程
  24. function startTaskProcessor($redis) {
  25.     while (true) {
  26.         // 处理任务队列中的任务
  27.         processTaskQueue($redis);
  28.         // 等待一段时间后再继续处理任务
  29.         sleep(1);
  30.     }
  31. }
  32. // 在应用程序启动时启动后台任务处理进程
  33. startTaskProcessor($redis);
复制代码
以上代码示例中,如果获取缓存的进程都无法获得锁并写入缓存,则使用补偿任务异步处理任务,并在后台异步将查询结果写入缓存。补偿任务的处理逻辑与业务逻辑相似,只是将数据的写入缓存操作改为异步操作,并放入任务队列中。后台任务处理进程会不断地从任务队列中获取任务,并进行处理。


文章来源:刘俊涛的博客 欢迎关注公众号、留言、评论,一起学习。
若有帮助到您,欢迎捐赠支持,您的支持是对我坚持最好的肯定(_)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

不到断气不罢休

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表