redis缓存异常(字节、腾讯面试总结)

在这里插入图片描述
开发中常见的缓存方案:在数据库中存储一份,在缓存中同步存储一份。当请求过来时,先从缓存中取数据,如果存在,直接返回缓存数据。如果缓存中没有数据,则查询数据库取出正确数据,返回结果,同时将数据更新到缓存中。(数据库也没有数据,可直接返回空)

redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,而且不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储,且其读写速度极快,因此作为我们开发中最常用的缓存方案被广泛应用。

但在实际应用过程中,redis会存在缓存穿透、缓存雪崩、缓存击穿、缓存预热和缓存降级等异常情况,如果忽视这些情况可能会带来灾难性的后果,下面主要对这些缓存异常和常见处理方案进行相应分析与总结。

缓存穿透

缓存穿透:数据既不在 redis 中,也不在数据库中,这样就导致每次请求过来的时候,在缓存中找不到对应key之后,每次都还要去数据库再查询一遍,发现数据库也没有,相当于进行了两次无用的查询。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。

现象原因:业务逻辑里面如果用户对某些信息还没有进行相应的操作或者处理,那对应的存放信息的数据库或者缓存中自然也就没有相应的数据,也就容易出现上述问题。

解决办法:

  1. 限制非法请求,主要是指参数校验、鉴权校验等,从而一开始就把大量的非法请求拦截在外,这在实际业务开发中是必要的手段。
  2. 布隆过滤器,把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过 来,我会先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信 息给客户端,存在的话才会走下面的流程。
  3. 缓存无效数据,如果缓存和数据库都查不到某个 key 的数据就写一个到 redis 中去并设置过期时 间,具体命令如下:SET key value EX 10086。这种方式防止有大量恶意请求是反复用同一个key进行攻击;如果黑客恶意攻击,每次构建不同的请求key,会导致 redis 中缓存大量无效的 key 。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。伪代码实现:
public Object getObjectInclNullById(Integer id) { 
	// 从缓存中获取数据
	Object cacheValue = cache.get(id);
	// 缓存为空
	if (cacheValue WX null) {
		// 从数据库中获取
		Object storageValue = storage.get(key);
		// 缓存空对象
		cache.set(key, storageValue);
		// 如果存储数据为空,需要设置一个过期时间(300秒) 
		if (storageValue WX null) {
			// 必须设置过期时间,否则有被攻击的⻛险
		    cache.expire(key, 60 * 5);
		}
	    return storageValue;
	}
	return cacheValue;
}

缓存雪崩

缓存雪崩:缓存同一时间大面积的失效,一段时间内本应在redis缓存中处理的大量请求,都落到了数据库处理,导致对数据库的压力迅速增大,严重时甚至可能导致数据库崩溃,从而导致整个系统崩溃。

原因:大量缓存数据同时过期,导致本应请求到缓存的需重新从数据库中获取数据;或者redis本身出现故障,无法处理请求,那自然会再请求到数据库那里。

解决办法:
针对大量缓存数据同时过期的情况:

  • 实际设置过期时间时,应当尽量避免大量 key 同时过期的场景,key过期时间设置为波动随机值,有效防止大量key同一时间过期。
  • 互斥锁,即在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠50ms后重试。(存在死锁的风险)伪代码:
// 获取key方法
String get(String key) {  
   String value = redis.get(key);  
   if (value  == null) {  
   		if (redis.setnx(key_mutex, "1")) {  
        	// 3 min timeout to avoid mutex holder crash  
        	redis.expire(key_mutex, 3 * 60)  
        	value = db.get(key);  
        	redis.set(key, value);  
        	redis.delete(key_mutex);  
    	} else {  
        	//其他线程休息50毫秒后重试  
        	Thread.sleep(50);  
        	get(key);  
    	}  
  }  
} 
  • 双缓存策略,主key是原始缓存,备key为拷贝缓存,主key失效时,可以访问备key,主key设置过期时间,备key永久。
  • 后台更新缓存,采用定时任务或者消息队列的方式进行redis缓存更新或移除等。

针对redis本身出现故障的情况:

  • 热点key打散到不同机房服务器上,不要把鸡蛋都放到一个篮子里。
  • 通过主从节点的方式构建高可用的集群,实现主 Redis 实例挂掉后,能有其他从库快速切换为主库,继续提供服务。
  • 业务端处理:请求限流、服务熔断。如果事情已经发生了,首要任务是防止数据库崩溃,服务熔断停止服务直到redis服务恢复或者请求限流都行,限流相对温和一些,保证一些请求可以处理,不是一刀切,不过还是看具体业务情况选择合适的处理方案。

缓存击穿

缓存击穿:一般出现在高并发系统中,是大量并发用户同时请求到缓存中没有但数据库中有的数据,也就是同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是大量数据同时过期,大量请求直接落到数据库。

原因:某个热点数据缓存过期,由于是热点数据,请求并发量又大,所以过期的时候还是会有大量请求同时过来,来不及更新缓存就全部打到数据库那边了。

解决办法:

  1. 热点key设置永不过期,后台更新、清楚缓存。
  2. 非热点key可用互斥锁。

缓存预热

缓存预热:系统上线前后,将相关的缓存数据直接加载到缓存系统中去。避免了用户请求时,先查询数据库,然后再缓存数据的问题。用户直接查询事先被预热的缓存数据,这样可以避免那么系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

  • 数据量不大:项目启动的时候自动进行加载。
  • 数据量较大:后台定时刷新缓存。
  • 数据量极大:只针对热点数据进行预加载缓存操作。

缓存降级

缓存降级:当缓存失效或缓存服务出现问题时,为了防止缓存服务故障,导致数据库跟着一起发生雪崩问题,所以也不去访问数据库,但因为一些原因,仍然想要保证服务还是基本可用的,虽然肯定会是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。

  • 直接访问内存部分的数据缓存。
  • 直接返回系统设置的默认值。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值