【高并发架构】缓存的挑战

缓存

  使用缓存,出于两个场景需要:高性能,高并发;计算机的业务数据,为了实现高可用和安全性,必然要进行持久化处理,但目前的技术而言, 持久化到硬盘或者从硬盘中查询本来就是一件非常慢的事情,但实际我们又要求在大量的数据中快速访问,所以,一般我们会把数据提前加载内存中,实现高性能访问;而高性能意味着可以做到高并发。

双写一致性

  只要用缓存,就可能会涉及到缓存与数据库双存储双写,双写会有数据一致性的问题;

  方案:串行化读写:串行化读写就是读和写都在一个库中操作,不做读写分离,串行化可以保证不会出现读写不一致情况,但效率会大大降低,通常涉及事务和锁表行等。所以如果业务不要求完全一致性,通常不要采取这样的方案。

  方案:最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。

  • 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先更新数据库,然后再删除缓存,或者先更新缓存再更新数据库,他们都各有优点和缺点。

  问题:为什么是删除缓存,而不是更新缓存?

  1、在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。

  2、懒加载策略

  问题:先删缓存,中间插入一个查询,又缓存了旧的值,最后更新数据库,问题就出现了,缓存和数据库长时间不一致,怎么办?

  这是一个同步问题,如果用锁解决就太过麻烦了,比较好的是利用MQ异步化这两个请求,同时保证他们的执行顺序,就可以保证缓存和数据库必然一致,考虑具体业务的时候,还可以做更多的优化,比喻允许非强一致性,可以发了获取缓存请求后,就去数据库取一个旧值来用一会。

缓存雪崩

  问题:对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。这就是缓存雪崩。

  

  

  方案:缓存雪崩的事前事中事后的解决方案如下。

  • 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死(注意ehcache是java的实现,其他程序请另行参考)。
  • 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

缓存穿透

  问题:对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。

  黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。

  举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

redis-caching-penetration

  方案:解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。

  方案:布隆过滤器(Bloom Filte),这个就是一个bitmap和hash结合的算法,可以保证存在的数据一定会被判别为存在;所以把所有的key放到过滤器中,查询的时候如果发现不存在,那一定是不存在的key,如果发现是存在的key,也有可能该key不存在,再配合方案1,这样就不可能会被缓存穿透。

缓存击穿: 

  问题:缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

  方案:简单粗暴,可以将热点数据设置为永远不过期;

  方案:其实击穿也是一个同步问题,同步问题的解决方案可以使用 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。

原文地址:https://www.cnblogs.com/iCanhua/p/10604991.html