欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

马哥 45_03 _http缓存机制及varnish详解 有大用

反向代理:缓存

HTTP/1.1

        RFC

每一种协议都有一个请求注解文档, Request For Comments(RFC)

HTTP/1.1 <--HTTP/1.0

HTTP/1.1对缓存(cache)功能更精细化的设置,

HTTP本身由众多的web对象(web object)组成的, 这些对象本身有的是静态的,有的是动态生成的,

为了加速HTTP的访问,现在的浏览器(browser)里面都引入了缓存机制,能够将此前我们所访问的静态内容或者是可缓存的动态内容缓存到本地,再次到服务器请求原有内容的时候,如果原始服务器内容没有发生改变,那么它将使用本地资源,而不是远程服务器上的资源,,,,,

HTTP到底如何支持缓存机制的,

虽然浏览器可以缓存内容,但是并非所有内容都可以缓存,

浏览器缓存应该有失效期限,

用户登录的帐号,密码,缓存时的cookie等不应该缓存,,,跟我们缓存在本地没有关系..



缓存服务器是什么?如下图,

在我们的反向代理架构当中,像nginx本身所提供的内容,本身所提供的缓存能力,它能够被前端很多用户请求的时候所获取,所使用,,,这种缓存叫公共缓存,(public cache)

用户本身自己的浏览器的缓存,叫 private cache,用户自己私有的独有的缓存信息,,一般用户自己的电脑的浏览器缓存是安全的,但是如果是一个公共电脑,也不安全,

能使用缓存的前提是服务器原始文档没有发生改变,

假设静态情况下,客户端第一次请求,是从原始服务器获取资源,不涉及缓存,,,,,,当第二次请求时,怎么知道,缓存在本地浏览器的资源没有失效,,,,,,

        1)缓存过期期限,比如缓存10分钟,服务器返回请求页面的同时,在http的响应首部当中,告诉客户端,此资源的缓存时长

        2)客户端自己设置缓存策略,(服务器未告知缓存时长),,只要是静态内容,客户端浏览器就缓存,,,,客户端下一次再请求时,发送缓存文件的修改时间,与服务器的修改时间对比,如果未改变,服务器发送304响应码,告诉客户端,内容未改变,,,于是浏览器就使用了本地缓存,

HTTP1.1当中引入很多首部,有些专用于客户端,有些专用于服务器端,让双方基于某种功能进行协商,判断缓存对象是否能够进一步使用的机制????

image.png




Expires: 绝对时间计时法

max-age: 相对时间计时法,时长

                同时指定了 Expire:2013-05-21 15:55:31 和 Cache-Control:max-age=600,,此时Expire 会被忽略,被覆盖


许多站点,页面变化速度快,比如index.html, 修改时间 2013-05-21 14:55:59 ,连续一秒之内发起三次请求,第一次请求后进行缓存,第二次请求,验证修改时间一致,所以使用本地缓存了,,,,有些页面动态生成,一秒变三次,

使用时间戳: 修改 14:55:59,0000001

                  第二次修改 14:55:59,0000011

服务器还是认定 14:55:59 这个时间改的,,,,,所以客户端认为服务器端文件没改,但是服务器端已经改了,,,客户到服务器端去验证的时候(是拿颗粒度为秒的时间戳与服务器端颗粒度为秒的时间戳去比较的)

 所以说时间戳过于精糙, 按秒不足以描述页面的变化频率的,有可能让已经失效的缓存继续使用的,,,

此时引入了另外一个首部Etag


HTTP/1.0时,假设没有相关的验证机制,告诉你页面缓存10分钟,10分钟内直接使用了,10分钟内服务器端发生改变,客户端是不知道的,这种判定机制很粗糙



HTTP/1.1,引入条件判断机制,每一次验证资源的时候,是向对方发起一个询问条件的,

        Last-Modified

        If-Modified-Since


HTTP/1.1协议引入的缓存机制,1.0中其实也有,1.0中通常有Pragma来标识能否缓存,用Expires定义缓存何时过期,由此客户端取得资源后,就可以缓存在本地了,


三、HTTP协议与varnish


1、缓存相关的HTTP首部


HTTP协议提供了多个首部用以实现页面缓存及缓存失效的相关功能,这其中最常用的有:

(1)Expires:用于指定某web对象的过期日期/时间,通常为GMT格式;一般不应该将此设定的未来过长的时间,一年的长度对大多场景来说足矣;其常用于为纯静态内容如JavaScripts样式表或图片指定缓存周期;#是个绝对日期,返回客户端的时间是个明确的时间比如 Expires: 2013-05-21 14:59:30,客户端缓存资源后,下一次再请求同样内容的时候,在有效期内,不会向服务器发送请求了,,,,怎么比较有效期的,如果远程服务器时间与本地不一致的话,怎么办?所以绝对时间是有缺陷的,这个Expires是http/1.0中常用的机制,在 1.1中,可以指定时长max-age,,,比如说告知浏览器缓存10分钟,从服务器端发送资源那一刻起做倒计时,只要倒计时结束,就失效了,

(2)Cache-Control:用于定义所有的缓存机制都必须遵循的缓存指示,这些指示是一些特定的指令,包括public、private、no-cache(表示可以存储,但在重新验证其有效性之前不能用于响应客户端请求)、no-store、max-age、s-maxage以及must-revalidate等;Cache-Control中设定的时间会覆盖Expires中指定的时间;#http/1.1当中,首部Cache-Control,,用于定义所有的缓存机制都必须遵循的缓存指示,无论时间到没到期,无论有没有给max-age?????,还需要接受Cache-Control指令的指挥,,,,比如缓存控制指令指出不能进行缓存,则Expires或max-age失效,,,,public可以放在公共缓存,所有人都可以获取,不涉及到私有人个问题,,private只能缓存在用户的私有缓存区域,像nginx或varnish这样的服务器,是公共缓存,不能缓存private,,,no-cache表示不能缓存???,表示可以缓存,但是每一次都得向服务器发起验证的,,比如告知你,可以缓存10分钟,但在10分钟内服务器资源随时会过期,所以每一次请求,都要向服务器端发起验证,,,,,,, no-store,表示不能存储下来,,,,,max-age,,,,s-max-age(主要是控制私有缓存的某些缓存期限的),,,,,must-revalidate(与no-cache有些效果是一样的,必须要进行重新服务器端验证),,,,,max-age指定,Expires也指定,Expires此时会被忽略

(3)Etag:响应首部,用于在响应报文中为某web资源定义版本标识符;#扩展的标识,为每一次生成的页面文件定义一个版本号(通常随机生成),,,,,,,,客户端请求页面发生变化没有,不再基于时间戳了,而是对于Etag,,, 更新频率低于秒级别时,验证服务器端文件是否发生变化

(4)Last-Modified:响应首部,用于回应客户端关于Last-Modified-Since或If-None-Match首部的请求,以通知客户端其请求的web对象最近的修改时间;#是一个响应首部,别人问我上一次修改时间是什么时间的时候, 我们响应的时候告诉它是什么时间,,,用于回应客户端关于Last-Modified-Since或If-None-Match首部的请求

