一、DB操作与缓存操作一致性的问题
当你既要更新数据库,又要更新缓存的时候,就涉及到一致性的问题,如何保证两者能同时成功呢?
- 通过消息队列,将对缓存的操作放到队列实现,可以确保其成功。
- 订阅 binlog 日志,需要借助 canal 工具来知道数据库做了哪些操作,然后去操作缓存。
- 通过 dtm 二阶段消息,也可以确保缓存被操作成功。
二、如何删除缓存
1、先删缓存再更新数据库
- 线程A删除缓存,还没来得及更新数据库。
- 线程B进来了,发现缓存没有,读取数据库(此时是旧值),将旧值回填缓存。
- 线程A更新了数据库。
会出现缓存中的数据是旧值。
缓解方法
- 设置简短的过期时间兜底,在这个时间内会出现不一致,缺点是数据库负载会加大。
- 延时双删,线程A删除缓存,更新数据库,sleep一会,再删一次缓存,这个睡眠时间是个问题,所以没办法根治。
- 在应用层引入类似版本的机制,对应用层有要求,通用性受限,不易复用。
2、先更新数据库再删缓存
- 线程A更新了数据库中的值,但还没来得及删除缓存中的值。
- 线程B读取到缓存,此时为旧值。
- 线程A删除缓存。
这种情况总体对业务影响较小。一般在生产环境中,也推荐大家采用该模式。
三、如何写入缓存
-
线程A发现缓存为空,查询数据库,准备回填缓存的时候发生了阻塞。
-
线程B发现缓存为空,查询数据库,此时看到的值为新值,因为其他线程修改了数据,回填缓存。
-
线程A苏醒过来,回填缓存,此时是旧值。
会出现缓存中的数据是旧值。
如果要想解决这种问题,那就只能在读缓存的时候加分布式锁,当然在这个过程中可以做一些优化,具体可以使用 rockscache,它的说明,具体原理图
四、缓存击穿
前面说到,我们应该先更新数据库,再删除缓存,那么对于热点数据,突然把缓存删了会导致缓存击穿,对数据库造成巨大冲击,针对这种情况,我们需要限制最终去到数据库的流量,我们可以在进程内部加锁,也就是单飞模式,这样每个进程只有一个查询数据库的操作,当然如果你的项目服务器很多,那么对数据库的查询也会很多,那么只能使用分布式锁来加以限制。
五、缓存穿透
如果请求的数据在数据库中也不存在,那么为了节省缓存空间一般是不会缓存它的,这就会导致这些请求都会一次次的被打到数据库,这就是缓存穿透。
首先,如果预防住了缓存击穿,那么在一点程度上也减轻了缓存穿透的危害,除此之外,我们还可以引入布隆过滤器将这些恶意的key存起来。
六、缓存雪崩
如果在某一个时刻,缓存大面积同时过期,那么这就是缓存雪崩,预防的方式也很简单,为不同的key设置不同的过期时间,过期时间最好带一个随机数。