小言_互联网的博客

Redis缓存穿透、缓存击穿、缓存雪崩的原理和解决办法

428人阅读  评论(0)
在大数据时代,由于网络请求的并发,导致的数据库的I/O开销巨大,所以为了缓解数据库的压力,缓存技术必不可少,而这其中redis基本是服务器的缓存服务之一,虽然缓存技术很好用,但是也会出现各种各样的问题,这里就最常见的三种问题进行原理分析和解决,希望能够给大家带来帮助。
  • 缓存穿透:key中对应的缓存数据不存在,导致去请求数据库,造成数据库的压力倍增的情况

  • 缓存击穿:redis过期后的一瞬间,有大量用户请求同一个缓存数据,导致这些请求都去请求数据库,造成数据库压力倍增的情,针对一个key而言

  • 缓存雪崩:缓存服务器宕机或者大量缓存集中某个时间段失效,导致请求全部去到数据库,造成数据库压力倍增的情况,这个是针对多个key而言

一、缓存穿透的解决方案

  • 常用方法可以采用布隆过滤器方法进行数据拦截,其次可以还有一种解决思路,就是如果请求的数据为空,将空值也进行缓存,就不会发生穿透情况

  
  1. <?php
  2. class getPrizeList {
  3. /**
  4. * redis实例
  5. * @var \Redis
  6. */
  7. private $redis;
  8. /**
  9. * @var string
  10. */
  11. private $redis_key = '|prize_list';
  12. /**
  13. * 过期时间
  14. * @var int
  15. */
  16. private $expire = 30;
  17. /**
  18. * getPrizeList constructor.
  19. * @param $redis
  20. */
  21. public function __construct($redis)
  22. {
  23. $this->redis = $redis;
  24. }
  25. /**
  26. * @return array|bool|string
  27. */
  28. public function fetch()
  29. {
  30. $result = $this->redis->get( $this->redis_key);
  31. if(! isset($result)) {
  32. //此处应该进行数据库查询...
  33. //如果查询结果不存在,给其默认空数组进行缓存
  34. $result = [];
  35. $this->redis->set( $this->redis_key, $result, $this->expire);
  36. }
  37. return $result;
  38. }
  39. }

二、缓存击穿解决办法

  • 使用互斥锁(mutex key),就是一个key过期时,多个请求过来允许其中一个请求去操作数据库,其他请求等待第一个请求成功返回结果后再请求。

  
  1. <?php
  2. class getPrizeList {
  3. /**
  4. * redis实例
  5. * @var \Redis
  6. */
  7. private $redis;
  8. /**
  9. * @var string
  10. */
  11. private $redis_key = '|prize_list';
  12. /**
  13. * @var string
  14. */
  15. private $setnx_key = '|prize_list_setnx';
  16. /**
  17. * 过期时间
  18. * @var int
  19. */
  20. private $expire = 30;
  21. /**
  22. * getPrizeList constructor.
  23. * @param $redis
  24. */
  25. public function __construct($redis)
  26. {
  27. $this->redis = $redis;
  28. }
  29. /**
  30. * @return array|bool|string
  31. */
  32. public function fetch()
  33. {
  34. $result = $this->redis->get( $this->redis_key);
  35. if(! isset($result)) {
  36. if( $this->redis->setnx( $this->setnx_key, 1, $this->expire)) {
  37. //此处应该进行数据库查询...
  38. //$result = 数据库查询结果;
  39. $this->redis->set( $this->redis_key, $result, $this->expire);
  40. $this->redis->del( $this->setnx_key); //删除互斥锁
  41. } else {
  42. //其他请求每等待10毫秒重新请求一次
  43. sleep( 10);
  44. self::fetch();
  45. }
  46. }
  47. return $result;
  48. }
  49. }

三、缓存雪崩的解决办法

  • 这种情况是因为多个key同时过期导致的数据库压力,一种方法可以在key过期时间基础上增加时间随机数,让过期时间分散开,减少缓存时间过期的重复率
  • 另一种方法就是加锁排队,这种有点像上面缓存击穿的解决方式,但是这种请求量太大,比如5000个请求过来,4999个都需要等待,这必然是指标不治本,不仅用户体验性差,分布式环境下就更加复杂,因此在高并发场景下很少使用
  • 最好的解决方法,是使用缓存标记,判断该标记是否过期,过期则去请求数据库,而缓存数据的过期时间要设置的比缓存标记的长,这样当一个请求去操作数据库的时候,其他请求拿的是上一次缓存数据

  
  1. <?php
  2. class getPrizeList {
  3. /**
  4. * redis实例
  5. * @var \Redis
  6. */
  7. private $redis;
  8. /**
  9. * @var string
  10. */
  11. private $redis_key = '|prize_list';
  12. /**
  13. * 缓存标记key
  14. * @var string
  15. */
  16. private $cash_key = '|prize_list_cash';
  17. /**
  18. * 过期时间
  19. * @var int
  20. */
  21. private $expire = 30;
  22. /**
  23. * getPrizeList constructor.
  24. * @param $redis
  25. */
  26. public function __construct($redis)
  27. {
  28. $this->redis = $redis;
  29. }
  30. /**
  31. * @return array|bool|string
  32. */
  33. public function fetch()
  34. {
  35. $cash_result = $this->redis->get( $this->cash_key);
  36. $result = $this->redis->get( $this->redis_key);
  37. if(!$cash_result) {
  38. $this->redis->set( $this->cash_key, 1, $this->expire);
  39. //此处应该进行数据库查询...
  40. //$result = 数据库查询结果, 并且设置的时间要比cash_key长,这里设置为2倍;
  41. $this->redis->set( $this->redis_key, $result, $this->expire * 2);
  42. $this->redis->del( $this->cash_key); //删除互斥锁
  43. }
  44. return $result;
  45. }
  46. }

 


转载:https://blog.csdn.net/weixin_41635750/article/details/108650469
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场