(5)If-Modified-Since:条件式请求首部,如果在此首部指定的时间后其请求的web内容发生了更改,则服务器响应更改后的内容,否则,则响应304(not modified);        #是一个条件请求首部,客户端发起请求的时候,直接向服务器端询问,某一个资源你给我以后,是不是又发生修改了,如果此请求首部对应的资源在后端服务器上发生了改变,服和器端就响应一个改变了的新资源给客户端,,,如果此资源在后端服务器上没有改变,就响应304,告诉客户端没修改,客户端浏览器就直接使用客户端本地缓存了,

(6)If-None-Match:条件式请求首部;web服务器为某web内容定义了Etag首部,客户端请求时能获取并保存这个首部的值(即标签);而后在后续的请求中会通过 If-None-Match 首部附加其认可的标签列表并让服务器端检验其原始内容是否有可以与此列表中的某标签匹配的标签;如果有,则响应304,否则,则返回原始内容;#条件式请求首部,如果不匹配,这是个否定请求,,,,web服务器为某web内容定义了Etag首部,如果Etag不相等,就是  If-None-Match 为真,,,就是要重新从服务器获取资源,否则就是从客户端浏览器本地缓存中获取

(7)Vary:响应首部,原始服务器根据请求来源的不同响应的可能会有所不同的首部,最常用的是Vary: Accept-Encoding,用于通知缓存机制其内容看起来可能不同于用户请求时Accept-Encoding-header首部标识的编码格式;#响应首部,,,,客户端请求时,能支持压缩功能,就压缩发给它,如果客户端浏览器不支持压缩功能,就原样返给它,,,,(nginx压缩返回的能力,gzip,可以将原始页面压缩的),,,,Vary就是用来标识你所能够接受的编码机制是文本还是gzip压缩的,还是deflate压缩的等,,,,,,它是通知缓存机制,我们获取页面的时候,如何得到这个响应的页面的.

(8)Age:缓存服务器可以发送的一个额外的响应首部,用于指定响应的有效期限;浏览器通常根据此首部决定内容的缓存时长;如果响应报文首部还使用了max-age指令,那么缓存的有效时长为“max-age减去Age”的结果??????;


四、Varnish状态引擎(state engine)


VCL用于让管理员定义缓存策略,而定义好的策略将由varnish的management进程分析、转换成C代码、编译成二进制程序并连接至child进程。varnish内部有几个所谓的状态(state),在这些状态上可以附加通过VCL定义的策略以完成相应的缓存处理机制,因此VCL也经常被称作“域专用”语言或状态引擎,“域专用”指的是有些数据仅出现于特定的状态中。

1、VCL状态引擎


在VCL状态引擎中,状态之间具有相关性,但彼此间互相隔离,每个引擎使用return(x)来退出当前状态并指示varnish进入下一个状态。


varnish开始处理一个请求时,首先需要分析HTTP请求本身,比如从首部获取请求方法、验正其是否为一个合法的HTT请求等。当这些基本分析结束后就需要做出第一个决策,即varnish是否从缓存中查找请求的资源。这个决定的实现则需要由VCL来完成,简单来说,要由vcl_recv方法来完成。如果管理员没有自定义vcl_recv函数,varnish将会执行默认的vcl_recv函数。然而,即便管理员自定义了vcl_recv,但如果没有为自定义的vcl_recv函数指定其终止操作(terminating),其仍将执行默认的vcl_recv函数。事实上,varnish官方强烈建议让varnish执行默认的vcl_recv以便处理自定义vcl_recv函数中的可能出现的漏洞。


2、VCL语法


VCL的设计参考了C和Perl语言,因此,对有着C或Perl编程经验者来说,其非常易于理解。其基本语法说明如下:

(1)//、#或/* comment */用于注释

(2)sub $name 定义函数

(3)不支持循环,有内置变量

(4)使用终止语句,没有返回值

(5)域专用

(6)操作符:=(赋值)、==(等值比较)、~(模式匹配)、!(取反)、&&(逻辑与)、||(逻辑或)

VCL的函数不接受参数并且没有返回值,因此,其并非真正意义上的函数,这也限定了VCL内部的数据传递只能隐藏在HTTP首部内部进行。VCL的return语句用于将控制权从VCL状态引擎返回给Varnish,而非默认函数,这就是为什么VCL只有终止语句而没有返回值的原因。同时,对于每个“域”来说,可以定义一个或多个终止语句,以告诉Varnish下一步采取何种操作,如查询缓存或不查询缓存等。


3、VCL的内置函数


VCL提供了几个函数来实现字符串的修改,添加bans,重启VCL状态引擎以及将控制权转回Varnish等。


regsub(str,regex,sub)

regsuball(str,regex,sub):这两个用于基于正则表达式搜索指定的字符串并将其替换为指定的字符串;但regsuball()可以将str中能够被regex匹配到的字符串统统替换为sub,regsub()只替换一次;

ban(expression):

ban_url(regex):Bans所有其URL能够由regex匹配的缓存对象;

purge:从缓存中挑选出某对象以及其相关变种一并删除,这可以通过HTTP协议的PURGE方法完成;

hash_data(str):

return():当某VCL域运行结束时将控制权返回给Varnish,并指示Varnish如何进行后续的动作;其可以返回的指令包括:lookup、pass、pipe、hit_for_pass、fetch、deliver和hash等;但某特定域可能仅能返回某些特定的指令,而非前面列出的全部指令;

return(restart):重新运行整个VCL,即重新从vcl_recv开始进行处理;每一次重启都会增加req.restarts变量中的值,而max_restarts参数则用于限定最大重启次数。


4、vcl_recv


vcl_recv是在Varnish完成对请求报文的解码为基本数据结构后第一个要执行的子例程,它通常有四个主要用途:

(1)修改客户端数据以减少缓存对象差异性;比如删除URL中的www.等字符;

(2)基于客户端数据选用缓存策略;比如仅缓存特定的URL请求、不缓存POST请求等;

(3)为某web应用程序执行URL重写规则;

(4)挑选合适的后端Web服务器;


可以使用下面的终止语句,即通过return()向Varnish返回的指示操作:

pass:绕过缓存,即不从缓存中查询内容或不将内容存储至缓存中;

pipe:不对客户端进行检查或做出任何操作,而是在客户端与后端服务器之间建立专用“管道”,并直接将数据在二者之间进行传送;此时,keep-alive连接中后续传送的数据也都将通过此管道进行直接传送,并不会出现在任何日志中;

lookup:在缓存中查找用户请求的对象,如果缓存中没有其请求的对象,后续操作很可能会将其请求的对象进行缓存;

error:由Varnish自己合成一个响应报文,一般是响应一个错误类信息、重定向类信息或负载均衡器返回的后端web服务器健康状态检查类信息;


vcl_recv也可以通过精巧的策略完成一定意义上的安全功能,以将某些特定的攻击扼杀于摇篮中。同时,它也可以检查出一些拼写类的错误并将其进行修正等。


Varnish默认的vcl_recv专门设计用来实现安全的缓存策略,它主要完成两种功能:

(1)仅处理可以识别的HTTP方法,并且只缓存GET和HEAD方法;

