# 缓存

用户的数据一般都是存储于数据库,数据库的数据是落在磁盘上的,磁盘的读写速度可以说是计算机里最慢的硬件了。因为Redis 是内存数据库,我们可以将数据库的数据缓存在 Redis 里,相当于数据缓存在内存,内存的读写速度比硬盘快好几个数量级,这样大大提高了系统性能。

# 1、缓存雪崩

两种情况可能会出现缓存雪崩:

  • 大量缓存数据同时过期。
  • Redis故障

通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

e2b8d2eb5536aa71664772457792ec40

当大量的用户数据涌入,都存放在了Redis中,此时Redis无法处理这些数据,于是全部都去请求数据库,从而导致数据库的压力瞬间增加,也有可能会导致数据库宕机,从而形成一系列的问题,造成系统的崩溃等,这个就称为缓存雪崩问题。

# 1.1、发生雪崩的原因

  1. 大量数据同时过期

大量数据同时过期而引发的缓存雪崩问题。

  1. Redis故障宕机

Redis出现故障。

# 1.2、解决方法

1、策略性的设置过期时间

要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。

2、后台更新缓存

业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新

缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰”,此时可能会认为数据丢失了,因此我们要解决这个问题的发生,就要在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数 据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让 缓存失效的时间点尽量均匀。这就是所谓的缓存预热

3、互斥锁

当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。

针对于Redis故障的解决

1、限流降级机制

当Redis故障宕机从而导致缓存雪崩时,我们就要启动服务的熔断机制了,然后暂停业务的应用对缓存的访问,直接返回错误提示信息,不让程序继续往数据库访问,这样降低了对数据库访问的压力,保证数据库系统的正常运行,等缓存服务再次恢复时,再进行访问缓存服务。

或者使用限流的方式,限制一部分的请求去访问数据库,判断有大批量的请求就拒绝访问,等到缓存服务恢复再解除限流。

2、高可用

限流机制是缓存雪崩发生后的应对方案,我们从开始设计业务的时候就要考虑到此类问题的发生,所以可以使用之前学过的主从的方式,来实现Redis缓存的高可用集群

如果 Redis 缓存的主节点故障宕机,从节点可以切换成为主节点,继续提供缓存服务,避免了由于 Redis 故障宕机而导致的缓存雪崩问题。

# 2、缓存穿透

# 2.1、概念

假如用户想要查询一个数据,先去查看Redis,发现Redis没有这个数据,也就是缓存没有命中,于是就向数据库取查询,此时发现也没有这条数据,于是本次的查询就失败了。这仅仅是一条,如果上万用户或者很多用户去查的时候都没有查到数据,缓存没有命中,于是都跑去查数据库了,就会给数据库造成很大的请求压力,这时候就出现了缓存穿透

image-20221122112246243

# 2.2、原因

1、攻击者伪造大量不存在的数据(比如:负数或者特别大的数),攻击数据库。

2、业务问题的bug,造成数据误删等操作。

# 2.3、解决方法

1、参数校验

当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,在请求入口进行校验,比如对用户进行鉴权、数据合法性检查等操作,这样可以减少缓存穿透发生的概率。

这个有一个缺点就是,虽然减轻了对 Redis 以及数据库的压力,但是增加了客户端的编码与维护的工作量。如果请求的入口很多,那么工作量很大。

image-20221122132456800

2、缓存空对象

当缓存与数据库中都没有 key 时,就设置一个空值写入缓存,并同时设置一个比较短的过期时间。由于在缓存中设置空值,所以请求在缓存这一级别就返回,也就不会被穿透。

image-20221122163121081

此时会有问题存在,如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键,即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于 需要保持一致性的业务会有影响。

3、布隆过滤器

使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在。

我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。

即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。

创建布隆过滤器:

//创建存储整型的布隆过滤器
bloomFilter = BloomFilter.create(Funnels.integerFunnel(), expectedInsertions, fpp);
1
2

可参考文章:

# 3、缓存击穿

# 3.1、概述

我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。

一个key非常热点,在不停的扛着大并发,大并发集中 对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,很容易就被高并发的请求冲垮,就像在一 个屏障上凿开了一个洞。

缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。

# 3.2、解决

1、设置热点数据永不过期

从缓存层面来看,没有设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间,所以不会出现热点 key 过期后产生的问题。

2、加互斥锁

使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布 式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考 验很大

# 4、总结

缓存雪崩和缓存击穿主要原因是数据不在缓存中,而导致大量请求访问了数据库,数据库压力骤增,容易引发一系列连锁反应,导致系统奔溃。

一旦数据被重新加载回缓存,应用又可以从缓存快速读取数据,不再继续访问数据库,数据库的压力也会瞬间降下来。因此,缓存雪崩和缓存击穿应对的方案比较类似,而缓存穿透主要原因是数据既不在缓存也不在数据库中,解决的方案相对来说比较复杂一些。

最近更新: 1/8/2024, 11:36:09 AM
Copyright © - 码上言   |