浅谈redis(四)——使用redis做缓存的常见问题

一、缓存与数据库数据一致性问题

如果缓存中已经存在的数据,在数据库中做了修改,那么,缓存中的数据也需要修改,否则从缓存中拿到的数据就是旧数据,造成数据不一致的问题。
解决方案有修改类解决方案和删除类解决方案。修改类解决方案就是,在修改数据库数据的同时,修改缓存中的数据。分为两种情况:
先修改缓存数据,再修改数据库数据
此种方案可能出现的情况是缓存数据修改成功了,但是数据库数据修改失败了。如果接受BASE理论,那么就可以记录失败日志,然后定时重试去修改数据库的数据,达到最终一致性。根据实际业务场景,判断是否可以使用这种方案。
先修改数据库数据,再修改缓存数据
此方案造成的问题一个是更新数据库成功,更新缓存失败,那么用户直接获取到的是原有的值,不是最新的值。这个看来就有问题了。因为用户直接从缓存中拿数据。在先更新缓存,再更新DB的方案中,缓存是第一时间更新的,所以用户拿到的是最新的值。而在先更新数据库,后更新缓存方案中,用户很有可能拿到旧的值。所以,此种解决方案不推荐使用。

除了修改类解决方案,还有删除类解决方案。具体也是分为两种情况:
先删除缓存,再更新数据库
此种解决方案造成的问题是,如果删除了缓存,但是还没来得及更新数据库,此时另一个请求又来查询缓存,发现没有缓存,又去查库,而库里的数据还是旧值,造成数据不一致问题。针对这个问题,可以采用延时双删策略。所谓的双删就是删除两次缓存。第一次删除缓存,然后更新数据库,更新完数据库后,等几秒,即延时,然后再删除一次缓存,这就是第二次删除缓存。这么做的目的就是避免在第一次删除缓存之后,更新数据库之前,又有程序把旧数据读到缓存中的情况出现。为何要延时呢?延时多少秒呢?延时主要是让程序先把旧的值读到缓存中,再删除,避免第二次删除的时间早于旧值存入缓存的时间。具体延迟几秒,需要根据自己的业务代码判断。总之要大于一次请求能从库里把数据读到redis中的时间。
这样,最多存在延时时间那么长的脏数据,而后就是同步的数据了。

需要注意的是,如果数据库采用读写分离架构,那么这个延时的时间就需要保证主数据库的数据能同步到从库的时间这么长,否则从数据库的数据还是旧值,读到缓存的也是旧值。
先更新数据库,再删除缓存
此种方案也就并发问题。如果A程序读取缓存,发现缓存失效了,从数据库中查出了数据,而此时网络抖动,还没来得及往缓存里插入数据,此时B程序更新了数据库,且删除了缓存。等B程序执行完操作后,A程序才把它获取到 的数据放入缓存中,此时,又是脏数据。可以看出,这种极端情况的出现概率很低,那么假如出现呢?应该如何避免这种情况呢?解决思路还是更新数据的程序延时删除缓存。和上面的延时双删思路类似。

从上面几种解决方案来看,删除类的解决方案似乎比修改类的解决方案要更靠谱一些。先更新数据,再删除缓存的解决方案最靠谱。无论哪个解决方案,其都是保证的最终缓存中的数据和数据库是同步的,无法保证实时同步性。无论哪种方案,都会有脏数据存在一段时间的情况出现。这就是分布式理论中,BASE理论的体现。在分布式中,很多东西都是只能追求接近完美,而不可能达到像单体项目那样完美。

二、缓存穿透

缓存穿透的意思就是请求一个根本不存在的数据,缓存中没有这个数据,就一直查询数据库。在一些恶意攻击中,以此来攻击后台数据库。解决方案是使用布隆过滤器。布隆过滤器是一种数据结构,可以快速判断请求的数据在数据库中是否存在,如果压根不存在,则就不再查询数据库了。布隆过滤器的命中率不能保证百分之百,但是可以有效的保护数据库免受攻击。

三、缓存击穿

缓存击穿是某个热点数据同时有很多请求访问,而恰好缓存失效了,这些请求都访问到了数据库,造成了数据库崩掉的现象。
解决方案有两种,一种是热点key永不过期。一种是使用互斥锁。
这里说的key永不过期不是直接设置redis的key永不过期。当然这样也可以。更好的解决方案是单独开个定时任务,监测热点key的过期时间,快过期的时候给它进行续活操作。
互斥锁是当发现redis没有缓存,load db的时候,加一个互斥锁,使用setnx命令。抢到这个锁的程序去load db。没有抢到锁的程序,继续去get 缓存。以此解决缓存击穿。

四、缓存雪崩

由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存 层由于某些原因不能提供服务,比如同一时间缓存数据大面积失效,那一瞬间 Redis 跟没有一样,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。
缓存击穿是一个热点key,缓存雪崩是多个热点key失效。
解决方案: redis高可用,避免单点故障发生的缓存雪崩。
热点key过期时间分散,避免同一时间集体失效。

你可能感兴趣的:(redis,缓存,redis,数据库)