缓存雪崩
- 什么是缓存雪崩?
缓存中数据大批量到过期时间,或者缓存中间件宕机,而查询数据量巨大,引起数据库压力过大甚至down机。 - 如何解决缓存雪崩?
事前: 保证redis集群高可用,采用主从+哨兵,或者redis cluster,避免全盘崩溃。
事中:本地ehcache缓存+限流组件,保证数据库绝对不会死,同时系统不是完全不可用。
事后:redis持久化机制,尽快恢复缓存集群,一旦重启,自动从磁盘上加载数据,恢复内存中的数据。
缓存穿透
- 什么是缓存穿透?
黑客恶意攻击,导致缓存根本无法命中,可能把数据库直接打死。 - 如何解决缓存穿透?
每次从数据库没查到key对应的结果时,就把这个key写入缓存,值为空。减缓数据库被打死。
缓存+数据库读写的模式
- Cache Aside Pattern
读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应
更新的时候,先删除缓存,然后再更新数据库 - 为什么是删除缓存,而不是更新缓存呢?
28法则,一般更新缓存的代价是很高的,删除缓存,而不是更新缓存,就是一个lazy计算的思想。
数据库与缓存双写不一致
-
最初级的缓存不一致问题以及解决方案
数据库更新成功,然后删除缓存失败,导致数据不一致。解决方法,调整操作顺序。 -
比较复杂的数据不一致问题分析
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。另一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。数据变更的程序完成了数据库的修改。 -
比较复杂的数据不一致问题解决方案
数据库与缓存的更新与读取操作进行异步串行化。
更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个jvm内部的队列中。
读取数据的时候,如果发现数据不在缓存中,那么将读操作根据唯一标识路由之后,也发送同一个jvm内部的队列中。
一个队列对应一个工作线程。
每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先执行删除缓存,然后再去更新数据库,但是还没完成更新。
此时如果一个读请求过来,读到了空的缓存,那么可以先将读请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
这里有一个优化点,一个队列中,其实多个读请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个读请求了,那么就不用再放个读请求进去了,直接等待前面的读请求完成即可。
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回; 如果请求等待的时间超过一定时长,那么就直接从数据库中读取当前的旧值。读请求等待时间的取值,需要考虑实际情况,需要进行严格的测试。
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过nginx服务器路由到相同的服务实例上
redis写操作并发竞争问题
- 是什么?
多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了。或者是多客户端同时获取一个key,修改值之后再写回去,只要顺序错了,数据就错了。 - 如何解决?
通过分布式锁来同步多个写操作,在执行写操作的时候,比较自己的版本是否比缓存中的版本更新,如果是,可以更新缓存;如果不是,就放弃执行。
面试题:生产环境中的redis是怎么部署的?
- redis cluster
10台机器,5台机器部署了redis主实例,另外5台机器部署了redis的从实例,每个主实例挂了一个从实例,5个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒5万,5台机器最多是25万读写请求/s。 - 机器是什么配置?
32G内存+8核CPU+1T磁盘,但是分配给redis进程的是10g内存,一般线上生产环境,redis的内存尽量不要超过10g,超过10g可能会有问题。
5台机器对外提供读写,一共有50g内存。
因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis从实例会自动变成主实例继续提供读写服务 - 你往内存里写的是什么数据?每条数据的大小是多少?
商品数据,每条数据是10kb。100条数据是1mb,10万条数据是1g。常驻内存的是200万条商品数据,占用内存是20g,仅仅不到总内存的50%。
目前高峰期每秒就是3500左右的请求量