Skip to content

Latest commit

 

History

History
168 lines (94 loc) · 8.02 KB

缓存.md

File metadata and controls

168 lines (94 loc) · 8.02 KB

缓存穿透

什么是缓存穿透

正常情况下,查询的数据都存在,如果请求一个不存在的数据,也就是缓存和数据库都查不到这个数据,每次都会去数据库查询,这种查询不存在数据的现象我们称为缓存穿透

解决办法

1.缓存空值

之所以发生穿透,是因为缓存中没有存储这些数据的key,从而每次都查询数据库

缺点:伪造一堆无用的ID,还是会穿透过去的。而且都需要进行缓存的值会增多,可能导致内存增大。

思考:所以可以把数据库的ID放到一个缓存里面,做成一个白名单之类的。不存在则直接返回。但是缺点就是ID数据可能过大,导致过大的缓存压力。

解决办法:BloomFilter

2.BloomFilter

BloomFilter 类似于一个hbase set 用来判断某个元素(key)是否存在于某个集合中

我们把有数据的key都放到BloomFilter中,每次查询的时候都先去BloomFilter判断,如果没有就直接返回null

布隆过滤器。没有的肯定不存在。存在的不一定存在。适用这里的应用场景


缓存击穿

什么是缓存击穿

在高并发的情况下,大量的请求同时查询同一个key时,此时这个key正好失效了,就会导致同一时间,这些请求都会去查询数据库,这样的现象我们称为缓存击穿

击穿带来的问题

会造成某一时刻数据库请求量过大

解决办法

采用分布式锁,只有拿到锁的第一个线程去请求数据库,然后插入缓存,当然每次拿到锁的时候都要去查询一下缓存有没有

分布式锁的应用场景


缓存雪崩

什么是缓存雪崩

当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了、热点数据集中失效问题

## 解决办法

  1. 采用集群,降低服务宕机的概率
  2. ehcache本地缓存 + Hystrix限流&降级

ehcache 本地缓存的目的也是考虑在 Redis Cluster 完全不可用的时候,ehcache 本地缓存还能够支撑一阵

使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑

解决热点数据集中失效问题

我们在设置缓存的时候,一般会给缓存设置一个失效时间,过了这个时间,缓存就失效了。

对于一些热点的数据来说,当缓存失效以后会存在大量的请求过来,然后打到数据库去,从而可能导致数据库崩溃的情况 解决办法

  1. 设置不同的失效时间
  2. 采用缓存击穿的解决办法,加锁
  3. 永不失效,就是采用定时任务对快要失效的缓存进行更新缓存和失效时间

缓存一致性

最初级的缓存不一致问题及解决方案

问题:先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。

解决思路:先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。

因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。

比较复杂的数据不一致问题分析

数据发生了变更,先删除了缓存,然后要去修改数据库。

但是还没来得及修改,一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。

随后数据变更的程序完成了数据库的修改。

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。

读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。

此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。

方案 问题 问题出现概率 推荐程度 更新缓存 -> 更新数据库 为了保证数据准确性,数据必须以数据库更新结果为准,所以该方案绝不可行 大 不推荐 更新数据库 -> 更新缓存 并发更新数据库场景下,会将脏数据刷到缓存 并发写场景,概率一般 写请求较多时会出现不一致问题,不推荐使用。 删除缓存 -> 更新数据库 更新数据库之前,若有查询请求,会将脏数据刷到缓存 并发读场景,概率较大 读请求较多时会出现不一致问题,不推荐使用 更新数据库 -> 删除缓存 在更新数据库之前有查询请求,并且缓存失效了,会查询数据库,然后更新缓存。如果在查询数据库和更新缓存之间进行了数据库更新的操作,那么就会把脏数据刷到缓存 并发读场景 & 读操作慢于写操作,概率最小 读操作比写操作更慢的情况较少,相比于其他方式出错的概率小一些。勉强推荐。

延迟双删

采用更新前后双删除缓存策略

先淘汰缓存

再写数据库

休眠1秒,再次淘汰缓存

大家应该评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上即可。

这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

问题及解法:

1、同步删除,吞吐量降低如何处理

将第二次删除作为异步的,提交一个延迟的执行任务

2、解决删除失败的方式:

添加重试机制,例如:将删除失败的key,写入消息队列;但对业务耦合有些严重;

写缓存策略

  1. 缓存key设置失效时间

  2. 先DB操作,再缓存失效

  3. 写操作都标记key(美团中间件)强制走主库

  4. 接入美团中间件监听binlog(美团中间件)变化的数据在进行兜底,再删除缓存

读缓存策略

  1. 先判断是否走主库

  2. 如果走主库,则使用标记(美团中间件)查主库

  3. 如果不是,则查看缓存中是否有数据

  4. 缓存中有数据,则使用缓存数据作为结果

  5. 如果没有,则查DB数据,再写数据到缓存


实际项目

每天一更新,直接把缓存直接删掉。因为是夜深人静,上班后大家错峰访问,进行了错峰预热。没有缓存雪崩的场景。

但是如果你是要高峰期,进行缓存的清理,就会有雪崩、击穿的场景。

解决方法:

  1. 穿透: 因为配置了固定数量的进程,最多也只有固定数量的查询。然后视图缓存也缓存了空值,即使下一次同样的请求过来,也是能命中的。最重要做了反爬虫的限流策略,杜绝穿透的情况。
  2. 击穿,加锁,分布式锁,只有拿到锁的第一个线程去请求数据库,然后插入缓存,当然每次拿到锁的时候都要去查询一下缓存有没有。类似redash的redis锁的查询限制策略。
  3. 雪崩:缓存永不失效。主动进行热点数据的缓存的更新。冷数据则进行懒更新的方式。查询后判断时间戳版本,过期也先返回,后续异步进行缓存的刷新。刷新缓存的队列可能会重复?版本号,旧的与最新的版本比较,落后就忽略了。

打点,可以获取到昨天/某个周期范围内比较热点的查询,然后进行提前预热.