缓存相关
数据库和缓存一致性问题
如果对数据库查询做了缓存,那么在更新数据库(增、删、改)的时候,就可能存在缓存和数据库数据不一致的情况。对于数据一致性要求比较高的场景,就需要重点考虑数据库和缓存怎么尽量减少不一致的情况。
对数据库做更新的时候,更新缓存的几种策略如下:
- 先删除缓存,再更新数据库。
- 存在问题:删除完缓存,更新数据库期间,有其他请求过来查询数据并更新缓存,那么就会产生数据不一致的情况。
- 解决方案:使用延迟双删的策略,更新完数据库一定时间后再次删除缓存。这种方案也是无法完美解决不一致情况的,只能尽量减少出现不一致的时间。在更新完数据库到再次删除缓存期间的查询就有可能拿到旧数据。
- 先更新数据库,再删除缓存。
- 存在问题:更新数据库到删除缓存这段时间内,如果有查询请求进来,那么拿到的还是缓存中的旧数据。
- 解决方案:异步更新缓存(基于订阅mysql的binlog的同步机制)。Mysql的binlog增量订阅消息+消息队列+增量数据更新到redis。一旦mysql中出现更新操作,就把binlog相关消息推送到redis,redis再根据binlog记录对redis更新。
- 串行化解决数据库缓存一致性问题。
缓存穿透
- 缓存穿透指的是,一条不存在数据库中的查询,因为每次查回来都是空的,不做缓存,所以对于大量不存在的数据发起的请求,会大量占用数据库资源。
- 解决方法:
- 布隆过滤器:预先将数据库中所有的数据存到布隆过滤器中,表示此数据存在。每次查询的时候先走布隆过滤器判断数据是否存在。
- 布隆过滤器原理:
布隆过滤器底层数据结构是一个固定长度的位数组,初始状态所有位都为0。当将变量添加到布隆过滤器时,通过k个哈希函数,将变量映射到数组上的k个点,并将这k个点的值设置为1。当查询某个变量是否存在的时候,同样通过k个函数将变量映射位k个点,检查是否每个点都为1,如果都为1表示可能存在,否则不存在。需要注意的是,布隆过滤器判断存在的条件并不是一定的,有一定概率误判。 - 布隆过滤器的缺点:
- 容易误判,且数据量越大越容易误判。
- 不能删除里面的元素,因为删除了会影响大量的其他数据。
- redis有布隆过滤器的插件(
RedisBloom
)。 -
Redission
可以直接操作布隆过滤器。
- 将空结果也作为返回结果缓存下载,但是这个值的过期时间需要设置的相对较短。
缓存击穿
- 缓存击穿指得是,设置了缓存的热点数据,在某一刻缓存失效了,导致大量请求全部打到数据库中,使得数据库压力急剧增加而影响系统整体运行情况。
- 解决方法:
- 设置热点数据永不过期,那么就不会存在这种情况。
- 使用互斥锁,来限制直接发起查询数据库的情况,具体方法如下几种:
- redis的setnx实现分布式锁:当缓存过期的时候,所有请求查询缓存为空,首先去获取分布式锁,检查是否被占用。如果被占用,则等待一段时间,继续检查缓存是否有值。如果没有被占用,则查询数据库的值,放到缓存中然后释放锁。
- 单机应用直接使用Lock(
ReentrantLock
互斥锁)。
- 使用hystrix限流。
- 使用两级缓存:每个微服务本地设置一个缓存作为一级缓存(ehcache),redis再存一份缓存,作为二级缓存。优先查本地缓存,如果本地缓存为空,则查二级缓存。如果都为空才去查数据库。
缓存雪崩
- 缓存雪崩指的是,大量缓存数据同时失效,或者缓存服务器宕机,这时候大量数据请求全部发到数据库中,导致数据库系统压力过大。
- 解决方案:
- 给缓存设置不同的过期时间,错开缓存同时过期的情况。可以在缓存的时间上加上1~5分钟的随机时间。
- 保障缓存服务器(Redis)的高可用性。
- 对缓存数据的交易进行资源隔离、限流、熔断等操作,一旦遇到缓存服务器宕机的情况,可以直接熔断。
- redis数据备份和事后恢复。
Redis实现分布式锁
- Redis实现分布式锁的原理是
setnx
命令,如果指定的key已经有值,则返回0,否则返回1,并设置为指定的值。这是一个原子操作,分布式情况下也能保证原子性。 RestTemplate
里面封装了对应的方法RestTemplate.opsForValue().setIfAbsent()
,设置成功则返回true,设置失败则返回false。- 实现分布式锁:
> setnx key value // 加锁
> expire key 1000 // 设置锁定最长时间,避免设置锁的应用挂掉而长久占有锁
> del key // 解锁