1、缓存失效问题
1、缓存穿透
? 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询。加大了数据库压力。
? 解决方法:
-
布隆过滤器:布隆过滤器底层使用bit数组存储数据,该数组中的元素默认值是0。 布隆过滤器第一次初始化的时候,会把数据库中所有已存在的key,经过一些列的hash算法(比如:三次hash算法)计算,每个key都会计算出多个位置,然后把这些位置上的元素值设置成1。之后,有用户key请求过来的时候,再用相同的hash算法计算位置。
- 如果多个位置中的元素值都是1,则说明该key在数据库中已存在。这时允许继续往后面操作。
- 如果有1个以上的位置上的元素值是0,则说明该key在数据库中不存在。这时可以拒绝该请求,而直接返回。
使用布隆过滤器确实可以解决缓存穿透问题,但同时也带来了两个问题:
-
存在误判的情况。 hash算法是会出现hash冲突的,如果有几千万或者上亿的数据,布隆过滤器中的hash冲突会非常明显。 -
存在数据更新问题。 如果数据库中的数据更新了,需要同步更新布隆过滤器。但它跟数据库是两个数据源,就可能存在数据不一致的情况。如更新数据库后网络失败,无法更新布隆过滤器。 -
缓存空值:当某个用户id在缓存中查不到,在数据库中也查不到时,也需要将该用户id缓存起来,只不过值是空的。这样后面的请求,再拿相同的用户id发起请求时,就能从缓存中获取空数据,直接返回了,而无需再去查一次数据库。 缓存空对象会有两个问题: 第一,value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。 第二,缓存层和存储层的数据会有一段时间的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象
2、缓存击穿
对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问, 是一种非常“热点”的数据。
? 这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所有对这个 key 的数据查询都落到 db,我们称为缓存击穿。
? 解决方法:
- 加锁:数据库压力过大的根源是,因为同一时刻太多的请求访问了数据库。如果我们能够限制,同一时刻只有一个请求才能访问某个数据库,问题就解决了。
- 自动续期:后台添加定时任务,定时刷新快要过期的热点数据。
3、缓存雪崩
? 缓存雪崩是缓存击穿的升级版,缓存击穿说的是某一个热门key失效了,而缓存雪崩说的是有多个热门key同时失效。看起来,如果发生缓存雪崩,问题更严重。
? 缓存雪崩目前有两种:
? 有大量的热门缓存,同时失效。会导致大量的请求,访问数据库。而数据库很有可能因为扛不住压力,而直接挂掉。
? 缓存服务器down机了,可能是机器硬件问题,或者机房网络问题。总之,造成了整个缓存的不可用。
? 归根结底都是有大量的请求,透过缓存,而直接访问数据库了。
? 解决方法:
- 随机过期时间:我们可以在设置的过期时间基础上,再加个1~60秒的随机数。这样即使在高并发的情况下,多个请求同时设置过期时间,由于有随机数的存在,也不会出现太多相同的过期key。
2、如何保证redis缓存和数据库的数据一致性
数据库和缓存更新等操作,就很容易出现数据一致性的问题。无论是先写数据库,在删除缓存,还是先删除缓存,在写入数据库,都会出现数据一致性的问题。列举两个小例子。
1、 先删除了redis缓存,但是因为其他什么原因还没来得及写入数据库,另外一个线程就来读取,发现缓存为空,则去数据库读取到之前的数据并写入缓存,此时缓存中为脏数据。
2、 如果先写入了数据库,但是在缓存被删除前,写入数据库的线程因为其他原因被中断了,没有删除掉缓存,就也会出现数据不一致的情况。
总的来说,写和读在多数情况下都是并发的,不能绝对保证先后顺序,就会很容易出现缓存和数据库数据不一致的情况。
解决方法:
-
延时双删: 基本思路: 在写库前后都进行删除缓存操作,并且设置合理的超时时间 基本步骤: 先删除缓存–再写数据库—休眠一段时间—再次删除缓存 注:休眠的时间是根据自己的项目的读数据业务逻辑的耗时来确定的。这样做主要是为了保证在写请求之前确保读请求结束,写请求可以删除读请求造成的缓存脏数据。 该方案的弊端: 集合双删策略+缓存超时策略设置,这样最差的结果就是在超时时间内数据存在不一致,又增加了写请求的耗时。 -
基于订阅binglog的同步机制 基本思路: mysql Binlog增强订阅消费+消息队列+增量数据更新到redis—读redis:热数据基本上都在redis—写mysql:增删改都是操作mysql—更新redis数据:mysql的数据操作Binlog,来更新redis 我们再来看看详细的过程 1、Redis更新 1)、数据操作主要分为两大块: 一个是全量,将全部数据写去redis;另一个就是增量(update、insert、delete),实时更新。 2)、读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。 这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。 其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
|