(2)不缓存任何用户特有的数据;


安全起见,一般在自定义的vcl_recv中不要使用return()终止语句,而是再由默认vcl_recv进行处理,并由其做出相应的处理决策。


下面是一个自定义的使用示例:


sub vcl_recv {

if (req.http.User-Agent ~ "iPad" ||

req.http.User-Agent ~ "iPhone" ||

req.http.User-Agent ~ "Android") {

set req.http.X-Device = "mobile";

} else {

set req.http.X-Device = "desktop";

}

}


此例中的VCL创建一个X-Device请求首部,其值可能为mobile或desktop,于是web服务器可以基于此完成不同类型的响应,以提高用户体验。


5、vcl_fetch


如前面所述,相对于vcl_recv是根据客户端的请求作出缓存决策来说,vcl_fetch则是根据服务器端的响应作出缓存决策。在任何VCL状态引擎中返回的pass操作都将由vcl_fetch进行后续处理。vcl_fetch中有许多可用的内置变量,比如最常用的用于定义某对象缓存时长的beresp.ttl变量。通过return()返回给arnish的操作指示有:

(1)deliver:缓存此对象,并将其发送给客户端(经由vcl_deliver);

(2)hit_for_pass:不缓存此对象,但可以导致后续对此对象的请求直接送达到vcl_pass进行处理;

(3)restart:重启整个VCL,并增加重启计数;超出max_restarts限定的最大重启次数后将会返回错误信息;

(4)error code [reason]:返回指定的错误代码给客户端并丢弃此请求;


默认的vcl_fetch放弃了缓存任何使用了Set-Cookie首部的响应。



如下图,

客户端先向反向代理发起请求,假设反向代理有缓存功能的话,客户端第一次请求的时候,向反向代理的缓存服务器发起请求,如果反向代理的缓存服务器自身有缓存结果,由反向代理的缓存服务器自己封装报文,返回给客户端,而且客户端可以自己缓存下来,在客户端浏览器自己这边进行缓存,,,,如果反向代理的缓存服务器没有缓存,缓存服务器自己向原始服务器(上游服务器)发起请求,,,,原始服务器然后返回结果给缓存服务器,并且告诉缓存服务器,你能否缓存,(比如说原始服务器返回给缓存服务器的时候,告诉缓存服务器,给缓存服务器返回一个首部 Cache-Control: no-cache,,,,(或者 Cache-Control: max-age=600),,,,对于公共缓存,资源是可以缓存下来的,,,,,,Cache-Control: max-age=600意思是600秒内有效??这不是客户端缓存吗????马哥说不是,,马哥说既是客户端缓存时间,,又是缓存服务器的缓存时间),,,,,而后,缓存服务器封装响应报文给客户端,客户端也会缓存的,,,,,,,如果同一客户端在某一时刻发起同样请求的话,在发起同样请求之前,它会检查浏览器本地缓存的,,(客户端浏览器的缓存时间与原始服务器响应的缓存时间是否相同??未必,,因为缓存服务器发起客户端浏览器时,可以修改请求首部缓存时间(Cache-Control: max-age)的,我们可以缓存一年,就算让客户端浏览器缓存一年,只要ctrl+f5强制刷新,客户端浏览器一定会到远程缓存(原始服务器??????,应该是缓存服务器吧??????)发起请求的),,,,,,,,,,但是对于缓存服务器那里(从原始服务器到缓存服务器的缓存)不能随便修改缓存时间,,,,,,,,,

image.png


如下图,下次我们的客户端在请求的时候,假如说我们的缓存服务器告诉我们客户端,最多只能缓存一分钟,假如说,在同一分钟之内,我们的客户端发起了请求,客户端发现它本地有缓存,而且在有效期内,资源就在客户端浏览器那边就返回了,,,,,如果超出了一分钟,客户端浏览器也不会立即清除缓存的,,它可以向缓存服务器端发起条件式请求,客户端自己缓存内容假设最近一次修改时间是2013-01-01 00:00:00,问缓存服务器最近一次修改时间,缓存服务器告知也是2013-01-01 00:00:00,这样,缓存服务器就返回给客户端浏览器304(not modified),这样,缓存就照样可以继续使用的,,,,,,而且在缓存没有修改的时候,我们的服务器甚至可以告诉客户端浏览器,资源还可以缓存使用一分钟,又缓存了一分钟,,,,,,所以就算缓存过期了,未必真要缓存服务器返回一个新的资源,,,这就是缓存的使用方式,If-Modified-Since

image.png


如下图,同样的道理,我们的客户端向我们的缓存服务器端发起请求了,,,假设已经到了11分钟了,因为缓存服务器那边只能缓存10分钟,,,所以缓存服务器资源失效了,所以缓存服务器向原始服务器发请求,,缓存服务器没必要向原始服务器请求原始资源,缓存服务器告诉原始服务器,我这边有缓存,而且上一次修改时间是什么时间,问原始服务器,从这个时间往后,内容有没有修改过,If-Modified-Since,从这个时间起,我这里这个资源

你原始服务器有没有发生过修改,,,,,,,发现原始服务器端发现没有发生修改, 原始服务器就会返回304(not modified)给缓存服务器端,,这样子,缓存服务器端的缓存此内容的更新时间,刷新时间也更新了,,,,于是就使用缓存服务器本地的缓存直接响应给客户端浏览器了

image.png

条件式请求和对方响应的时候给的答案当中,能否使用?缓存,就是这样一种关系,,,,它是这么请求的,

假如请求首部由must-revalidate控制了?怎么办?重新验证,,,,,,,,,,,,,,,,,响应也好,请求也好,可以使用Cache-Control的,明确说明,能使用缓存,不能使用缓存,

如下图,ctrl+f5强制刷新,,,强制刷新就是告诉服务器端,你不能使用缓存??????应该是告诉自己,不能使用缓存,


一般来讲,静态资源,客户端请求后会自动缓存的,



把上次使用的某个电脑(192.168.0.61 即  192.168.10.11)的网卡改成桥接吧

image.png

# service network restart

# ifconfig

image.png



http://192.168.0.61/   响应状态码200

image.png



http://192.168.0.61/    F5 刷新下

304 Not Modified 未修改,

request headers: 请求的

        Cache-Controll: max-age=0

        If-Modified-Since:Fri, 30 Apr 2021 02:33:56 GMT    #从这个时间之后,发生了改变没有

        If-None-Match: "60009-1a-5c127718c4a06"    #是不是跟它不匹配

response headers: 响应的

        ETag: "60009-1a-5c127718c4a06" #与If-None-Match匹配的 ,所以响应的是304 Not Modified 未修改

        Last-Modified: Fri, 30 Apr 2021 02:33:56 GMT #上一次修改时间



image.png



http://192.168.0.61/    Ctrl+F5 强制刷新下


200 OK ,#请求原始服务器,响应最原始的内容,正常响应

request headers: 请求的

        Cache-Control: no-cache #不能使用缓存了????可以使用缓存,但每次都得验证

        

