一、关于Varnish
1、varnish系统架构
varnish主要运行两个进程:Management进程和Child进程(也叫Cache进程)。
Management进程主要实现应用新的配置、编译VCL、监控varnish、初始化varnish以及提供一个命令行接口等。Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。
Child进程包含多种类型的线程,常见的如:
Acceptor线程:接收新的连接请求并响应;
Worker线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;
Expiry线程:从缓存中清理过期内容;
Varnish依赖“工作区(workspace)”以降低线程在申请或修改内存时出现竞争的可能性。在varnish内部有多种不同的工作区,其中最关键的当属用于管理会话数据的session工作区。
2、varnish日志
为了与系统的其它部分进行交互,Child进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个worker线程都使用了日志数据缓存。
共享内存日志大小一般为90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。varnish提供了多个不同的工具如varnishlog、varnishncsa或varnishstat等来分析共享内存日志中的信息并能够以指定的方式进行显示。
3、VCL
Varnish Configuration Language (VCL)是varnish配置缓存策略的工具,它是一种基于“域”(domain specific)的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用set自定义变量、支持if判断语句,也有内置的函数和变量等。使用VCL编写的缓存策略通常保存至.vcl文件中,其需要编译成二进制的格式后才能由varnish调用。事实上,整个缓存策略就是由几个特定的子例程如vcl_recv、vcl_fetch等组成,它们分别在不同的位置(或时间)执行,如果没有事先为某个位置自定义子例程,varnish将会执行默认的定义。
VCL策略在启用前,会由management进程将其转换为C代码,而后再由gcc编译器将C代码编译成二进制程序。编译完成后,management负责将其连接至varnish实例,即child进程。正是由于编译工作在child进程之外完成,它避免了装载错误格式VCL的风险。因此,varnish修改配置的开销非常小,其可以同时保有几份尚在引用的旧版本配置,也能够让新的配置即刻生效。编译后的旧版本配置通常在varnish重启时才会被丢弃,如果需要手动清理,则可以使用varnishadm的vcl.discard命令完成。
4、varnish的后端存储
varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。后端存储的类型包括:
(1)file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);
(2)malloc:使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象;
(3)persistent(experimental):与file的功能相同,但可以持久存储数据(即重启varnish数据时不会被清除);仍处于测试期;
varnish无法追踪某缓存对象是否存入了缓存文件,从而也就无从得知磁盘上的缓存文件是否可用,因此,file存储方法在varnish停止或重启时会清除数据。而persistent方法的出现对此有了一个弥补,但persistent仍处于测试阶段,例如目前尚无法有效处理要缓存对象总体大小超出缓存空间的情况,所以,其仅适用于有着巨大缓存空间的场景。
选择使用合适的存储方式有助于提升系统性,从经验的角度来看,建议在内存空间足以存储所有的缓存对象时使用malloc的方法,反之,file存储将有着更好的性能的表现。然而,需要注意的是,varnishd实际上使用的空间比使用-s选项指定的缓存空间更大,一般说来,其需要为每个缓存对象多使用差不多1K左右的存储空间,这意味着,对于100万个缓存对象的场景来说,其使用的缓存空间将超出指定大小1G左右。另外,为了保存数据结构等,varnish自身也会占去不小的内存空间。
二、HTTP协议与varnish
1、缓存相关的HTTP首部
HTTP协议提供了多个首部用以实现页面缓存及缓存失效的相关功能,这其中最常用的有:
(1)Expires:用于指定某web对象的过期日期/时间,通常为GMT格式;一般不应该将此设定的未来过长的时间,一年的长度对大多场景来说足矣;其常用于为纯静态内容如JavaScripts样式表或图片指定缓存周期;
(2)Cache-Control:用于定义所有的缓存机制都必须遵循的缓存指示,这些指示是一些特定的指令,包括public、private、no-cache(表示可以存储,但在重新验正其有效性之前不能用于响应客户端请求)、no-store、max-age、s-maxage以及must-revalidate等;Cache-Control中设定的时间会覆盖Expires中指定的时间;
(3)Etag:响应首部,用于在响应报文中为某web资源定义版本标识符;
(4)Last-Mofified:响应首部,用于回应客户端关于Last-Modified-Since或If-None-Match首部的请求,以通知客户端其请求的web对象最近的修改时间;
(5)If-Modified-Since:条件式请求首部,如果在此首部指定的时间后其请求的web内容发生了更改,则服务器响应更改后的内容,否则,则响应304(not modified);
(6)If-None-Match:条件式请求首部;web服务器为某web内容定义了Etag首部,客户端请求时能获取并保存这个首部的值(即标签);而后在后续的请求中会通过If-None-Match首部附加其认可的标签列表并让服务器端检验其原始内容是否有可以与此列表中的某标签匹配的标签;如果有,则响应304,否则,则返回原始内容;
(7)Vary:响应首部,原始服务器根据请求来源的不同响应的可能会有所不同的首部,最常用的是Vary: Accept-Encoding,用于通知缓存机制其内容看起来可能不同于用户请求时Accept-Encoding-header首部标识的编码格式;
(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进行处理,并由其做出相应的处理决策。
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]:返回指定的错误代码给客户端并丢弃此请求;
四、下面是varnish工作流程图
五、实验环境:
192.168.30.116 OS:Centos 6.4 x86_64 varnish.luojianlong.com
192.168.30.117 OS:Centos 6.4 x86_64 apache1.luojianlong.com
192.168.30.119 OS:Centos 6.4 x86_64 apache2.luojianlong.com
varnish version:varnish-3.0.5
安装varnish
[root@varnish ~]# wget http://repo.varnish-cache.org/redhat/varnish-3.0/el6/noarch/varnish-release/varnish-release-3.0-1.el6.noarch.rpm [root@varnish ~]# rpm -ivh varnish-release-3.0-1.el6.noarch.rpm [root@varnish ~]# yum -y install varnish --enablerepo=varnish-3.0
调整varnish监听端口为80,存储类型为malloc
[root@varnish ~]# cp /etc/sysconfig/varnish /etc/sysconfig/varnish.bak [root@varnish ~]# vi /etc/sysconfig/varnish # 修改和添加如下几项 VARNISH_LISTEN_PORT=6081 改为 VARNISH_LISTEN_PORT=80 添加 VARNISH_MEMORY_SIZE=64M VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}"改为 VARNISH_STORAGE="malloc,${VARNISH_MEMORY_SIZE}" [root@varnish ~]# service varnish start Starting Varnish Cache: [ OK ] [root@varnish ~]# ss -anplt | grep :80 LISTEN 0 128 :::80 :::* users:(("varnishd",19452,8)) LISTEN 0 128 *:80 *:* users:(("varnishd",19452,7)) [root@varnish ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 200 ----------------------------- Varnish Cache CLI 1.0 ----------------------------- Linux,2.6.32-431.3.1.el6.x86_64,x86_64,-smalloc,-smalloc,-hcritbit varnish-3.0.5 revision 1a89b1f Type 'help' for command list. Type 'quit' to close CLI session. varnish> # 启动正常
在俩台后端服务器上安装httpd,并建立测试文件
[root@apache1 ~]# yum -y install httpd [root@apache1 ~]# service httpd start [root@apache1 ~]# ss -anptl | grep :80 LISTEN 0 128 :::80 :::* users:(("httpd",18511,5),("httpd",18513,5),("httpd",18514,5),("httpd",18515,5),("httpd",18516,5),("httpd",18517,5),("httpd",18518,5),("httpd",18519,5),("httpd",18520,5)) [root@apache1 ~]# echo "apache1.luojianlong.com" > /var/www/html/index.html [root@apache1 ~]# curl http://192.168.30.117/index.html apache1.luojianlong.com #同样在另外一台服务器上也执行上述操作,并建立测试页面
自定义vcl配置文件
[root@varnish ~]# vi /etc/varnish/test.vcl backend websrv1 { .host = "192.168.30.117"; .port = "80"; } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } [root@varnish ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 200 ----------------------------- Varnish Cache CLI 1.0 ----------------------------- Linux,2.6.32-431.3.1.el6.x86_64,x86_64,-smalloc,-smalloc,-hcritbit varnish-3.0.5 revision 1a89b1f Type 'help' for command list. Type 'quit' to close CLI session. varnish> vcl.load test1 test.vcl 200 VCL compiled. varnish> vcl.use test1 200 varnish> vcl.list 200 available 0 boot active 2 test1 varnish>
使用curl命令,测试缓存是否生效
[root@apache2 ~]# curl -I http://192.168.30.116/index.html HTTP/1.1 200 OK Server: Apache/2.2.15 (CentOS) Last-Modified: Tue, 08 Apr 2014 07:34:38 GMT ETag: "1fef6-18-4f683043844cd" Content-Type: text/html; charset=UTF-8 Content-Length: 24 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 08:46:20 GMT X-Varnish: 1291353379 1291353378 Age: 1 Via: 1.1 varnish Connection: keep-alive X-Cache: HIT [root@apache2 ~]# curl -I http://192.168.30.116/index.html HTTP/1.1 200 OK Server: Apache/2.2.15 (CentOS) Last-Modified: Tue, 08 Apr 2014 07:34:38 GMT ETag: "1fef6-18-4f683043844cd" Content-Type: text/html; charset=UTF-8 Content-Length: 24 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 08:46:21 GMT X-Varnish: 1291353380 1291353378 Age: 2 Via: 1.1 varnish Connection: keep-alive X-Cache: HIT
发现缓存生效
如果想在客户端知道是在哪台服务器命中的缓存,则修改如下配置
[root@varnish ~]# vi /etc/varnish/test.vcl backend websrv1 { .host = "192.168.30.117"; .port = "80"; } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> vcl.load test2 test.vcl 200 VCL compiled. varnish> vcl.use test2 200 varnish> vcl.list 200 available 0 boot available 2 test1 active 0 test2 [root@apache2 ~]# curl -I http://192.168.30.116/index.html HTTP/1.1 200 OK Server: Apache/2.2.15 (CentOS) Last-Modified: Tue, 08 Apr 2014 07:34:38 GMT ETag: "1fef6-18-4f683043844cd" Content-Type: text/html; charset=UTF-8 Content-Length: 24 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 08:51:23 GMT X-Varnish: 1291353381 Age: 0 Via: 1.1 varnish Connection: keep-alive X-Cache: MISS [root@apache2 ~]# curl -I http://192.168.30.116/index.html HTTP/1.1 200 OK Server: Apache/2.2.15 (CentOS) Last-Modified: Tue, 08 Apr 2014 07:34:38 GMT ETag: "1fef6-18-4f683043844cd" Content-Type: text/html; charset=UTF-8 Content-Length: 24 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 08:51:24 GMT X-Varnish: 1291353382 1291353381 Age: 1 Via: 1.1 varnish Connection: keep-alive X-Cache: HIT from 192.168.30.116
发现已经显现缓存命中的地址
如果想指定某个资源不使用缓存,而直接发往后端服务器,则修改如下配置
[root@varnish ~]# vi /etc/varnish/test.vcl backend websrv1 { .host = "192.168.30.117"; .port = "80"; } sub vcl_recv { if (req.url ~ "index.html$") { return (pass); } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> vcl.load test3 test.vcl 200 VCL compiled. varnish> vcl.use test3 200 varnish> vcl.list 200 available 0 boot available 0 test1 available 2 test2 active 0 test3 [root@apache2 ~]# curl -I http://192.168.30.116/index.html HTTP/1.1 200 OK Server: Apache/2.2.15 (CentOS) Last-Modified: Tue, 08 Apr 2014 07:34:38 GMT ETag: "1fef6-18-4f683043844cd" Accept-Ranges: bytes Content-Length: 24 Content-Type: text/html; charset=UTF-8 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 08:56:35 GMT X-Varnish: 1291353385 Age: 0 Via: 1.1 varnish Connection: keep-alive X-Cache: MISS [root@apache2 ~]# curl -I http://192.168.30.116/index.html HTTP/1.1 200 OK Server: Apache/2.2.15 (CentOS) Last-Modified: Tue, 08 Apr 2014 07:34:38 GMT ETag: "1fef6-18-4f683043844cd" Accept-Ranges: bytes Content-Length: 24 Content-Type: text/html; charset=UTF-8 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 08:56:37 GMT X-Varnish: 1291353386 Age: 0 Via: 1.1 varnish Connection: keep-alive X-Cache: MISS
测试发现,无论怎么访问,index.html始终无法命中缓存
强制缓存未命中:
在vcl_recv中使用return(pass)能够强制到上游服务器取得请求的内容,但这也会导致无法将其缓存。使用purge会移除旧的缓存对象,但如果上游服务器宕机而无法取得新版本的内容时,此内容将无法再响应给客户端。使用req.has_always_miss=ture,可以让Varnish在缓存中搜寻相应的内容但却总是回应“未命中”,于是vcl_miss将后续地负责启动vcl_fetch从上游服务器取得新内容,并以新内容缓存覆盖旧内容。此时,如果上游服务器宕机或未响应,旧的内容将保持原状,并能够继续服务于那些未使用req.has_always_miss=true的客户端,直到其过期失效或由其它方法移除。
如果想在后端主机日志中记录用户的IP信息,则可以修改如下配置
[root@varnish ~]# vi /etc/varnish/test.vcl backend websrv1 { .host = "192.168.30.117"; .port = "80"; } sub vcl_recv { set req.http.X-Forward-For = client.ip; if (req.url ~ "index.html$") { return (pass); } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> vcl.load test4 test.vcl 200 VCL compiled. varnish> vcl.use test4 200 varnish> vcl.list 200 available 0 boot available 0 test1 available 0 test2 available 2 test3 active 0 test4 # 登录后端服务器,修改日志格式 [root@apache1 ~]# vi /etc/httpd/conf/httpd.conf # 修改LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 为 LogFormat "%{X-Forward-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined [root@apache1 ~]# service httpd restart [root@apache2 ~]# curl -I http://192.168.30.116/index.html HTTP/1.1 200 OK Server: Apache/2.2.15 (CentOS) Last-Modified: Tue, 08 Apr 2014 07:34:38 GMT ETag: "1fef6-18-4f683043844cd" Accept-Ranges: bytes Content-Length: 24 Content-Type: text/html; charset=UTF-8 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 09:08:00 GMT X-Varnish: 1291353391 Age: 0 Via: 1.1 varnish Connection: keep-alive X-Cache: MISS [root@apache1 ~]# tail -f /etc/httpd/logs/access_log 192.168.30.116 - - [08/Apr/2014:16:51:23 +0800] "GET /index.html HTTP/1.1" 200 24 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" 192.168.30.116 - - [08/Apr/2014:16:56:33 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" 192.168.30.116 - - [08/Apr/2014:16:56:34 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" 192.168.30.116 - - [08/Apr/2014:16:56:35 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" 192.168.30.116 - - [08/Apr/2014:16:56:37 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" - - - [08/Apr/2014:17:05:32 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" - - - [08/Apr/2014:17:06:01 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" - - - [08/Apr/2014:17:06:02 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" - - - [08/Apr/2014:17:06:04 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" 192.168.30.119 - - [08/Apr/2014:17:08:00 +0800] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh
测试发现后端服务器日志已经记录了客户端的IP地址
如果想自定义缓存的时间,则可以修改如下配置
[root@varnish ~]# vi /etc/varnish/test.vcl backend websrv1 { .host = "192.168.30.117"; .port = "80"; } sub vcl_recv { set req.http.X-Forward-For = client.ip; if (req.url ~ "index.html$") { return (pass); } } sub vcl_fetch { if (req.request == "GET" && req.url ~ "\.(html|jpg|jpeg)$") { set beresp.ttl = 3600s; } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); }
如果想实现根据不同的请求内容发送不同的后端服务器,则修改如下配置
[root@varnish ~]# vi /etc/varnish/test.vcl backend websrv1 { .host = "192.168.30.117"; .port = "80"; } backend websrv2 { .host = "192.168.30.119"; .port = "80"; } sub vcl_recv { set req.http.X-Forward-For = client.ip; if (req.url ~ "index.html$") { return (pass); } if (req.url ~ "\.php$") { set req.backend = websrv2; } else { set req.backend = websrv1; } } sub vcl_fetch { if (req.request == "GET" && req.url ~ "\.(html|jpg|jpeg)$") { set beresp.ttl = 3600s; } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> vcl.load test6 test.vcl 200 VCL compiled. varnish> vcl.use test6 200 varnish> vcl.list 200 available 0 boot available 0 test1 available 0 test2 available 1 test3 available 1 test4 available 0 test5 active 0 test6 # apache2上面安装php,并建立测试页面 [root@apache2 ~]# yum -y install php [root@apache2 ~]# vi /var/www/html/index.php <?php echo "this apache2 php server"; ?> [root@apache2 ~]# service httpd restart [root@apache2 ~]# curl http://192.168.30.116/index.php this apache2 php server [root@apache2 ~]# curl http://192.168.30.116/index.html apache1.luojianlong.com
发现已经分发到不同的后端服务器
如果想配置varnish服务器对后端服务器进行状态检测,则配置
[root@varnish ~]# vi /etc/varnish/test.vcl probe healthchk { .url = "/"; .interval = 5s; .timeout = 2s; } backend websrv1 { .host = "192.168.30.117"; .port = "80"; .probe = healthchk; } backend websrv2 { .host = "192.168.30.119"; .port = "80"; .probe = healthchk; } sub vcl_recv { set req.http.X-Forward-For = client.ip; if (req.url ~ "index.html$") { return (pass); } if (req.url ~ "\.php$") { set req.backend = websrv2; } else { set req.backend = websrv1; } } sub vcl_fetch { if (req.request == "GET" && req.url ~ "\.(html|jpg|jpeg)$") { set beresp.ttl = 3600s; } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> vcl.load test8 test.vcl 200 VCL compiled. varnish> vcl.use test8 200 varnish> vcl.list 200 available 0 boot available 0 test1 available 0 test2 available 0 test3 available 0 test4 available 0 test5 available 0 test6 available 2 test7 active 0 test8
测试,将apache2服务器的httpd停止
[root@apache2 ~]# service httpd stop Stopping httpd: [ OK ]
打开浏览器访问
发现状态检测已经生效
Varnish可以检测后端主机的健康状态,在判定后端主机失效时能自动将其从可用后端主机列表中移除,而一旦其重新变得可用还可以自动将其设定为可用。为了避免误判,Varnish在探测后端主机的健康状态发生转变时(比如某次探测时某后端主机突然成为不可用状态),通常需要连续执行几次探测均为新状态才将其标记为转换后的状态。
每个后端服务器当前探测的健康状态探测方法通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的Backend_health查看或varnishadm的debug.health查看。
.probe中的探测指令常用的有:
(1) .url:探测后端主机健康状态时请求的URL,默认为“/”;
(2) .request: 探测后端主机健康状态时所请求内容的详细格式,定义后,它会替换.url指定的探测方式;比如:
.request =
"GET /.healthtest.html HTTP/1.1"
"Host: www.baidu.com"
"Connection: close";
(3) .window:设定在判定后端主机健康状态时基于最近多少次的探测进行,默认是8;
(4) .threshold:在.window中指定的次数中,至少有多少次是成功的才判定后端主机正健康运行;默认是3;
(5) .initial:Varnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold;
(6) .expected_response:期望后端主机响应的状态码,默认为200;
(7) .interval:探测请求的发送周期,默认为5秒;
(8) .timeout:每次探测请求的过期时长,默认为2秒;
如果Varnish在某时刻没有任何可用的后端主机,它将尝试使用缓存对象的“宽容副本”(graced copy),当然,此时VCL中的各种规则依然有效。因此,更好的办法是在VCL规则中判断req.backend.healthy变量显示某后端主机不可用时,为此后端主机增大req.grace变量的值以设定适用的宽容期限长度。
如果varnish缓存后端服务器有多台,对多个后端服务器做负载均衡,则要配置
# 调度算法使用hash [root@varnish ~]# vi /etc/varnish/test.vcl probe healthchk { .url = "/"; .interval = 5s; .timeout = 2s; } backend websrv1 { .host = "192.168.30.117"; .port = "80"; .probe = healthchk; } backend websrv2 { .host = "192.168.30.119"; .port = "80"; .probe = healthchk; } director webservers hash { { .backend = websrv1; .weight = 2; } { .backend = websrv2; .weight = 1; } } sub vcl_recv { set req.http.X-Forward-For = client.ip; if (req.url ~ "\.html|js|css|jpg|gif|png|jpeg$") { return (pass); } # if (req.url ~ "\.php$") { # set req.backend = websrv2; # } else { # set req.backend = websrv1; # } set req.backend = webservers; } sub vcl_fetch { if (req.request == "GET" && req.url ~ "\.(html|jpg|jpeg)$") { set beresp.ttl = 3600s; } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> vcl.load test9 test.vcl 200 VCL compiled. varnish> vcl.use test9 200
测试发现同一个客户端被定向到同一台后端服务器
# 使用random调度算法 [root@varnish ~]# vim /etc/varnish/test.vcl probe healthchk { .url = "/"; .interval = 5s; .timeout = 2s; } backend websrv1 { .host = "192.168.30.117"; .port = "80"; .probe = healthchk; } backend websrv2 { .host = "192.168.30.119"; .port = "80"; .probe = healthchk; } director webservers random { .retries = 2; { .backend = websrv1; .weight = 2; } { .backend = websrv2; .weight = 1; } } sub vcl_recv { set req.http.X-Forward-For = client.ip; if (req.url ~ "\.html|js|css|jpg|gif|png|jpeg$") { return (pass); } # if (req.url ~ "\.php$") { # set req.backend = websrv2; # } else { # set req.backend = websrv1; # } set req.backend = webservers; } sub vcl_fetch { if (req.request == "GET" && req.url ~ "\.(html|jpg|jpeg)$") { set beresp.ttl = 3600s; } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> vcl.load test10 test.vcl 200 VCL compiled. varnish> vcl.use test10 200
测试发现,请求被随机发送到后端服务器
Varnish中可以使用director指令将一个或多个近似的后端主机定义为一个逻辑组,并可以指定的调度方式(也叫挑选方法)来轮流将请求发送至这些主机上。不同的director可以使用同一个后端主机,而某director也可以使用“匿名”后端主机(在director中直接进行定义)。每个director都必须有其专用名,且在定义后必须在VCL中进行调用,VCL中任何可以指定后端主机的位置均可以按需将其替换为调用某已定义的director。
Varnish的director支持的挑选方法中比较简单的有round-robin和random两种。其中,round-robin类型没有任何参数,只需要为其指定各后端主机即可,挑选方式为“轮叫”,并在某后端主机故障时不再将其视作挑选对象;random方法随机从可用后端主机中进行挑选,每一个后端主机都需要一个.weight参数以指定其权重,同时还可以director级别使用.retires参数来设定查找一个健康后端主机时的尝试次数。
Varnish 2.1.0后,random挑选方法又多了两种变化形式client和hash。client类型的director使用client.identity作为挑选因子,这意味着client.identity相同的请求都将被发送至同一个后端主机。client.identity默认为cliet.ip,但也可以在VCL中将其修改为所需要的标识符。类似地,hash类型的director使用hash数据作为挑选因子,这意味着对同一个URL的请求将被发往同一个后端主机,其常用于多级缓存的场景中。然而,无论是client还hash,当其倾向于使用后端主机不可用时将会重新挑选新的后端其机。
另外还有一种称作fallback的director,用于定义备用服务器,如下所示:
director b3 fallback {
{ .backend = www1; }
{ .backend = www2; } // will only be used if www1 is unhealthy.
{ .backend = www3; } // will only be used if both www1 and www2
// are unhealthy.
}
如果想实现手动清除某个资源的缓存,则要配置
[root@varnish ~]# vim /etc/varnish/test.vcl acl purgers { "127.0.0.1"; "192.168.30.0"/24; } probe healthchk { .url = "/"; .interval = 5s; .timeout = 2s; } backend websrv1 { .host = "192.168.30.117"; .port = "80"; .probe = healthchk; } backend websrv2 { .host = "192.168.30.119"; .port = "80"; .probe = healthchk; } director webservers random { .retries = 2; { .backend = websrv1; .weight = 2; } { .backend = websrv2; .weight = 1; } } sub vcl_recv { if (req.request == "PURGE") { if (!client.ip ~ purgers) { error 405 "Method not allowed"; } return(lookup); } set req.http.X-Forward-For = client.ip; if (req.url ~ "\.html|js|css|jpg|gif|png|jpeg$") { return (pass); } # if (req.url ~ "\.php$") { # set req.backend = websrv2; # } else { # set req.backend = websrv1; # } set req.backend = webservers; } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged."; } } sub vcl_miss { if (req.request == "PURGE") { purge; error 404 "Not in cache."; } } sub vcl_pass { if (req.request == "PURGE") { error 503 "Purged on a passed object"; } } sub vcl_fetch { if (req.request == "GET" && req.url ~ "\.(html|jpg|jpeg)$") { set beresp.ttl = 3600s; } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> varnload test11 test.vcl 200 VCL compiled. varnish> vcl.use test11 200
测试清除缓存
[root@apache2 ~]# curl -I PURGE http://192.168.30.116/index.php curl: (6) Couldn't resolve host 'PURGE' HTTP/1.1 404 Not Found Server: Apache/2.2.15 (CentOS) Content-Type: text/html; charset=iso-8859-1 Content-Length: 287 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 10:12:24 GMT X-Varnish: 1291353453 1291353450 Age: 15 Via: 1.1 varnish Connection: keep-alive X-Cache: HIT from 192.168.30.116 [root@apache2 ~]# curl -X PURGE http://192.168.30.116/index.php <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>200 Purged.</title> </head> <body> <h1>Error 200 Purged.</h1> <p>Purged.</p> <h3>Guru Meditation:</h3> <p>XID: 1291353454</p> <hr> <p>Varnish cache server</p> </body> </html> [root@apache2 ~]# curl -I PURGE http://192.168.30.116/index.php curl: (6) Couldn't resolve host 'PURGE' HTTP/1.1 404 Not Found Server: Apache/2.2.15 (CentOS) Content-Type: text/html; charset=iso-8859-1 Content-Length: 287 Accept-Ranges: bytes Date: Tue, 08 Apr 2014 10:13:13 GMT X-Varnish: 1291353455 Age: 0 Via: 1.1 varnish Connection: keep-alive X-Cache: MISS
发现缓存已经被清除
purge用于清理缓存中的某特定对象及其变种(variants),因此,在有着明确要修剪的缓存对象时可以使用此种方式。HTTP协议的PURGE方法可以实现purge功能,不过,其仅能用于vcl_hit和vcl_miss中,它会释放内存工作并移除指定缓存对象的所有Vary:-变种,并等待下一个针对此内容的客户端请求到达时刷新此内容。另外,其一般要与return(restart)一起使用。
下面配置varnish的防盗链
[root@varnish ~]# vim /etc/varnish/test.vcl acl purgers { "127.0.0.1"; "192.168.30.0"/24; } probe healthchk { .url = "/"; .interval = 5s; .timeout = 2s; } backend websrv1 { .host = "192.168.30.117"; .port = "80"; .probe = healthchk; } backend websrv2 { .host = "192.168.30.119"; .port = "80"; .probe = healthchk; } director webservers random { .retries = 2; { .backend = websrv1; .weight = 2; } { .backend = websrv2; .weight = 1; } } sub vcl_recv { if (req.http.referer ~ "http://.*") { if (!(req.http.referer ~ "http://.*\.baidu\.com"|| req.http.referer ~ "http://.*\.google\.com.*")) { set req.http.host = "www.baidu.com"; set req.url = "/abc/abc.jpg"; } } if (req.request == "PURGE") { if (!client.ip ~ purgers) { error 405 "Method not allowed"; } return(lookup); } set req.http.X-Forward-For = client.ip; if (req.url ~ "\.html|js|css|jpg|gif|png|jpeg$") { return (pass); } # if (req.url ~ "\.php$") { # set req.backend = websrv2; # } else { # set req.backend = websrv1; # } set req.backend = webservers; } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged."; } } sub vcl_miss { if (req.request == "PURGE") { purge; error 404 "Not in cache."; } } sub vcl_pass { if (req.request == "PURGE") { error 503 "Purged on a passed object"; } } sub vcl_fetch { if (req.request == "GET" && req.url ~ "\.(html|jpg|jpeg)$") { set beresp.ttl = 3600s; } } sub vcl_deliver { if (obj.hits > 0 ) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return (deliver); } varnish> vcl.load test12 test.vcl 200 VCL compiled. varnish> vcl.use test12 200
在apache1上安装php,并在apahce1和apache2网站目录里分别建立/abc/abc.jpg拒绝提示页面
[root@apache1 ~]# yum -y install php [root@apache1 ~]# service httpd restart [root@apache1 ~]# mkdir /var/www/html/abc [root@apache1 ~]# echo "not allow" >> /var/www/html/abc/abc.jpg [root@apache2 ~]# mkdir /var/www/html/abc [root@apache2 ~]# echo "not allow" >> /var/www/html/abc/abc.jpg
测试防盗链效果
[root@apache2 ~]# curl -e http://www.abc.com http://192.168.30.116/index.php not allow [root@apache2 ~]# curl -e http://www.baidu.com http://192.168.30.116/index.php this apache1 php server
测试发现防盗链效果生效
到此,varnish各种配置功能就说这么多了。
转载于:https://blog.51cto.com/luojianlong/1392259