概述
接着上篇去聊一下,缓存的主要类型
缓存的类型主要分为客户端缓存,浏览器缓存,CDN缓存,反向代理缓存,应用缓存等;
客户端缓存相对于其他端的缓存而言,要简单一些,目的就是加速各种静态资源的访问,想想现在的大型网站,随便一个页面都是一两百个请求,每天 pv 都是亿级别,如果没有缓存,用户体验会急剧下降、同时服务器压力和网络带宽都面临严重的考验。客户端缓存主要分为两种:页面缓存和浏览器缓存
页面缓存
页面缓存有两层含义: 一个是页面自身对某些元素或全部元素进行缓存;另一层意思 是服务端将静态页面或动态页面的元素进行缓存, 然后给客户端使用。 这里的页面缓存指 的是页面自身的缓存或者离线应用缓存。
页面缓存是将之前渲染的页面保存为文件, 当用户再次访问时可以避开网络连接, 从而减少负载, 提升性能和用户体验。
浏览器缓存
浏览器缓存:这种形式使用很广泛,极大地提升了用户体验,但有时会出现没及时更新导致显示“错误”的信息。把已经请求过的Web资源(如html页面,图片,js,css等)拷贝一份副本储存在浏览器中,缓存会根据进来的请求保存输出内容的副本。这种缓存带来的好处有三点:减少网络带宽消耗,降低服务器压力,减少网络延迟、加快页面打开速度,适合请求量大、静态的数据请求。
当我们请求一个网页的时候,服务器会向浏览器返回大量数据,但是这些数据需要全部缓存吗?浏览器又是如何区分哪些数据需要进行缓存,哪些是需要实时跟源站获取的?接下来我们就来看一下浏览器的缓存策略。
服务器可以在响应中返回 ETag,然后浏览器会在后续的请求中携带上这个参数来确定缓存是否需要更新。如果 ETag 值相同,说明资源未更改,服务器会返回 304响应码,浏览器就知道本地缓存仍然是可以使用的。
不过需要注意的是,ETag 只有在本地缓存已过期(Expires)或者缓存模式设置为 no-cache(Cache-Control)的时候,才会被浏览器携带上与服务器端的值进行判别。
CDN缓存
CDN 是互联网上内容分发的重要一环。无论您之前是否了解过 CDN,其实它已经在您的日常生活中发挥作用了。比如您正在淘宝挑选心仪的商品,或者在观看本视频,这些资源展示的背后都有 CDN 的默默支撑。
为什么 CDN 使用如此广泛呢?首先大家需要知道,CDN 旨在解决的最重要的问题是什么,我们称之为网络延迟。举个例子,当您输入一个网址,敲击回车后到网页内容实际出现在屏幕上,中间加载耗费的这个时间,就是网络延迟。通过网络获取资源总是比从本地获取慢,无论服务器是在同一个局域网中还是位于世界的另一个角落,都是如此。这里的速度差异是 IT 行业的一个核心问题,开发者想了很多办法试图去弥补这个差异,CDN 就是应用最为广泛的一个解决方案。
CDN 为解决网络延迟提供了一整套技术方案,在使用了 CDN 之后,数据是如何被缓存的,以及缓存是如何提高数据加载速度的。
在未接入CDN 之前,用户使用浏览器访问服务的时候,相互交互的过程如下图所示。
用户在第一次访问网站服务器的时候,浏览器会从服务器获取所有的资源,在传输过程中,浏览器会通过一些约定好的响应头,从而确定是否需要将这个资源保存一份到本地作为缓存。当用户第二次访问该网站的时候,浏览器就会优先从缓存中加载资源,不用向服务器请求资源,从而提高了网站的访问速度。
而对于一些用户访问量巨大的网站而言,如果所有用户都去服务器请求数据,服务器会很快崩溃,并且在不同网络以及不同地区的用户,请求服务器的速度也不一样。为了提高这部分用户的访问速度,CDN 中又提出了新的网络架构,即创建一些最接近用户网络的边缘服务器,然后将文件缓存在这些边缘服务器(节点)上,这就是 CDN 缓存。
反向代理缓存
最近工作中用到反向代理,我只知道有代理这个概念,并不清楚代理还有正向和反向之分,首先弄清楚什么是正向代理,什么是反向代理,然后是二者在实际使用中展示的方式是什么样的,最后总结一下正向代理用来做什么,反向代理可以做什么。
正向代理( Forward Proxy ):
目前国内无法访问google,但是我们有时说挂个代理,然后就能顺利访问,而这种代理模式就是正向代理。假如我们在香港有一台服务器,这台服务器是能访问google的,而国内无法直接访问谷歌,但是可以访问香港的服务器。每次我们请求香港服务器,香港服务器拿到我们请求以后,再去访问google服务器,google服务器把响应返回给香港服务器,香港服务器再把响应返回给我们。这样我们就能顺利的访问google了。
正向代理是指是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容, 客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
正向代理最大的特点是
- 代理客户;
- 隐藏真实的客户,为客户端收发请求,使真实客户端对服务器不可见;
- 一个局域网内的所有用户可能被一台服务器做了正向代理,由该台服务器负责 HTTP 请求;
- 意味着同服务器做通信的是正向代理服务器;
反向代理( Reverse Proxy ):
例如淘宝,每天访问量很大,不可能只用单个服务器处理所有业务,于是出现了分布式部署。也就是通过部署多台服务器来解决访问人数限制的问题。
客户端请求taobao.com,DNS服务器把域名解析到nginx服务器上(简单的这么理解),nginx服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。
反向代理是指以代理服务器来接受 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器
反向代理最大的特点是
- 代理服务器;
- 隐藏了真实的服务器,为服务器收发请求,使真实服务器对客户端不可见;
- 负载均衡服务器,将用户的请求分发到空闲的服务器上;
- 意味着用户和负载均衡服务器直接通信,即用户解析服务器域名时得到的是负载均衡服务器的 IP ;
正向代理其实是客户端的代理,反向代理则是服务器的代理。正向代理中,服务器并不知道真正的客户端到底是谁;而在反向代理中,客户端也不知道真正的服务器是谁。正向代理主要是用来解决访问限制问题;而反向代理则是提供负载均衡、安全防护等作用。
上面讲到的代理缓存、反向代理缓存、CDN 缓存,都是通读缓存。它代理了用户的请求,也就是说用户在访问数据的时候,总是要通过穿透型缓存。
今天我们来介绍一下缓存架构的常用实现方式,常见的缓存架构主要有两种:
- 穿透型缓存
- 旁路型缓存
穿透型缓存是将缓存与后端数据库的交互细节对应用层服务隐藏
应用层服务所有的读写请求均请求缓存,读请求 miss 后,缓存向后端数据服务器请求数据,先更新缓存后返回
而写请求也是同样的,先写入缓存服务器,后同步给后端服务器
在旁路缓存模式中,如果数据未缓存,应用便从后端存储中获取数据,并将数据放入缓存中以备后续读取。
这种模式的优势在于它不需要开发人员向缓存服务器中部署任何代码。相反,旁路模式让开发人员和应用代码负责管理缓存。但缓存控制这个优势,也需要和缓存管理成本一起考虑。
大部分业务场景是“一写多读”的场景,在这样的场景下,旁路型缓存是非常适用的
读请求
上图展示了旁路型缓存的读 miss 情况的处理:
- 应用服务器先请求缓存服务器,如果数据存在则直接返回
- 如果缓存 miss,则应用服务器请求后端数据库
- 应用服务器将后端数据库返回的数据更新到缓存服务器
写请求
对于写请求,这个模式要求所有的数据更新都需要删除缓存中对应的数据,官方建议旁路型缓存的设计原则是先操作后端数据库后操作缓存
在旁路缓存模式中,在应用层的控制力更大。而在穿透型缓存中,代码部署到缓存服务器,由缓存负责控制和后端存储之间的读写交互。
在这里插入讲解一下各种介质数据访问的延迟,以便对数据的存储、缓存的特性以及数据的访问延迟有一个感性的认识。
作为一名优秀的架构师,更应该深入理解各种介质解数据访问的延迟,以便对计算机的层次存储结构,缓存特性以及数据延迟有感性的认识,有利于我们更好的利用程序的局部性原理,编写出高效的代码,所以话不多说,先附上一张图说明情况。
存储器是分层次的,离CPU越近的存储器,速度越快,每个字节的成本越高,同时容量也因此越小。寄存器速度最快,离CPU最近,成本最高,所以以个数容量有限,其次是高速缓存(缓存也是分级,有L1,L2等缓存),再次是主存(普通内存),再次是本地磁盘。
在计算机中, CPU所需要的数据全部都来自于内存, 不管是在内存本身的数据还是在磁盘上的数据还是存在网络上的数据, 最终终究是会读取到内存中去, 然后CPU才能够得到数据并作出相应的反应。而在CPU和内存之间就存在者另一种一种物理硬件就是缓存,缓存中往往存着CPU正在执行的一些指令,这样就会减少CPU对内存访问的次数,从而加快CPU的执行效率。在这里所讲的缓存是计算机设备的缓存。
虽然使用缓存给系统带来了一定的质的提升,但同时也带来了一些需要注意的问题。
使用Redis时经常遇到的几个问题。缓存雪崩、缓存穿透、缓存预热、缓存更新。
首先我们使用Redis的逻辑是这样的
- 缓存雪崩
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 解决办法:
- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
- 事中:本地缓存 + hystrix 限流&降级,避免 MySQL 被打死。
- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据
用户发送一个请求,系统 A 收到请求后,先查本地缓存,如果没查到再查 redis。如果 本地缓存 和 redis 都没有,再查数据库,将数据库中的结果,写入 本地缓存 和 redis 中。
限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值。
这种架构的好处是:
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。
2、缓存穿透
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
3、缓存预热
是一种机制, 就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。
4、缓存更新
怎么样保证缓存中的key是实时有效的,以及及时的更新数据资源
解决办法:
1)缓存服务器自带的缓存失效策略
2)定时去清理过期的缓存;当用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
分布式对象缓存
下面看一下分布式对象缓存,如下图所示。
为什么要使用分布式缓存
高并发环境下,例如典型的淘宝双11秒杀,几分钟内上亿的用户涌入淘宝,这个时候如果访问不加拦截,让大量的读写请求涌向数据库,由于磁盘的处理速度与内存显然不在一个量级,服务器马上就要宕机。从减轻数据库的压力和提高系统响应速度两个角度来考虑,都会在数据库之前加一层缓存,访问压力越大的,在缓存之前就开始CDN拦截图片等访问请求。
那要是我们的缓存节点挂了,不可用了,那岂不是又回到了原点,请求都会打到我们的数据库中的。所以,我们在使用缓存一定要搭建高可用缓存,避免上面的单点缓存架构。今天,我们就来学习该怎么做缓存的高可用方案即搭建分布式缓存的高可用方案。
依据经验来说,对于分布式缓存高可用方案目前一般采用应用端、中间代理层以及服务端这三大方案。
- 应用端方案,在应用端自己配置缓存节点,通过缓存写入和读取算法策略来实现分布式,从而提高缓存的可用性。
- 代理层方案,在应用代码和缓存节点之间增加一个独立的代理层,应用端就直接喝代理层连接,代理层自己内置高可用策略,以提升缓存的可用性。
- 服务端方案,即为缓存服务自身提供的高可用,例如Redis Sentinel
接下来我们就来分别看一下这三种方案
应用端方案
在应用端也就是代码层面上,我们就需要自己管理缓存的读和写,也就是通过写代码方式来进行分布式缓存的写入和读取,主要是下面这两模块:
- 写缓存时,我们需要将数据分散到缓存的各个节点中,即要实现数据分片。
- 读缓存时,需考虑主从或者多副本粗略以及使用多组缓存进行容错。
下面我们来看看该怎么进行设计,其实这种设计思路不一定局限在缓存上,我们大部分的底层开发都能用上,希望大家好好掌握
缓存数据如何分片
我们知道单节点的缓存因受到各种原因如本身机器内存、网络带宽等,从而不能承受更高的并发,所以我们需要将数据进行分片存储,即将数据通过分片算法打散到各个缓存节点中。其实这块大家有没有注意到和我们前面的分库分表很类似,所以大部分架构思想都是相通的。
现在我们的数据就在各个缓存节点都有一部分,即使部分故障,也是不影响我们整个业务的。那这个时候,你可能在想,既然数据需要被均匀分散到各个节点,那我该怎么来写这个分片算法呢?别急,我们下面就来看怎么写这个分片算法。
数据分片算法
一般做数据分片算法的有两种,大家应该都清楚吧,前面分库分表就有用到的
- Hash分片算法
- 一致性Hash分片算法
Hash分片算法
Hash分片算法就是我们拿到缓存的key,然后对其做hash运算,最后将hash运算的结果对缓存总节点数取余,得到的数字则为具体的分片节点。比如,现在我们缓存节点一共有 3 个,当我们写入数据的时候,将key进行hash运算hash(key),然后将结果对3取余就行了,如下图所示:
这种分片算法优点就是开发简单且容易理解,缺点就是当我们的缓存总节点数改变的时候,就会导致数据不均匀,则会造成大量缓存失效不可用的情况。但是这种算法我们开发中也是会使用的,比如我们的业务对于缓存的命中率不是那么太在意的,就可以使用这种hash分片算法。
一致性Hash分片算法
上面简单的Hash分片算法对缓存命中率要求较高的业务会有一定影响,所以一致性Hash分片算法就出来了,它很好的解决了因缓存节点的增加或减少带来的缓存命中率下降的问题。那我们就来看看它是怎么做的。
基于上面的问题,提出了hash环的概念。hash环的过程有两次hash
(1) 把所有的机器编号hash到这个环上
(2) 把key也hash到这个环上,然后在这个环上进行匹配,看这个key和哪台机器匹配
具体过程是这样: 假定有一个hash函数,其值空间为(0 ~ 2^32-1)。也就是说,其hash值是个32位无整型数字,这些数字组成一个环。首先对机器进行hash(比如根据机器ip),算出每台机器在这个环上的位置; z再对key进行hash,算出该key在环上的位置,然后从这个位置往前走,遇到的第一台机器就是该key对应的机器,就把该(key, value)存储到该机器上,如下图所示。
首先计算出每台cache服务器在环上的位置(图中浅蓝色的大圆圈),然后每来一个key计算出value填到环上的位置(图中橙色的小圆圈),然后顺时针走,遇到的第一个机器,就是要存储的机器
这里的关键点是:当机器数N变化时,其他机器在环上的位置并不会发生改变。这样只有增加/减少的那台机器附近的数据会失效,其他机器上的数据还是有效的。
数据倾斜问题
当机器不很多时,很可能出现几台机器在环上面贴的很近,分布很不均匀。这将会导致大部门数据集中在某几台机器上。
为了解决这个问题,可以引入"虚拟机器"的概念,也就是说,一台机器需要在环上映射出多个位置。比如我们用机器的ip来hash,那么我们可以在ip后面加几个编号,形如ip_1, ip_2, ip_3... 这样就实现了一台物理机器映射出了多个虚拟机器的编号。
数据首先映射到"虚拟机器"上,再从"虚拟机器"映射到物理机器上。因为虚拟机器可以很多,在环上均匀分布,从而保证数据相对均匀地分布在物理机器上。
zk的引入
上面我们提到了服务器的机器数N的变化,那么如何通知到客户端呢
一种笨方法就是手动,当机器数N变化,重新配置客户端,重启客户端。
另外一种,引入zk,服务器的节点列表注册到zk上面,客户端监听zk。发现节点数发生变化,自动更新自己的配置。
当然不用zk用一个其他的中心节点也可以,只要能实现这种更改的通知即可(也即分布式服务协调)
中间代理层方案
上面的应用端方案基本能解决我们绝大部分问题了,现在主要是像有些公司技术语言比较多的话,这种就得每种语言都得开发一套,比如我们公司有Java PHP 还有.net之类的,那么这个时候就需要中间代理层来最好不过了,不需要业务方进行考虑这些复杂情况,直接连接代理层就行了
代理层自己管理缓存节点高可用,通过某种协议,如redis协议,来和各种语言业务端连接。业界也有很多中间代理层方案,比如Twitter 的Twemproxy,豌豆荚的Codis。基本架构如下
如图所示,中间层代理方案即所有缓存读写的操作都直接通过代理层完成,代理层自己完成上面应用端所有的操作。
服务端方案
服务端方案主要是缓存服务自己管理的,对于开发人员不用自己写代码管理,也不用引入中间层,就是需要相关运维配置支持,比如redis的sentinel模式就是用来解决redis部署时高可用问题,它可以在主节点挂了以后自动将从节点提升为主节点,保证整体集群的可用。
总结,今天我们讲到了在使用缓存的时候为了避免单节点所带来的各种问题,所以我们需要搭建高可用缓存架构,共讲到了三种方案,应用端、中间代理层以及服务端方案,大家可以根据公司的资源情况来选择合适的方案。
最后
以上就是大方早晨为你收集整理的缓存面试五连击(下篇)的全部内容,希望文章能够帮你解决缓存面试五连击(下篇)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复