response headers: 响应的

        Date: Wed, 12 May 2021 06:21:15 GMT        #这是什么意思,是响应时间 时区是+8吧 06点就是14点        

        ETag: "60009-1a-5c127718c4a06"            #版本号 

        Last-Modified: Fri, 30 Apr 2021 02:33:56 GMT    #上一次修改时间

image.png



http://192.168.0.61/    F5 刷新下

304 Not Modified 未修改,

request headers: 请求的

        Cache-Controll: max-age=0    #最大时间为0

        If-Modified-Since:Fri, 30 Apr 2021 02:33:56 GMT    #从这个时间之后,发生了改变没有

        If-None-Match: "60009-1a-5c127718c4a06"    #是不是跟它不匹配

response headers: 响应的

        ETag: "60009-1a-5c127718c4a06" #与If-None-Match匹配的 ,所以响应的是 304 Not Modified 未修改

        Last-Modified: Fri, 30 Apr 2021 02:33:56 GMT #上一次修改时间

image.png


如下图

浏览器自己的缓存叫私有缓存

缓存服务器的缓存叫公共缓存

cookie 不能公共缓存,cookie 不能缓存(能私有缓存吗????)

如果用户的请求都有cookie的话,缓存能不能命中,,,如果客户端每次请求的时候,都加了些cookie,而且加的cookie中有些内容是变化的,请求的内容是命中不了的,,,,,,,,,cookie的所有变化,,,,,,,,,,如果我们考虑缓存的时候, 本身缓存的键当中本身包含经常变化的内容的话,命中率是非常低的,,,所以很多时候,在缓存资源的时候,要把里面的cookie信息给它去了,把经常变化的信息给它去掉,以提高命中率的...

我们动态生成资源可以缓存吗?用户名密码登录页面信息的内容是不能缓存的,

一般请求方法中为POST或者PUT,是不能缓存的,因为它是写操作,,,一般能缓存的是GET读操作,,,,,就算都是GET操作,包含了自己私有认证信息的也是不能缓存的

尽管我们可以放一个公共缓存,但是并不是能缓存任意数据的,

image.png


明白缓存服务器的基本工作机制,将来我们自己定义一个缓存服务器的时候,就可以手动定义缓存服务器中的缓存效果了

如下图,我们的原始服务器在缓存服务器请求的时候,告诉缓存服务器,你可以缓存10分钟,,,但是缓存服务器觉得浏览器客户端随时可以刷新的,而且我们发现缓存的这个是静态资源,并且我们认为原始服务器上的这个静态资源很少发生改变,(比如上传一个图片,网站logo),,,,,所以说原始服务器让缓存服务器只缓存10分钟,但是缓存服务器告诉客户端浏览器可以缓存半年,,,,由此我们可以自己需要定义缓存策略的,把某些资源根据我们缓存服务器自己的理解,将某些资源从原始服务器的响应方式中剥离出来,缓存服务器自己去定义它的缓存时间,告诉客户端它的私有缓存中的缓存时间,,客户端自己私有缓存中缓存的内容越多,向缓存服务器发起请求的次数就越少,缓存服务器需要响应的内容就越少,意味着带宽占用量就越少,,更重要的是,客户端从自己本地直接返回了,那它的性能会很好,所以用户体验就会好,,,,,,,,缓存本身放得离客户端越近越好,,,,能够放到用户浏览器缓存中的,我们都可以放在用户浏览器的缓存中,,,,,,,,,(手持智能终端上缓存的一些数据,,第一打开速度快,第二节省流量,,,有些软件动不动清理垃圾,,尤其是浏览器缓存,清一下,下次一请求,会重新占用流量去获取的)

image.png


要把缓存本身定义成一个扩展结构的话,缓存可以定义成内容网络的,如下图,

从客户端到原始服务器端之间可以定义n层缓存服务器,这些缓存服务器之间完全还可以分层次,客户端请求的时候,可以直接请求1号缓存服务器,1号缓存服务器如果没有内容,可能不会直接请求原始服务器,1号缓存服务器的父缓存服务器可能是另外两台缓存服务器,,,,,,,,,所以本地缓存服务器中没有,找父缓存服务器,父缓存服务器中没有,父缓存服务器可以去找原始服务器,,,,,,,如下图,从客户端,如果请求的是2号缓存服务器,到原始服务器要经过3个缓存服务器,,,,如果从2号到1号后,1号缓存服务器里面有缓存数据的话,就直接返回了,,,,,这些缓存服务器之间通过某种协议,如果能够共享缓存对象,就算1号里面没有,但是3号里面有,就可以从3号直接获取缓存数据,,,,,由此,客户端请求的时候,直接在中间这个缓存网络上,就可以取得所有内容的,,,,,,,更重要的是这些缓存服务器,我们还可以定义一些策略,,,,无论客户端浏览器有没有请求,缓存服务器会定期的把可以缓存的资源从原始服务器上拉过来一遍,先缓存到缓存服务器上,,,这样客户端一请求,就直接从缓存服务器中返回了,,,,,,,,,中间的n个缓存服务器组成的网络,我们称为cdn( Content Delivery Network 内容分发网络)

image.png


如下图,我们建立了一个内容分发网络,它们可以缓存资源到用户的家门口的, 我们假设四个服务器组成cdn,假设一个是北方联通,一个是北方电信,一个是南方联通,一个是南方电信,,,,,假设在北方联通有个客户端,这个客户端试图访问站点www.magedu.com的时候,它访问对应的站点,通过DNS的A记录解析以后,应该指向的是我们的原始服务器 1.1.1.1,意味着我们的客户端将通过网络直接联系 1.1.1.1 这个服务器了,

image.png


如下图,我们的服务器,为了让用户体验做得更好,而且减轻了服务器的流量压力,使用了CDN网络,CDN缓存服务器上都可以缓存我们的内容了,假如我们把原始服务器的内容都缓存到CDN缓存服务器上去了,,,,我们的客户端请求的时候,一通过DNS解析,通过DNS的A记录解析以后,应该指向的是我们的原始服务器 1.1.1.1,,,我们的目的是让客户端从最近的CDN缓存服务器上找数据,这样速度会快很多.现在我们的解析办法是:CNAME解析指向到某一个缓存服务器,而且是以别名(某一个CDN缓存服务器的别名)的方式定义的,,,,,我们可以解析多个CNAME别名

 www.magedu.com in CNAME cach1.cdn.com

 www.magedu.com in CNAME cach4.cdn.com

如下图有,4个客户端 但我们如何保证每个客户端请求的都是离它最近的那个CDN缓存服务器呢呢?智能DNS,根据客户端的来源判定它属于哪一个网络,我们可以自己划分好区域,北方联通,北方电信,南方联通,南方电信,,,,而只要客户端发现,判定一下结果是哪个CDN服务器,,,,客户端请求www.magedu.com域名的时候,解析的结果不是四个结果都出来,解析的结果只返回一个,,只返回离我的客户端最近的跟我的客户端是在同一个网络中的那个(判断客户端来源,返回客户端所在网络中的那台服务器),,,,,,,,,因此有智能DNS结合这些内容分发网络,结果就是CDN

image.png

