缓存
用户的数据一般都是存储在数据库里面,数据库的数据写在磁盘上,磁盘的读写性能是计算机硬件里面最慢的,在有大量请求的时候,如果我们直接去请求数据库,很容易导致数据库崩溃,为了避免数据库直接承受来自请求的压力,所以我们使用redis做一层缓存,因为redis是内存型数据库,数据的写入和读取比硬盘要高好几个量级。
我们引入缓存,当缓存出现问题的时候就会面临3个问题,缓存雪崩,缓存击穿和缓存穿透。
缓存雪崩
通常我们为了保证数据库和redis数据的一致性,会给redis里面的数据设置ttl,当缓存的数据过期后,用户访问的数据如果不存在,业务的就会从数据库里面重新获取数据放到redis里面,这样后续的请求就都可以直接命中缓存了。
如果大量的数据在同一时间内过期或redis宕机,那么就会有大量的请求直接去访问数据库,从而导致数据库的压力突然增加,严重的时候会造成数据库直接宕机,导致整体服务的不可用,这就是缓存雪崩。
那么如何解决呢?
- 均匀的设置过期时间
给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。 - 互斥锁
当业务线程在处理用户请求的时候,如果发现访问的数据不在redis里,就加一个互斥锁,保证同一时间只有一个请求来构建redis里的数据,当构建完成后释放掉锁。未能获取到互斥锁请求的,要么自旋等待锁的释放,要么返回空值。(互斥锁根据业务需求设置超时时间,防止死锁导致系统阻塞) - 后台更新缓存
业务线程不在负责更新缓存数据,缓存数据的更新交给后台线程定时更新。
解决方案有3种
- 后台线程频繁的检测缓存是否有效,检测到缓存失效了,那么就从数据库里面获取数据来更新缓存(但是这个检测密度非常大,是需要毫秒级的检测)
- 在业务先发现缓存失效的时候,通过消息队列的形式通知到后台,后台线程收到消息以后,判断数据是否存在不存在就更新缓存,存在就不做任何操作。
- 后台线程通过定时任务的形式去更新缓存,例如3分钟一次,发现缓存中的数据ttl就剩最后10分钟了,那么查询数据更新缓存,并更新掉缓存的时间,这样检测缓存有效期的频率也降低下来了,缓存数据也能百分之百得到更新。
Redis宕机导致的雪崩
解决方案有两种:
- 服务器熔断或限流
- redis熔断导致雪崩的时候,我们可以启动服务器熔断机制,暂停业务应用对缓存服务的访问,直接放回错误页,不用再继续访问数据库,从而降低对数据库的访问压力,保证数据库系统的正常运行,然后等到 Redis 恢复正常后,再允许业务应用访问缓存服务。
- 我们还可以启动请求限制,只将少部分请求发送到数据库进行处理,在请求入口拒绝大部请求服务,等redis恢复正常并把缓存预热完成后,在接触请求限流机制。
- 构建高可用的redis机群
- 服务熔断或请求限流机制是缓存雪崩发生后的应对方案,我们最好通过主从节点的方式构建 Redis 缓存高可靠集群。如果redis 某个节点挂掉以后,还可以提供缓存服务,防止缓存雪崩。
缓存击穿
某个热点数据突然过期导致大量的请求打到数据库里,导致数据库在高并发的情况下宕机,这就是缓存击穿
我们发现他和雪崩很相似,但是不同的是雪崩是大面积数据的过期,击穿是某条数据的过期导致
解决方案
- 互斥锁方案,添加互斥锁,保证同一时间内只有一个请求在构建缓存数据,构建完成后释放锁,其他请求自旋等待锁的释放或者直接返回空。(锁要加过期时间)
- 不给热点数据设置过期时间,由后台来负责缓存数据的更新
- 设置过期时间,后台程序定期去检测数据是否过期,如果发现数据马上要过期了去查询数据库来更新缓存,例如:缓存是120分钟有效,我们3分钟检测一次缓存,如果发现他小于10分钟了就去更新缓存
缓存穿透
当用户访问数据的时候,发现缓存不存在,数据库也不存在,导致在访问缓存时,发现缓存缺失,去访问数据库,这就导致了两个数据库都在承受相同的压力,导致数据库压力突增,这就是缓存穿透。
缓存穿透的发生一般有这两种情况:
- 业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
- 黑客恶意攻击,故意大量访问某些读取不存在数据的业务;
解决方案
- 非法请求限制
当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,不合理的请求就拒绝掉。 - 缓存设置空值或默认值
当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。 - 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在。
我们可以在写入数据库的时候在布隆过滤器里面加入一个标记为,来标记当前的数据是否存在,请求在进来的时候先访问bloom过滤器,如果存在就去访问redis,如果不存在就拒绝请求。