文章源自公众号:后端学长投稿
这是程序员cxuan的第 9 篇原创分享前端
在好久好久之前人类和洪水做斗争的过程当中,水库发挥了相当重要的做用 : 在发洪水时能够蓄水,缓解洪水对下游的冲击;在干旱时能够把库存的水释放出来以供人们使用。这里的水库就起着缓存的做用。在现在互联网的世界里随着互联网的普及,内容信息愈来愈复杂,用户数和访问量愈来愈大,咱们的应用须要支撑更多的并发量,同时咱们的应用服务器和数据库服务器所作的计算也愈来愈多。程序员
可是每每咱们的应用服务器资源是有限的,且服务器技术变革是缓慢的,数据库每秒能接受的请求次数也是有限的,那么如何可以有效利用有限的资源来提供尽量大的吞吐量呢?一个有效的办法就是引入缓存,打破标准流程,每一个环节中请求能够从缓存中直接获取目标数据并返回,从而减小计算量,有效提高响应速度,让有限的资源服务更多的用户。面试
缓存就是数据交换的缓冲区(称做Cache),这个概念最初是来自于内存和 CPU。当某一硬件要读取数据时,会首先从缓存中查找须要的数据,若是找到了则直接使用执行,缓存找不到的话则从内存中找。因为缓存的运行速度比内存快得多,故缓存的做用就是帮助硬件更快地运行。redis
当用户从键入一个地址到页面的展现过程当中一般包含了不少种缓存。有前端缓存、本地缓存(协商缓存,强缓存等)到咱们的网关缓存(CDN 缓存)、最后到咱们服务端缓存。服务端缓存又区分为进程缓存(本地缓存),还有比较火的分布式缓存,最后到了数据库层面的缓存。以下图所示:算法
在咱们一般的软件设计中,有一些热点数据须要展现到页面,咱们一般当这些数据缓存到内存或者其余读写速度优异的框架中。减小与数据库进行 I/O 操做。提高数据的响应速度。这一切看起来就是这么完美。
实际上,在缓存系统的设计架构中,还有不少坑。若是设计不当会致使不少严重的后果。设计不当,轻则请求变慢、性能下降,重则会数据不一致、系统可用性下降,甚至会致使缓存雪崩,整个系统没法对外提供服务。数据库
接下来咱们着重讲述一下在缓存设计过程当中几大经典的问题。后端
缓存失效缓存
先解释一下什么叫作缓存失效安全
咱们在存放缓存的时候,能够指定缓存 Key 的失效时间,当失效时间到了,此缓存就会失效,因为在缓存中找不到该数据,因此这个时候若是用户有请求该数据就绕过缓存直接到数据库中请求数据。服务器
看到这里小伙伴们确定有不少问号?
这不是很正常的现象嘛?为何要把这个问题拿出来讲呢?莫急看下图图示
这里咱们经过两个场景来讲明一下
场景一:这种状况下通常不会对数据库形成比较严重的影响,由于失效的 key 的数量比较少,即便同时请求到数据库层面也是能够接受的。
解决方案
看到这里不少聪明的小伙伴其实已经想到了。场景 2 的事故主要由于不少 key 一块儿失效的缘由,跟咱们平常写缓存的过时时间息息相关。若是咱们在平常的开发过程当中须要将一批 Key 设置到缓存中并制定失效时间。这个时候就要注意场景 2 发生的状况。咱们能够在失效时间 + 随机时间。避免大量 Key 失效冲击咱们的数据库。
缓存击穿
一般状况下,咱们去查询数据都是存在的。那么若是请求去查询一条压根儿数据库中根本就不存在的数据,也就是缓存和数据库都查询不到的这条数据会怎么样呢?这样会致使每次访问都会直接打到数据库上面去。这种查询不存在数据的现象咱们称为缓存穿透。
下面是缓存失效的场景
不少伙伴看到这里确定又会以为这是一件很正常的事情。试想一下,若是有***会对你的系统进行***,拿一个不存在的 key 不停的去查询数据,会产生大量的请求到数据库去查询。可能会致使你的数据库因为压力过大而宕掉。
解决方案一
固然这网关层所能作到的只是一些简单过滤。每一个后端的设计人员应该对服务的可用性和健壮性负责。接下来咱们看看服务端应该如何处理
可是***/恶意***者是不会这么轻易被打发的。每次请求都会传不一样的 key 来***咱们的服务。这个时候这个方案起不到做用了。
解决方案二
构建一个 BloomFilter(布隆过滤器) 缓存过滤器,记录全量数据。这样访问数据时,能够直接经过 BloomFilter 判断这个 key 是否存在,若是不存在直接返回便可,根本无需查缓存和 DB。这样在缓存以前加了一层校验。若是key 值不存在,就不会请求到咱们的缓存更加不会到咱们的数据库中。
布隆过滤器能够理解为一个不怎么精确的 set结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判。可是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度能够控制的相对足够精确,只会有小小的误判几率。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就确定不存在。即便误判不存在走到缓存和后端服务也是能够接受的。
缓存雪崩
缓存雪崩是指缓存的部分节点不可用致使整个缓存体系甚至整个服务系统不可用。
那么你可能会有疑问,缓存雪崩和缓存击穿有什么关系呢?
从概念上来看,缓存击穿是由于查询不存在的 key 穿透缓存直接访问咱们的数据库。而缓存雪崩是由于咱们的缓存节点不可用,请求未通过缓存就直到了咱们的数据库层面。然而二者都会影响咱们的服务稳定性。
缓存节点的不可用会致使缓存雪崩,那么咱们缓存组件集群部署是否是就解决了这个问题呢?
集群部署有两种状况:
第一种状况:发送雪崩的时候通常是多个节点同时不可用,例如咱们的节点服务器内容不足,虽然分主从节点都是存储的数据都是同样的。若是缓存中的数据过大致使节点不可用。那大部分节点也会存在这个问题。请求会大面积的落到数据库层面致使后端系统崩溃。
第二种状况: 首先看一下下图虽然数据根据会根据取模算法分配到不一样的节点中,假设节点 A 不可用,数据 A 会按照逆时针找到节点 B,会由于原本应该存放到节点 A 的数据存放到节点 B,以此类推会致使整个缓存节点不可用。请求也会大面积落到咱们后端的数据库层面致使系统崩溃。
解决方案
数据不一致
数据不一致的概念很简单:就是缓存中的数据和数据库中的数据不一致。
那为何会不一致呢?咱们的数据被缓存以后,一旦数据被修改(修改时也是删除缓存中的数据)或删除,咱们就须要同时操做缓存和数据库。这时就会存在一个数据不一致的问题。
如上图所示当咱们先删除数据库再去操做缓存,缓存中未删除数据库其实已经不存在该数据了。这个时候就会出现缓存不一致的状况。
聪明的小伙伴确定想到了咱们仍是须要先作缓存删除操做,再去完成数据库操做。则会去数据库中查询,若是缓存中没有该数据,则会去数据库中查询,以后再放入到缓存中。这样就完美了嘛?答案确定不会这么简单。请看下图:
解决方案
这里其实没有什么很完美的解决方法。能够将变动的 key 添加到安全队列中。当另外一个查询请求 B 进来时,若是发现缓存中没有该值,则会先去队列中查看该数据是否正在被更新或删除,若是队列中有该数据,则阻塞等待,直到 A 操做数据库成功以后,唤醒该阻塞线程,再去数据库中查询该数据。这里其实也是有不少缺陷的。线程须要阻塞等待。
最好的解决方案就是若是数据更新比较频繁且对数据有必定的一致性要求,我一般不建议使用缓存。看到这里是否是发出了一句切!!!!
缓存虽然能大幅度的提升服务器的性能以及用户的体验感。可是随着而来的就是各类因为缓存致使的一系列问题。因此当咱们使用缓存的过程当中须要注意以上的经典问题。
目录
作了15年的技术开发后,我确实焦虑了。。。
40 个 Java 多线程问题总结