如下图,在这些缓存服务器之间,当我们的客户端请求一个缓存服务器的时候,如果缓存服务器上没有内容,,一般它不会直接去找原始服务器,一般去找与它相连的缓存服务器,假设3号.4号缓存服务器是1号的父服务器( parent 上游服务器,当本地缓存服务器没有相关内容的时候,找上游服务器,,,上游服务器再没有的话,就再找上游的上游(原始服务器),,再没有,就返回错误结果了),2号服务器是1号的兄弟服务器(sibling),,,,,,,,,,,,这些缓存服务器之间能够根据所谓的内容缓存协议来实现缓存对象共享,,,,,而且有些高级的智能网络,智能CDN网络还可以实现内容路由的,当客户端请求一个资源以后,这个资源不在本地,它会找一个有这个资源的缓存服务器,一般尽可能不去找原始服务器,,但是肯定不能100%的避免找原始服务器,,,原始服务器上有些内容是动态的,我们是不能缓存的,就算有些内容是静态的,缓存服务器上也不可能把我们的整站全部缓存到缓存服务器上,它最多只能缓存一部分内容,,,,,,,,如果dns缓存做得好,它把我们的热区数据都拉到缓存服务器上去,实现尽可能在缓存服务器本地返回,,,,,如果我们的静态缓存服务器做得足够好,缓存命中率可以做得非常高,

image.png


cdn的收费通常是按流量的,1兆几十块,三四十,五六十,七八十,一百块的都有,很多站点,一个月下来,收入很大,但是分给cdn一大笔钱,对于cdn又爱双恨,老想它命中,但是一命中就是钱,,,很多的视频流服务器,图片服务器,必须要用cdn的,不然的话,流量一大,基本上服务器就垮掉了,,,,暴风影音,pptv,奇艺,都要到cdn的,,,,如果有钱,可以自建cdn,可以在各地机房里面做几台服务器,,,,,少部分公司自建cdn,,大部分公司按流量收费租用cdn,,,,,,,,对于中小型电子商务站点来讲,自建cdn也行,代价也不太大


如何构建智能DNS,是我们创建智能CDN的前提,如果不想自己建智能DNS,也可以,得使用公共的智能DNS服务器,像dnsport,(dnsport 已经被腾讯收购了,dnsport 在防黑,防ddos攻击方面做的相当好,它一台服务器的解析速度每秒钟的响应量可以达到数百万个,而且可以达到秒级更新,更新以后随时都能生效,,,但是达到这种级别是要收费的),,dns port当中很大一部分是免费的,,,,花钱后这些公共智能dns服务器,它不但能给我们提供智能dns解析,还能提供服务器监控的,,,,

如下图,一监控,比如有三台web原始服务器,做了负载均衡,直接通过dns来做负载均衡,是可以的

image.png

如下图,或者干脆用两个keepalived做了一个高可用haproxy(我怎么感觉像是负载均衡?????),而且是双主模型,后台再是 web 服务器?????很显然dns解析就是两个结果了??????它能够监控其中某一个keepalived坏了,自动的删除dns解析的那个条目???????当keepalived又好了,,dns又会增加对应的解析的条目,,,,,,,,,,这叫健康状况检查,有些dns比如dnsport可以做到这个地步的,当然dnsline???也可以,,,,,,dnsline与dnsport差别很远了,dnsline需要的二次开发的能力要求特别高,,,,,,,,,,,,,,,,,,,,,, 这些公共智能DNS给我们至少也提供了免得自己去建dns服务器的难题,,更重要的是DNS本身也得分网(人家是联通的客户,你把dns服务器放在电信了,dns解析速度都会变慢的)

image.png

网站访问中间需要经过几个时间,获取网页的时间,DNS解析请求出去,返回回来,才能再去请求资源的,,,,所以DNS很慢也会影响用户体验的,,,

实在自己不想建dns服务器,可以自己租,租比自建成本要低的,,,,,,只要流量不是特别的大,不像淘宝的流量


bind-dlz:--->MySQL

根据dlz的功能,可以将用户的资源放在MySQL里面,,,,放在mysql里面,速度非常慢,,,如果不做公共dns,只为自己公司去做智能解析,,,我们可以放在文件里面,,,,,,,以后dns一启动,直接加载进内存中解析,速度会很快,,,,所以不到万不得已的时候,别使用bind-dlz,,,,

bind本身的view就能实现智能解析,

    bind-dlz + MySQL 可以实现将用户的实现放在MySQL里面,也可以放在Pgsql.Oracle,db4(这是一种基于hash方式编码的数据库,它只是一个数据库引擎,,,,,因为hash,所以性能高得多,比mysql性能高得多,,,,但是,因为它是键值对,所以存的信息非常简陋,只能一键一值,而且键还不能重复,所以编码的格式比较头疼??????,,,,,好在 bind-dlz 的编码机制不是简单的db4,它也不是一个简单的键值数据库,也能够支持将我们的资源记录放在db当中????????这些可以自己去摸索)



互联网早期的最著名的缓存服务器是squid,诞生时间与httpd差不多,

squid:varnish,相当于httpd:nginx

varnish,尽可能应用时下最新的技术,尽可能利用时下较好的软件设计结构,尽可能利用时下安全体系方面各种经验来进行设计,

squid和httpd一定程度上,宝刀未老,到目前为止,依然存活,自有它的道理,,而且还占据很大的市场份额

早期淘宝前端使用100多个服务器创建的squid缓存集群,据说优化后缓存命中率(当然缓存的是静态内容),达到了97%,,,当然了,要对所有的访问的静态资源做好分区的,做好筛选,知道将哪些内容给它缓存到缓存服务器当中,

varnish在官方说自己设计的主要目的是为web服务器提供反向代理的时候提供缓存功能的,varnish是一个反向代理服务器,又能提供缓存功能,跟nginx很近似,,,,varnish创建连接,维持连接的能力比nginx差得多,,,,,,所以人们一般使用nginx+varnish,,nginx专门负责处理连接,varnish专门负责缓存,,,也有人使用nginx+squid,,,,,,,,,,其实squid既提供反向代理,又提供正向代理,还可以实现透明代理,,,,squid配置复杂,支持强大的acl,支持各种访问控制,支持内容分发协议,,,,squid本身特性非常丰富,功能强大,而且经过良好优化以后,squid的性能比varnish差不了多少的,,,,,,,,但是varnish配置简单,接口简单,监控接口丰富,但是使用学习起来也不简单,


我们仅用作web作代理的缓存的话,用varnish吧,性能确实比squid好,比squid更新的架构,将来扩展能力更强,,,据有人说4台varnish性能超过10台squid的性能,


如下图,varnish体系结构,下图是varnish官方给的一张图,是varnish的架构图,varnish在设计体系上采用的模型与nginx差不多,有一个主进程(管理进程)负责配置文件的分析,启动子进程,然后子进程负责响应,,

varnish有主进程(M进程 m就是main的意思吧),它是管理进程,它功能:1)提供命令行接口 2)提供子进程管理(主进程生成很多子进程) 3)提供初始化  它能够读取配置文件并分析,而且支持热布署的,与nginx的架构,机制类似

