Redis缓存击穿,失效以及维度划分

一、redis缓存
  • 加入缓存的好处
    1、加速读写:因为缓存通常都是全内存的,而存储层通常读写性能不够强悍(例如MySQL),通过缓存的
    使用可以有效地加速读写,优化用户体验。
    2、降低后端负载:帮助后端减少访问量和复杂计算(例如很复杂的SQL语句),在很大程度降低了后端的负
    载。
  • 产生的问题
    1、据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关。
    2、代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本。
    3、运维成本:以Redis Cluster为例,加入后无形中增加了运维成本。

缓存的使用场景基本包含如下两种:
1、开销大的复杂计算:以MySQL为例子,一些复杂的操作或者计算(例如大量联表操作、一些分组计
算),如果不加缓存,不但无法满足高并发量,同时也会给MySQL带来巨大的负担。
2、加速请求响应:即使查询单条后端数据足够快(例如select*from tablewhere id=),那么依然可以使用缓
存,以Redis为例子,每秒可以完成数万次读写,并且提供的批量操作可以优化整个IO链的响应时间。

二、 缓存维度划分
  • 缓存粒度问题是一个容易被忽视的问题,如果使用不当,可能会造成很多无用空间的浪费,网络带宽的浪
    费,代码通用性较差等情况,需要综合数据通用性、空间占用比、代码维护性三点进行取舍。
  • 缓存比较常用的选型,缓存层选用Redis,存储层选用MySQL。
  • 通用性:缓存全部数据比缓存部分数据更加通用,但从实际经验来看,很长时间内应用只需要几个重要的属性
  • 空间占比:缓存全部数据比缓存部分数据更占空间。可能存在更多的问题。
    1、 全部数据会造成内存的浪费
    2、 全部数据可能每次传输产生的网络流量会比较大,耗时相对较大,在极端情况下会阻塞网络
    3、 全部数据进行序列化与反序列化CPU消耗很大。
  • 代码维护:全部数据的优势更加明显,部分数据一旦要加新字段需要修改业务代码,修改后还需要更新缓存的数据。
  • 下面是缓存全部数据与部分数据时
    在这里插入图片描述
三、缓存击穿

1、 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时
读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
2、为什么会出现缓存击穿?

一般在使用缓存去缓存数据时,会使用“缓存+过期时间”的策略既可以加速数据读写,又可以保证数据的定期更新,这个模式在一般情况的业务都是可以满足的。

但是这样也会出现两个致命问题:

  • 当前key是一个热点数据(比如:某个热门的文章,新闻),并发量非常大。
  • 重建缓存不能在短时间完成,可能是一个复杂的计算,例如复杂的sql,多次IO,多个依赖等。

3、在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能让应用崩溃,如果要解决这个
问题至少是遵循如下:

  • 减少重建缓存的次数
  • 数据尽可能一致
四、缓存击穿问题解决

1、互斥锁

php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\DB;
class TestController extends Controller
{
	public function index(Request $request)
	{
		$userId = $request->input("user_id");
		dd(json_decode($this->getArticle(1)));
	}
	public function lock($key,$random)
	{
		$lock = Redis::set($key,$random,"nx","ex",10);
		return $lock;
	}
	public function unlock($key,$random)
	{
		if (Redis::get($key) == $random){
		Redis::del($key);
	}
	}
	public function getArticle($id)
	{
		$key = "article_content_".$id;
		$ret = Redis::get($key);
		if ($ret === null) {
		//生成锁的key
		$lockKey = $key . '_lock';
		//生成随机数,用于设置锁的值,后面释放锁时会用到
		$random = mt_rand();
		//拿到互斥锁
		if ($this->lock($lockKey, $random)) {
		
		$ret = json_encode(DB::table("article")->where("id",$id)->first());
		//更新缓存,过期时间可以根据情况自已调整
		Redis::set($key, json_encode($ret));
		//释放锁
		$this->unLock($lockKey, $random);
		} else {
		//等待200毫秒,然后重新获取缓存值,让其他获取到锁的进程取得数据并设置缓存
		usleep(200);
		return $this->getArticle($id);
		}
		}else{
		$ret = json_decode($ret);
		}
		return $ret;
	}
}
?>

说明:从redis获取数据,如果值不为空,则直接返回值,否则执行下面的步骤:

  • 如果set(nx和ex)结果为true,说明此时没有其他线程重建缓存,那么当前线程执行缓存构建逻辑
  • 如果set(nx和ex)结果为false,说明此时已有其他线程正在执行构建缓存的工作,那么当前线程将休息指定时间后,重新执行函数,直到获取到数据。

2、key设置永不过期

  • 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是物 理过期。
  • 从功能层面看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独
    的线程去构建缓存。
  • 从实战方面来说,设置热点数据永不过期有效杜绝了热点key产生的问题,但是不足的点在于重构缓存期 间,会出现数据不一致性的情况,这种情况在应用中是坚决不允许出现的。
  • 解决数据不一致的问题可以是每次查询的时候都去判断下是否超时,超时就立刻更新缓存数据。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值