命令行接口有三种接口,这个命令行接口是个管理接口,它可以让你去启动子进程,管理子进程,查看子进程的运行状态等等,,,,能够利用这个接口给我们输出信息的,,,,包括   1)纯命令行接口 2) 可以使用Telnet直接连进来进行管理的,但是现在为了加强安全Telnet机制不太常用了,,可以使用varnish的专用客户端工具 varnishadm 来连到这个命令行接口上来实现管理我们进程的启动停止等,甚至还能装载新的配置文件 3) web interface,即支持基于web界面的gui接口,

M进程的主要目的是生成两类子进程,一类是用于提供服务的进程(Child进程,,,,,,,负责响应用户请求,负责将用户请求发送至后端,并且取回内容后,封装并响应给客户端),一类是用于cache缓存管理的进程(Cache进程,,,,,,,缓存清理,缓存有效期限检查等等),

Child进程,接受请求 Accept,,,Accept会将用户的请求分发至某一个工作线程(Worker threads),,,它也是有多个工作线程的,每个工作线程可以响应多个请求,,,,,Backend communication,用于跟后端主机通信的,,,,,Log/stats,它有Log和状态统计的功能,varnish的一个重要特色是它的日志信息不是保存在文件当中的(假如保存在文件当中,有日志,就得有IO),它是直接保存在内存中的,,,,,,,我们启动服务的时候,就明确说明给它多少兆内存保存日志信息,一旦填满了这么多兆怎么办?它就重头一圈圈的轮着使用,,,因此有了这个日志文件,它里面记录了,有多少个客户端请求进来了,有多少个客户端请求命中了,有多少个客户端请求没命中,服务器运行多长时间了等等,,,,,,,,所以我们能够使用一个工具,从日志里面导出信息的话,就可以监控我们服务器的运行状态的,

image.png如下图,比如我们能够通过Log file来执行当前运行状态分析的命令行工具有很多,varnishlog,varnishstat,varnishhist,varnishtop,varnishncsa,,,,,,,,varnishncsa是分析日志的一种工具,和varnishlog一样,只不过varnishncsa专门分析ncsa格式兼容的日志的,

VCL compiler: 

        VCL: varnish control language


varnish配置文件,不是指令=值的方式,而是提供一个编程接口,要自己写程序提供配置文件的,,,,我们要使用VCL这种语言去开发程序,指明我们的缓存怎么工作,哪些内容缓存,哪些内容不缓存,哪些内容让它命中,哪些内容不让它从缓存中取,而是通过后端服务器去取,,,这些都通过VCL来定义,,,定义好之后,为了让VCL开发的程序足够高效,,varnish需要将这个程序语言编译成二进制格式,,,而VCL语言,它的底层是使用C开发的,所以它的使用格式完全兼容C语言,,,,由此你使用VCL语言编写好一个配置文件以后,这个配置文件必须要使用gcc给它编译成二进制格式,,,,,它使用C compiler编译,编译完以后,Child,Cache子进程怎么工作?就取决于编译好的共享对象(Share object)中的定义的????,,,,,,所以它的配置文件是以二进制格式被各子进程读取的,,,,,各子进程在从后端backend server上取下内容后,要缓存到varnish服务器本地,,谁来管理缓存?cache子进程,,,,,,,,,谁来负责存缓存?Storage/hashing来负责存缓存,(为什么要hashing?,,,,,,因为键本身要做成所谓的在hash桶里面,接下来可以实现O(1)的查找的,,,,看到hash应该明白,它是要完成整个键值存储的,值就是缓存内容,键就是自己定义的查找标准,)

以上就是所说的varnish的体系结构,

image.png



varnish的主控进程(M进程)只有一个,worker threads 可以有多个,一般建议是两个,varnish好像总体连接数超出5000个以后,性能会下降的,所以一般我们不会让varnish本身每一个工作线程接受太多太多的连接,,,,它的性能是有上限的,,,,,5000个并发连接量其实是相当大的了,因为前端varnish是不会直接面对客户端的,所以在前端,整个系统可能已经响应了某些请求了,,,,比如客户端请求不在的内容,或不允许访问的内容,对于那些客户端,前端是直接响应,不经过varnish的,,,,,所以每一个客户端的请求,可能由nginx转过来,nginx可能只有一台,后面的varnish可以放多个,所以一个varnish平均到5千个,十台varnish可以负载5万个了,,,,5万个连接同时进来,而且又是缓存服务器,缓存服务器的响应速度通常是非常快的,如果用到十个varnish的时候,你的站点几乎是亿级pv的,甚至是10亿级pv,



VCL工作机制是在varnish的状态引擎上工作的,varnish在自己的内部有n个状态引擎,如下图,(像iptables的netfilter的框架,netfilter的几个接口,就是钩子函数,放几个钩子函数,如果某一个报文不经过钩子,钩子就不起作用了,就检测不到这个报文了,,所以这几个钩子函数就放在了报文必经路口中的某一处,报文必经至少其中的一个钩子,在这个钩子点上做一下检查,就可以实现了对报文进行管理的,)varnish的状态引擎也是大致的意思,,,,,,,,作为一个代理服务器来讲,来自于客户端的请求进来以后,先检查本地缓存,如果本地找不到,请向后端转发了,在这中间会经过很多步骤的,,,,,,,比如刚到本机,连进来的时候,即刚有我们的套接字接入的时候,我们可以让来自于某客户端的不让它访问( 对客户端的请求首部进行分析,我们可以决定让不让其访问),,,我们假如让这个客户端请求接进来了,,我们可以决定让这个客户端请求是否查缓存(如果判断请求的内容不可能在缓存中有的话,就让请求转向到后端服务器去取,),(如果判断请求需要在缓存中取,就让请求转向到查读缓存的机制,,,怎么读?到哪里去读?又有很多状态引擎,)

image.png

如下图(下面几个图),看状态引擎,每一个圆圈,表示是一个状态引擎,每一个菱形表示这是一个检查机制,,,,

image.png如下图,第一个入口处的状态引擎vcl_recv,这表示报文接进来了,有三个地方可以送达,一)认为它不可能有缓存,认为是动态内容,直接到后端去取,交给vcl_pass,二)到vcl_hash,根据键来查找,在缓存中查找,三)vcl_pipe,这个内容我不负责处理,交给管道,由另外的外部程序去处理,,,,,,通常这里只关心一)和二),,,,,先看二)这缓存中可能有,到vcl_hash来查找,hash来决定,根据用户指定的键到缓存中找,is the object in cache,两种可能 甲)命中,命中后可以让你取数据,取出来,进行响应,vcl_deliver,vcl_deliver就是把内容投递给客户端了,,,或者命中了,现在我这里是管理缓存的全命令行,命中了,我就清掉它,我到后端去取数据vcl_pass,,,,,,,,也可以不让你取数据,

image.png如下图,乙)未命中vcl_miss,到后端去取fetch object from backend,,到后端看有没有服务器,如果有,就vcl_fetch,vcl_fetch就是到后端服务器上去取的意思,,,取回来之后的话,,,,,如果是私有数据,就不能缓存 Don't cache,,,如果是公共信息,就可以缓存,,,,不管是私有还是公有数据,都是到达vcl_deliver,响应给客户端的,

image.png

如下图,再看一种情况,用户过来请求了,这是用户的私有内容,不能到缓存中去找,直接到vcl_pass,vcl_pass直接让我们去 fecth object from backend,再到vcl_fetch,它去联系后端服务器的,,vcl_fetch取完了之后,又问我们要不要缓存,如果不能缓存,就到Don't cache,再到vcl_deliver,然后响应给客户端了,,,,如果缓存,就缓存下来即Cache,

image.png


状态引擎(状态位)好像比netfilter的钩子要多一点,

vcl_recv: 接受请求过来

         vcl_pass: 直接取数据

         vcl_hash: 从缓存中数据

                 vcl_hit: 命中了(通常是url作为键?)

                             vcl_deliver:直接从缓存中取数据,返还给客户端了vcl_deliver

                 vcl_miss: 未命中

                             vcl_pass: 未命中后,可以交给vcl_pass,最终还是要交给vcl_fetch的,,,,(为什么要多一个vcl_pass,因为它是一个统一的位置,vcl_recv,vcl_hit,vcl_miss都可以统一到vcl_pass,这样vcl_pass可以做统一的处理了)          

                             vcl_fetch: 未命中,到后端去取数据,

                                     vcl_deliver: 到后端取数据后,无论缓不缓存,都要送到vcl_deliver,



如下图,vcl_recv,如果是lookup时,就找vcl_hash,,,如果是pass时,就找vcl_pass,,,,,所以vcl_recv判断结果以后,(vcl_recv那里一大堆的条件判断,如果是什么客户端,直接拒绝访问,,,如果是什么客户端,访问什么资源,命中了,可以查找缓存,,,什么什么资源,不能查找缓存,),,怎么指挥着下一个数据流,下一个处理机制到底是到vcl_hash,还是到vcl_pass,,是通过返回值,,,下图每一个椭圆,我们称为一个domain(域),,每一个椭圆的状态引擎都有自己可以执行的指令,(就像我们往netfilter的各个链上加规则一样,每个链上所能够接受的规则,机制是不一样的,,,,处理完之后,要经过下个链,也有可能直接返回的),,,我们的varnish,一般状态引擎,通常要走向下一步的,除了vcl_deliver,和vcl_pipe,,,,,,,,,,,,要走向下一步,怎么走?我们每一个vcl_receive????????,它的编程,它的所写的代码,只对当前这一个状态引擎生效,这个代码执行以后,可以理解成一个函数,每一个函数执行完后,有个返回值,,,,,,如果返回lookup,意味着找vcl_hash,如果返回pass,意味着找vcl_pass,,,,到底返回lookup还是hash,自己要做条件判断的,在里面可以写一大堆的if语句,if什么什么,返回lookup,if什么什么,返回pass,,,,至于到底if什么,等下再说,,,,,,,,,,,,,,,,,,


image.png


如下图,varnish如果没有后端服务器,很多内容响应不了,,,,,,vcl_fetch以后,就会缓存或者不缓存,,谁来决定缓存或者不缓存???是vcl_fetch上写的程序,在vcl_fetch上写一大堆代码,比如if内容是图片等静态的,就缓存,缓存时间为2个小时,if请求的是php动态资源,或者是用户认证的资源,里面包含有auth,cookie,我们不缓存,Don't cache,,,,所以都需要自己编程的,相关的程序写得好,策略设定的好,缓存命中率就会很高,,,,,,所以要想学会使用varnish,得学会使用vcl,前提是得先理解下图的执行流程

image.png


varnish官方给了两个图片,上图是简单的一个,还有一个,它把整个所有的可能性,中间所有的返回值,以及所有处理的机制都给我们写了进来,此图暂时我们看不懂,,,

varnish官方的作者,写了一本书,而且这书是开源出来的,官方网站上有链接,马哥说好像叫varnish book,马哥所有的讲课内容都来自于varnish book的,





四、Varnish状态引擎(state engine)


VCL用于让管理员定义缓存策略,而定义好的策略将由varnish的management进程分析、转换成C代码、编译成二进制程序并连接至child进程。varnish内部有几个所谓的状态(state),在这些状态上可以附加通过VCL定义的策略以完成相应的缓存处理机制,因此VCL也经常被称作“域专用”语言或状态引擎,“域专用”指的是有些数据???????仅出现于特定的状态中。

1、VCL状态引擎


在VCL状态引擎中,状态之间具有相关性,但彼此间互相隔离 (每一个引擎里面编的程序和下一个就没有关系了),每个引擎使用return(x)来退出当前状态并指示varnish进入下一个状态。(通过return 来转到下一个状态引擎)


varnish开始处理一个请求时,首先需要分析HTTP请求本身,比如从首部获取请求方法(GET还是PUT、POST)、验证其是否为一个合法的HTTP请求等。当这些基本分析结束后就需要做出第一个决策 (往哪里去,是去缓存中找,还是不去缓存中找),即varnish是否从缓存中查找请求的资源。这个决定的实现则需要由VCL来完成,简单来说,要由vcl_recv方法(vcl_recv这个状态 中所编写的程序)来完成。如果管理员没有自定义vcl_recv函数,varnish将会执行默认的vcl_recv函数。然而,即便管理员自定义了vcl_recv,但如果没有为自定义的vcl_recv函数指定其终止操作(terminating)(没有执行到return????),其仍将执行默认的vcl_recv函数 (所以我们不能禁用默认的,否则你得有return来指定下一个出口)。事实上,varnish官方强烈建议让varnish执行默认的vcl_recv以便处理自定义vcl_recv函数中的可能出现的漏洞 (所以最好别定义return,最好最后都让它读default的vcl_recv??????由default来作出决策)。


2、VCL语法


VCL的设计参考了C和Perl语言,因此,对有着C或Perl编程经验者来说,其非常易于理解。其基本语法说明如下:

(1)//、#或/* comment */用于注释

(2)sub $name     ????????#定义函数  subroutine 子例程

(3)不支持循环,有内置变量        #有许多内置变量,内置变量的应用位置比较独特,有的变量只能用在前半段,有的变量只能用在后半段,有的变量是处理请求报文首部的,有的变量是处理响应报文首部的,,,,,很显然在请求上处理响应报文没有任何意义,,,,内置变量跟nginx一样, 比如 $remote_addr,$server_addr,

(4)使用终止语句,没有返回值      #?????啥意思  

(5)域专用            #每一个状态引擎都使用花括号来括起来定义它自有的程序

(6)操作符:=(赋值)、==(等值比较)、~(模式匹配)、!(取反)、&&(逻辑与)、||(逻辑或)

VCL的函数不接受参数并且没有返回值,因此,其并非真正意义上的函数 (所以叫做例程 subroutine,不叫function),这也限定了VCL内部的数据传递只能隐藏在HTTP首部内部进行。VCL的return语句用于将控制权从VCL状态引擎返回给Varnish(return 决定了下一步到底往哪里走),而非默认函数,这就是为什么VCL只有终止语句而没有返回值的原因。同时,对于每个“域”来说,可以定义一个或多个终止语句,以告诉Varnish下一步采取何种操作,如查询缓存或不查询缓存等。


3、VCL的内置函数


VCL提供了几个函数来实现字符串的修改,添加bans,重启VCL状态引擎?????以及将控制权转回Varnish等。


regsub(str,regex,sub)    # regular expression substitute 基于字符串查找,基于正则表达式作模式匹配以后实现字符串替换的,str是原字符串,regex是正则表达式,sub是把正则表达式替换成的内容,,,,,相当于nginx中的rewrite(url模式匹配中的内容换成指定的内容)

regsuball(str,regex,sub):这两个用于基于正则表达式搜索指定的字符串并将其替换为指定的字符串;但regsuball()可以将str中能够被regex匹配到的字符串统统替换为sub,regsub()只替换一次;    #出现多次,通通替换,,相当于正则表达式中的加了g修饰符的regsub,,,,用到rewrite功能就要用到regsub,regsuball

ban(expression):          #ban禁止的意思,给我们的某个缓存对象设置栅栏的,放一个栅栏,别人进不来,就不能使用了, expression表达式,是根据表达式来判断, 所有能够被表达式判定符合条件的那些缓存对象都给它ban起来,放到ban列表中,叫做禁止访问的列表中去,,,,,,,首先放到一个ban列表中去,然后再在ban列表中将它禁止用户使用???,     #ban,ban_url,purge是实现缓存清理的,假始原始服务器告诉我们缓存服务器对于一个资源一个对象,只能缓存10分钟,但是我们手动强行指定缓存2个小时,但是2个小时没到,它过期了,而且缓存中的内容还在,原始服务器发生了修改,我们想手动清掉这个资源,,,这种情况下,我们必须得使用一种特殊的函数,从varnish的命令行管理接口进来,向我们的管理接口发起请求,管理接口可以接受一个特殊的http方法?????,叫做purge(修剪),指定purge以后,修剪哪一部分能够符合条件的cache,,,,如果能够清空缓存,那么缓存命中率会大大降低的,或者我们清缓存,只清一个对象,,,,,purge方法只能允许有权限的人(经过认证的用户)使用

ban_url(regex):Bans所有其URL能够由regex匹配的缓存对象;

purge:从缓存中挑选出某对象以及其相关变种(只要符合条件的)一并删除,这可以通过HTTP协议的PURGE方法完成;    #可以使用curl传递一个方法过来?

hash_data(str):        #取得一个字符串str的hash码,如果字符串确好是url,是查找键,hash以后,我们可以手动判断我们的键是不是我们的缓存中有,,,,,,,

return():当某VCL域运行结束时将控制权返回给Varnish,并指示Varnish如何进行后续的动作;其可以返回的指令包括:lookup、pass、pipe、hit_for_pass、fetch、deliver和hash等;但某特定域可能仅能返回某些特定的指令,而非前面列出的全部指令;        #不同的域(状态引擎),它的可用返回值也是不一样的,比如vcl_fetch,只能返回deliver或pass值,如下图,下图其实是不全面的,比如vcl_pass上还可以做一个regsub重写url的,重写后,再转到vcl_recv上跑一遍,此时有可能会命中的,,,,,,,, vcl_recv 向下一步,可能有 lookup ,可能有 pass ,还有可能有restart(重启,重头再来一次),,,restart不对,再来一次,与nginx中一样,不小心写了一个错误的rewrite,有可能死循环的,,,,如果是死循环的话,restart要判定,比如超过5次的话,就退出了,直接向客户端返回一个error,,,,,,,所以我们必须还要有一个机制,vcl_deliver,,,,,能够在用户请求压根不存在内容的时候,或者restart出现错误的时候,要vcl_deliver一个错误页面给客户端,,,,,,,因此它也要能够在varnish自己服务器本身生成错误响应页的(跟nginx一样,404页面,502页面),生成一大堆的错误页面,

image.png


return(restart):重新运行整个VCL,即重新从vcl_recv开始进行处理;每一次重启都会增加req.restarts变量中的值,而max_restarts参数则用于限定最大重启次数。


4、vcl_recv


vcl_recv是在Varnish完成对请求报文的解码为基本数据结构后第一个要执行的子例程,它通常有四个主要用途:

(1)修改客户端数据以减少缓存对象差异性;比如删除URL中的www.等字符;

(2)基于客户端数据选用缓存策略;比如仅缓存特定的URL请求、不缓存POST请求等;

(3)为某web应用程序执行URL重写规则;

(4)挑选合适的后端Web服务器;


可以使用下面的终止语句,即通过return()向Varnish返回的指示操作:

pass:绕过缓存,即不从缓存中查询内容或不将内容存储至缓存中;

pipe:不对客户端进行检查或做出任何操作,而是在客户端与后端服务器之间建立专用“管道”,并直接将数据在二者之间进行传送;此时,keep-alive连接中后续传送的数据也都将通过此管道进行直接传送,并不会出现在任何日志中;

lookup:在缓存中查找用户请求的对象,如果缓存中没有其请求的对象,后续操作很可能会将其请求的对象进行缓存;

error:由Varnish自己合成一个响应报文,一般是响应一个错误类信息、重定向类信息或负载均衡器返回的后端web服务器健康状态检查类信息;


vcl_recv也可以通过精巧的策略完成一定意义上的安全功能,以将某些特定的攻击扼杀于摇篮中。同时,它也可以检查出一些拼写类的错误并将其进行修正等。


Varnish默认的vcl_recv专门设计用来实现安全的缓存策略,它主要完成两种功能:

(1)仅处理可以识别的HTTP方法,并且只缓存GET和HEAD方法;

(2)不缓存任何用户特有的数据;


安全起见,一般在自定义的vcl_recv中不要使用return()终止语句,而是再由默认vcl_recv进行处理,并由其做出相应的处理决策。


下面是一个自定义的使用示例:


sub vcl_recv {

if (req.http.User-Agent ~ "iPad" ||

req.http.User-Agent ~ "iPhone" ||

req.http.User-Agent ~ "Android") {

set req.http.X-Device = "mobile";

} else {

set req.http.X-Device = "desktop";

}

}


此例中的VCL创建一个X-Device请求首部,其值可能为mobile或desktop,于是web服务器可以基于此完成不同类型的响应,以提高用户体验。


5、vcl_fetch


如前面所述,相对于vcl_recv是根据客户端的请求作出缓存决策来说,vcl_fetch则是根据服务器端的响应作出缓存决策。在任何VCL状态引擎中返回的pass操作都将由vcl_fetch进行后续处理。vcl_fetch中有许多可用的内置变量,比如最常用的用于定义某对象缓存时长的beresp.ttl变量。通过return()返回给arnish的操作指示有:

(1)deliver:缓存此对象,并将其发送给客户端(经由vcl_deliver);

(2)hit_for_pass:不缓存此对象,但可以导致后续对此对象的请求直接送达到vcl_pass进行处理;

(3)restart:重启整个VCL,并增加重启计数;超出max_restarts限定的最大重启次数后将会返回错误信息;

(4)error code [reason]:返回指定的错误代码给客户端并丢弃此请求;


默认的vcl_fetch放弃了缓存任何使用了Set-Cookie首部的响应。





普通分类: