、Varnish的配置语言VCL

Varnish的所有配置都是通过VCL(varnish configure language)来配置的。它是一种基于“域”(domain specific)的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用set自定义变量、支持if判断语句,也有内置的函数和变量等。使用VCL编写的缓存策略通常保存至.vcl文件中,其需要编译成二进制的格式后才能由varnish调用。事实上,整个缓存策略就是由几个特定的子例程如vcl_recv、vcl_fetch等组成,它们分别在不同的位置(或时间)执行,如果没有事先为某个位置自定义子例程,varnish将会执行默认定义的代码,这些代码就是default.vcl中被注释的代码。

VCL策略在启用前,会由management进程将其转换为C代码,而后再由gcc编译器将C代码编译成二进制程序。编译完成后,management负责将其连接至varnish实例,即child进程。正是由于编译工作在child进程之外完成,它避免了装载错误格式VCL的风险。因此,varnish修改配置的开销非常小,其可以同时保有几份尚在引用的旧版本配置,也能够让新的配置即刻生效。编译后的旧版本配置通常在varnish重启时才会被丢弃,如果需要手动清理,则可以使用varnishadm的vcl.discard命令完成。

 

二、VCL语法

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

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

(2)sub $name 定义函数

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

(4)使用终止语句(return)将控制权返回给varnish,没有返回值

(5)域专用

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

 

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

 

三、Varnish的内置函数(或者称为子例程)

(1)vcl_recv函数

用于接收客户端请求、解析请求、决定是否响应请求、如何处理请求。当成功接收客户端请求后被调用。

它通常有四个主要用途:

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

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

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

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


vcl_recv函数可以通过具有的返回状态或动作来决定如何处理请求。

该函数有如下返回状态或动作:

pass:表示该请求不再本地缓存中查找,并进入pass模式,且将处理请求控制权交给vcl_pass函数。通常请求的内容包含一些cookie信息、用户认证的用户名和密码等私有信息时都一般不查询缓存。还有请求的是POST、PUT等方法时通常也不需要查询缓存。如果请求的内容时动态内容时,一般也不从缓存中查询。

pipe:表示该请求不再本地缓存中查找,进入pipe模式,并将请求控制权交给vcl_pipe函数。此模式下varnish不会对客户端做任何的检查或操作,而是在客户端和后端服务器直接建立管道,并将数据直接在这个管道中传输。此时,keep-alive连接中后续的数据也会通过管道进行传输,并且不会出现在任何日志中。通常varnish无法识别的请求,则交给vcl_pipe函数处理,继而交给后端原始服务器进行处理。

error code [reason]:varnish返回错误代码给客户端并丢弃该请求。code表示错误代码,如404,405等等。Reason表示错误提示信息。

lookup:表示该请求在缓存中查找请求的资源。并根据查找的结果将请求控制权交给vcl_hitvcl_miss函数。

 

(2)pipe函数

当vcl_recv函数返回pipe状态时被调用,并进入pipe模式,用于将客户端请求直接传递给后端服务器,在请求和返回的内容没有改变的情况下,将不变的内容直接返回给客户端。直到这个连接关闭。

该函数具有如下返回状态或动作:

error code reason:返回错误代码并丢弃该请求

pipe:将请求交给后端原始服务器进行处理。

 

(3)、vcl_pass函数

vcl_recv函数或者vcl_hit函数或vcl_miss函数返回pass状态时该函数被调用,用于将客户端请求直接转发给后端服务器,后端服务器响应给客户端时,varnish通常不对响应数据进行缓存。由于直接将请求转发给后端服务器,因此该连接下的响应数据都是最新的。

该函数具有如下执行动作:

error code  reason:返回错误代码并丢弃该请求

pass:表示从后端服务器获取数据,并将请求控制权交给vcl_fetch函数

restart:重启整个vcl,并且该请求重新进行检查。即该请求重新接受vcl_recv函数的检查。执行restart动作后,会计算重启计数,当超过max_restarts最大重启计数会返回错误信息。

 

 

(4)、vacl_hash函数

当vcl_recv函数返回lookup状态时,该函数被调用。该函数的主要作用是对请求的uri做hash计算。并将hash计算的结果作为key来存储,其请求的内容作为value来存储。因此,varnish是一个key:value的缓存服务器。


(5)、vcl_hit函数

当vcl_recv函数返回lookup状态后,并对请求的uri做hash计算后,如果在缓存中找到请求数据,则表示命中,则自动调用该函数。

该函数具有如下返回状态或动作:

deliver:表示从缓存中找到的数据返回给客户端,并将控制权交给vcl_deliver函数。

pass:进入pass模式,并将控制权交给vcl_fetch。这种模式下,虽然在缓存中找到了数据,但是不使用缓存中的数据,有可能是过期的资源,需要对资源做新鲜度检测或者其他机制需要从后端服务器获取最新资源。

error code reason:返回错误代码并丢弃该请求

restart:重启整个vcl,并且该请求重新接受vcl_recv函数的检查。执行restart动作后,会计算重启计数,当超过max_restart最大计数器后会返回错误信息。

 

(6)、vcl_miss函数

当vcl_recv函数返回lookup状态后,并对请求的uri做hash计算后,如果在缓存中没有找到请求数据,则自动调用该函数。

该函数具有如下返回状态或动作:

fetch:表示从后端服务器获取请求数据,并叫控制权交给vcl_fetch

pass::进入pass模式,并将控制权交给vcl_pass

error code reason:表示返回错误代码和信息并丢弃该请求。

restart:重启整个vcl,并且该请求重新接受vcl_recv函数的检查。执行restart动作后,会计算重启计数,当超过max_restart最大计数器后将返回错误信息。

 

 

(7)、vcl_fetch函数

vcl_fetch函数主要用来将请求转发给后端的原始服务器,并对原始服务器的响应的数据做缓存决策的。

通常当vcl_pass函数返回pass状态时或vcl_miss函数返回fetch状态时,调用vcl_fetch函数。这些过程都是需要从后端服务器获取最新数据,并且根据返回的状态或动作来判断获取的数据是否需要缓存,还是将响应数据直接返回给客户端。

该函数具有如下动作:

deliver:表示从后端服务器获取到的资源或数据进行缓存。并将控制权交给vcl_deliver函数。

hit_for_pass:表示从后端服务器获取到的数据或资源不进行缓存,且将控制权交给vcl_deliver函数。并且后续对该对象的请求直接交给vcl_pass函数。

      error code reason:表示返回错误代码和信息并丢弃该请求。

restart:重启整个vcl,并且该请求重新接受vcl_recv函数的检查。执行restart动作后,会计算重启计数,当超过max_restart最大计数器后将返回错误信息。

 

 

(8)vcl_deliver函数

将请求的数据返回给客户端调用此函数。通常当vcl_hit返回deliver状态时或者vcl_fetch函数返回deliver和hit_for_pass状态时,该函数会被调用。

该函数具有如下执行动作:

deliver:将请求数据直接返回给客户端

error code reason:表示返回错误代码和信息并丢弃该请求。

restart:重启整个vcl,并且该请求重新接受vcl_recv函数的检查。执行restart动作后,会计算重启计数,当超过max_restart最大计数器后将返回错误信息。

 

(9)vcl_timeout函数

在缓存内容到期前调用此函数,该函数有如下执行动作:

discard:表示从缓存中清除

fetch:表示从后端服务器获取资源进行更新

 

(10)vcl_discard函数

在缓存内容到期后调用此函数,该函数具有如下执行动作:

keep:表示过期缓存对象仍然保留在缓存中。

discard:表示从缓存中清除

 

四、Varnish的工作流程图

wKioL1VbTo3iEgQGAAHSK_Vuhds734.jpg

 

Varnish处理请求流程大致可以分为如下几个步骤:

(1)、首先当请求到达时,接受vcl_recv函数的检查,在这个函数中,可以返回行passpipelookuperror等状态,不同的状态则将请求转给不通的函数或子例程进程处理。

(2)vcl_recv函数返回lookup状态时,则会在缓存中查看是否有该缓存对象。如果有,则表示缓存命中,则在接受vcl_hit函数的检查。在该函数下可以将缓存对象直接返回给客户端,即执行deliver操作。也可以不使用本地缓存对象,从后端服务器获取最新请求对象,即执行pass操作。如果本地没有该缓存对象,则表示缓存丢失,则会接受vcl_miss函数检查。该函数会从后端服务器获取最新资源,即将会执行passfetch操作,并将控制权交给vcl_fetch函数。

(3)如果在vcl_recv函数执行pass操作,则表示不再本地缓存查找,直接从后端服务器进行获取,即将会接受vcl_pass函数的检查。

(4)如果在vcl_recv函数处执行pipe操作,则表示不再本地缓存中查找,并且客户端和后端服务器建立管道,后续客户端的keep-alive连接中的请求数据传输将在管道中进行,并且请求数据接受vcl_pipe函数的检查。直到数据传输完成时关闭该管道连接。

(5)当需要从后端服务器获取资源或数据时,需要调用vcl_fetch函数,并且根据某种条件判断该数据或资源是否缓存在本地,如果该资源缓存在varnish中,则执行deliver操作;如果varnish获取到的最新数据不缓存在本地,则执行hit_for_pass操作。最后,不管是从本地缓存中响应的数据还是从后端原始服务器获取到的数据返回给客户端都需要调用vcl_deliver函数来完成。

 

五、VCL的内置变量

VCL中有许多的内置变量,这些内置变量在不同的阶段被使用。

定义后端原始服务器相关信息时,可以使用的变量

.host 表示后端原始服务器ip

.port 后端原始服务器监听的端口


当varnish接受客户端请求时,可以使用的内置变量

    server.ip     指定varnish的ip

    server.port   varnish监听的端口

    client.identity 客户端标识,该标识通常为客户端ip或者服务器打给客户端的cookie信息。

    client.ip     表示客户端ip

     req.backend    表示将请求转发给哪个后端原始服务器

    req.backend.healthy  表示后端原始服务器是否处于健康状态

    req.request :表示请求方法,如GET,HEAD,POST等

    req.url       请求的url

    req.proto     表示客户端发起请求使用的http协议版本

    req.http.<header>:  表示客户端请求报文中的http头部信息

    req.restarts:表示请求重启的次数;默认最大值为4。如果该值为0,表示该请求只经过vcl_recv一次检测。

 

当varnish向后端服务器发送请求时,可以使用的内置变量:

    bereq.url    请求的方法

    bereq.proto  表示varnish发起请求使用的http协议版本

    bereq.request 请求的方法

    bereq.http.header 请求报文的首部

    bereq.connect_timeout varnish连接原始服务器的超时时长



当原始服务器把响应报文发给varnish时,可以使用的内置变量:

    beresp.status   原始服务器响应的状态码

    beresp.proto    原始服务器响应时使用的http协议版本

    beresp.http.header  原始服务器响应报文中http首部信息

    beresp.ttl: 设置缓存的缓存时长 ,单位为秒

    beresp.backend.name  响应此请求的原始服务器的主机名

    beresp.backend.ip    响应此请求的原始服务器的ip

 

当varnish对客户端响应时,可以使用的内置变量:

resp.status: 返回给客户端的状态码

resp.proto:返回给客户端的http的协议版本

resp.http.<header>: 返回给客户响应报文的首部

resp.response: 返回给客户端的http状态信息

 

 

当某对象或资源进入缓存后,可以使用的内置变量:

obj.status: 返回该对象的响应状态码,如200,302等

obj.cacheable: 表示返回内容是否可以缓存,如果http返回的状态码为200,203,301,302,404,410,并且有非0的生存期,则可以缓存。

obj.valid: 是否为有效的http应答,即该缓存对象是否是有效的;

obj.response:表示返回该对象的状态信息

obj.ttl: 缓存对象剩下的缓存时长,单位为秒

obj.hits:请求的对象被deliver的次数,如果该值大于0,表示该对象已经被缓存。

obj.lastuse:表示上一次请求到现在所经过的时间

 

六、varnish的缓存策略

作为一个高性能的缓存服务器,并不是所有的数据有需要缓存的。有些时候,某些数据不缓存,其效果更好。所以,varnish需要对请求的数据做判断,已决定何种数据需要缓存。

varnish的缓存策略通常包括如下几点:

1、通常静态资源需要缓存。如html文档、图片等资源;

2、varnish通常只能识别正常的http请求方法,比如GET、HEAD、PUT、POST、OPTIONS、TRACE、DELETE等等。如果客户端请求的方法不是这些的话,则varnish不会执行任何处理,直接将请求转发给后端的原始服务器。

3、如果请求的GET、HEAD方法时,其请求的内容通常需要缓存。如果其PUT、POST等方法时,由于一些上传或提交的数据,因此缓存下来无意义,因此如果是这样的请求方法时,其内容通常也不会缓存下来。

4、不缓存用户的私有信息。对于用户的cookie信息,通常是不能缓存的,这是因为用户请求报文中带有cookie信息,varnish在缓存时,会将其uri和cookie信息一起做hash计算并将其结果作为key,由于每个客户端的cookie信息都是唯一的,而且varnish是一个key:value的缓存服务器,因此这样会导致不同的客户端即使请求同一个资源时,也是不会被某命中的,只有同一个客户端请求同一个资源时才会被命中。因此,这种场景下,不仅不会提高客户端的访问速度,而且无疑会增加原始服务器的负载。但是,在某些时候,用户请求资源时,根本就不需要提供cookie信息时,我们可以在varnish中将请求报文中的cookie首部给删除掉,这样就可以对该请求的资源进行缓存了。


如下面的一个例子,如果请求的不是正常的http方法时,则不交给varnish处理,直接交给后端服务器处理。

if (req.request != "GET" &&

    req.request != "HEAD" &&

    req.request != "PUT" &&

    req.request != "POST" &&

    req.request != "TRACE" &&

    req.request != "OPTIONS" &&

    req.request != "DELETE") {

    return (pipe);

  }

  //如果请求的即不是GET方法也不是HEAD方法,则不查询缓存

  if (req.request != "GET" && req.request != "HEAD") {

    return (pass);

  }

 //如果请求首部中包含客户端认证信息,如用户名和密码则不查询缓存

  if (req.http.Authorization) {

    return (pass);

  }

 

下面一个事例中,请求报文带有cookie首部,但是这些请求的资源中不需要cookie时,应当采取的缓存策略。

如果请求的url中不包括login或admin这些字符时,则应该将请求报文中的cookie首部删除。因为请求的资源是不需要客户端提供cookie的。

sub vcl_recv {

    if (!(req.url ~ "wp-(login|admin)")) {

        unset req.http.cookie;

    }

}

如果请求的url中不包括login或admin字符串时,那么当varnish从后端原始服务器获取数据时,将响应报文中的set-cookie首部删除掉。

sub vcl_fetch {

    if (!(req.url ~ "wp-(login|admin)")) {

        unset beresp.http.set-cookie;

    }

}





七、管理Varnish缓存内容

1、提高缓存命中率

提高缓存命中率有效的途径有:

a、增加缓存对象的生存时间(TTL

b、加大内存

 

2、清理缓存

增大缓存的ttl值也可能带来副作用,有时候某个缓存的有效时间还没到期时,该缓存对象已经更新了。因此,对于这种情况,对于缓存管理员来说,需要手动清理已经过期或者旧缓存对象。对于Varnish来说,它提供了三种方式来完成这类任务:HTTP修剪(HTTP purging)、禁用某类缓存对象和强制缓存未命中forced cache misses

aHTTP修剪(purge)

purge用于清理缓存中的某特定对象及其变种(variants),因此,在有着明确要修剪的缓存对象时可以使用此种方式。HTTP协议的PURGE方法可以实现purge功能,不过,其仅能用于vcl_hit和vcl_miss中,它会释放内存空间并移除指定缓存对象的所有Vary:-变种,并等待下一个针对此内容的客户端请求到达时刷新此内容。另外,其一般要与return(restart)一起使用。下面是个在VCL中配置的示例。

//首先定义一个可以执行PURGE操作的地址,这里使用acl的方式来定义

acl purgers {

        "127.0.0.1";

        "192.168.108.0"/24;

}


sub vcl_recv {

        if (req.request == "PURGE") {

                if (!client.ip ~ purgers){

//如果客户端的ip不属于acl中的话,那么varnish将丢弃该请求,并响应如下错误信息。

                        error 405 "The Method is not allowed";

                }

//如果请求的方法是PURGE,且客户端的ip是属于acl中的话,则在缓存中查找

                return(lookup);

        }

}


//如果缓存命中的话,则执行purge操作,并返回错误信息

sub vcl_hit {

        if (req.request == "PURGE"){

                purge;

                error 200 " already purged";

        }

}


//如果缓存为命中的话,也执行purge操作,并返回错误信息

sub vcl_miss {

        if (req.request == "PURGE") {

                purge;

                error 404 "Not in cache";

        }

}


客户端在发起HTTP请求时,只需要为所请求的URL使用PURGE方法即可,其命令使用方式如下:

# curl -I -X PURGE http://varniship/path/to/someurl



http pugre功能是还可以通过Varnishtelnet管理端口发送purge指令来清除不需要的缓存对象。其指令格式为:

/usr/local/varnish/bin/varnishadm  -T 192.168.0.102:2000 purge.url  <REGEXP>

REGEXP:是基于正则表达式匹配的url

如:# /usr/local/varnish/bin/varnishadm –T 192.168.0.102:2000 purge.url  /a/varnish1.html

删除所有的缓存对象,可以使用如下指令

       如:# /usr/local/varnish/bin/varnishadm –T 192.168.0.102:2000 purge.url ^.*$

查看最近删除的缓存对象列表,可以使用如下命令

       如:# /usr/local/varnish/bin/varnishadm –T 192.168.0.102:2000 purge.list

 

除了在命令行中删除缓存对象这种方式外,可以在通过登录到telnet的管理界面中进行删除动作。如:

[root@varnish-server ~]#telnet 192.168.0.102  2000             

Trying 192.168.0.103...  

Connected to localhost.localdomain (192.168.0.103).  

Escape character is '^]'.  

200 154       

-----------------------------  

Varnish HTTP accelerator CLI.  

-----------------------------  

Type 'help' for command list.  

Type 'quit' to close CLI session.  

 

purge.url  /a/mz/2010/0421/11.html  #这里是清除这个页面缓存 

200 0         

 

purge.url  ^/zm/a/d.*$   #这里是清除/zm/a/d/目录下所有以字母d开头的缓存页面 

200 0  

 

b、强制缓存未命中

vcl_recv中使用return(pass)能够强制到上游服务器取得请求的内容,但这也会导致无法将其缓存。使用purge会移除旧的缓存对象,但如果上游服务器宕机而无法取得新版本的内容时,此内容将无法再响应给客户端。使用req.has_always_miss=ture,可以让Varnish在缓存中搜寻相应的内容但却总是回应“未命中”,于是vcl_miss将后续地负责启动vcl_fetch从上游服务器取得新内容,并以新内容缓存覆盖旧内容。此时,如果上游服务器宕机或未响应,旧的内容将保持原状,并能够继续服务于那些未使用req.has_always_miss=true的客户端,直到其过期失效或由其它方法移除。

 

c、禁用某类缓存对象(banning

ban()操作是在Varnish 3中才有的,Varnish 2中的purge()操作在Varnish 3中被替换为了ban()操作,而Varnish 3也使用了purge操作,但为其赋予了新的功能。ban()这个操作有人说支持也有人反对,因此,对于这种方式暂且不说。

 


八、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自身也会占去不小的内存空间。

 

varnishd指定使用的缓存类型时,-s选项可接受的参数格式如下:

       malloc[,size]

       file[,path[,size[,granularity]]]

       persistent,path,size {experimental}

      

file中的granularity用于设定缓存空间分配单位,默认单位是字节。使用该参数后,系统不会一次性为varnish分配指定的磁盘空间大小,只有当存储不足时,则存储varnish的磁盘空间会自动增长,直到最大值。所有其它的大小都会被圆整。


varnish的缓存类型可以在varnish脚本的配置文件/etc/sysconfig/varnish中进行配置。

 

 


九、Varnish定义多后端主机

Varnish中可以使用director指令将一个或多个近似的后端主机定义为一个逻辑组,并可以指定相应的调度方式(也叫挑选方法)来将请求发送至这些主机上。不同的director可以使用同一个后端主机,而某director中也可以使用“匿名”后端主机(director中使用.backend直接进行定义)。每个director都必须有其专用名,且在定义后必须在VCL中进行调用,VCL中任何可以指定后端主机的位置均可以按需将其替换为调用某已定义的director

定义多个后端主机的配置实例

.backend web1 {          #显式定义一台后端服务器

       .host = “backend1.xsl.com”;

       .port = “80”;

}

director webserver random {  #定义一个后端服务器逻辑组,其调度方式为random(即加权随机)

       .retries = 5;

       {

              .backend = web1;

              .weight =2;

}

{

       .backend = {    #定义一个匿名后端主机

              .host = “backend2.xsl.com”;

              .port = ‘80’;

       }

       .weight = 3;

}

}

说明:如果定义了多个后端主机时,不管是使用.backend方式定义的,还是使用director方式定义的,都需要调用这些主机才会生效。如果在varnish只定义了一个后端主机,则不需要调用就会生效。



对于上例而言,web1为显式定义的后端主机。如果有多台的话,需要使用director指令来定义。在上例中,使用director指令定义了一个逻辑组叫做webserver。其中webserver组中需要指定后端服务器的地址及其ip等参数。在webserver组中,定义了2个后端服务器,web1为显式定义的后端主机,“backend2.xsl.com”为匿名主机。

使用director定义某个逻辑组时,由于有多台主机,因此,需要指定调度方式,以便将请求转给相应的后端服务器。director中有2种调调方式:

       round-robin:轮调,即请求依次循环的转发给下一个后端主机。该调度方式没有其他参数,只需要指定后端原始主机即可。

       random:随机调度,即从后端原始主机中随机挑选一台服务器对请求进行响应。在random方式下需要为每一个后端主机指定其weight值,因此这是基于权重的随机算法。Varnish 2.1.0后,random挑选方法又多了两种变化形式clienthashclient类型的director使用client.identity作为挑选因子,这意味着client.identity相同的请求都将被发送至同一个后端主机。client.identity默认为cliet.ip,但也可以在VCL中将其修改为所需要的标识符。类似地,hash类型的director使用hash数据作为挑选因子,这意味着对同一个URL的请求将被发往同一个后端主机,其常用于多级缓存的场景中。然而,无论是clienthash,当其倾向于使用后端主机不可用时将会重新挑选新的后端主机。

director级别下,还可以使用.retries参数来设定查找一个健康的后端主机的尝试次数。


十、varnish实现资源重定向(动静分离)

varnish可以根据用户请求资源的不同,将其请求定向到不同的原始服务器进行处理。

在一个中等以上的架构环境中,我们需要将用户请求的静态资源如html文档则交给静态服务器进行响应;如果请求的是图片这样的静态资源,则交给图片服务器进行处理。如果请求的是php等动态资源时,则交给动态服务器处理。不能被varnish正常识别的请求也交给静态服务器进行处理。除此之外,所有的请求都需要先查询缓存,然后在进一步处理。

其拓扑图为:

wKioL1agSRLwr04sAABkiGG7zpc345.png


如下实例,定义了3个后端主机。并根据请求的资源不同,将其请求转发至不同的后端主机上。

//静态服务器

backend web1 {

        .host="172.16.1.100";

        .port="80";

}

//图片服务器

backend web2 {

        .host="172.16.1.200";

        .port="80";

}

//动态服务器

backend web3 {

      .host="172.16.1.33";

       .port="80";

}

   

//如果请求的方法不是正常的http方法,则varnish无法识别,则将请求交给后端原始服务器web1进行处理。

sub vcl_recv {

      if (req.request != "GET" &&

            req.request != "HEAD" &&

            req.request != "PUT" &&

            req.request != "POST" &&

             req.request != "TRACE" &&

            req.request != "OPTIONS" &&

            req.request != "DELETE") {

          set req.backend = web1;

           return (pipe);

      }


//如果请求的资源是以.html、.css、.js、.txt结尾的文件,则直接将请求转发至web1服务器上。

        if (req.url ~ "\.(html|css|js|txt)$") {

                set req.backend=web1;

        }

//如果请求的资源是以.png、.jpg、.img结尾的文件,则直接将请求转发至web2服务器上。

        if (req.url ~ "\.(png|jpg|img)$") {

                set req.backend=web2;

        }

//如果请求的是动态资源,则不查询缓存,直接转交给后端原始服务器web3进行处理

        if (req.url ~ "\.php") {

            set req.backend=web3

                return(pass);

        }

//其他的请求则都需要查询缓存

        return(lookup);

}


为了测试请求的资源是否命中,在http的响应首部中添加了一个X-Cache首部。

sub vcl_deliver {

        if (obj.hits > 0 ){

                set resp.http.X-Cache = "Hit from " + server.ip;

        }else {

                set resp.http.X-Cache = "Miss";

        }

}


//为了方便测试,在http的响应首部中添加了一个X-Server首部,该首部的value为响应该请求的原始服务器。

sub vcl_fetch {

        if (!beresp.http.X-Server) {

                set beresp.http.X-Server=beresp.backend.ip;

        }

}

 


十一、Varnish的后端状态检测

Varnish可以检测后端主机的健康状态,在判定后端主机失效时能自动将其从可用后端主机列表中移除,而一旦其重新变得可用还可以自动将其设定为可用。为了避免误判,Varnish在探测后端主机的健康状态发生转变时(比如某次探测时某后端主机突然成为不可用状态),通常需要连续执行几次探测均为新状态才将其标记为转换后的状态。

 

Varnish检测后端服务器的健康状况是通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的Backend_health查看或varnishadmdebug.health查看。

 

Varnish检测后端服务器健康状况的配置实例:

backend web1 {

       .host = "www.xsl.com";

       .probe = {

              .url = "/.healthtest.html";

              .interval = 1s;

              .window = 5;

              .threshold = 2;

       }

}

.probe中的探测指令常用的有:

(1) .url:探测后端主机健康状态时请求的URL,默认为“/”;

(2) .request: 探测后端主机健康状态时所请求内容的详细格式,定义后,它会替换.url指定的探测方式;比如:

       .request =

              "GET /.healthtest.html HTTP/1.1"

              "Host: www.magedu.com"

              "Connection: close";

(3) .window:设定在判定后端主机健康状态时基于最近多少次的探测来进行,默认是8

(4) .threshold:在.window中指定的次数中,至少有多少次是成功的才判定后端主机是健康运行;默认是3

(5) .initialVarnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold

(6) .expected_response:期望后端主机响应的状态码,默认为200

(7) .interval:探测请求的发送周期,默认为5秒;

(8) .timeout:每次探测请求的过期时长,默认为2秒;

因此,对于上例中表示每隔1秒对此后端主机www.xsl.com探测一次,请求的URLhttp://www.xsl.com/.healthtest.html,在最近5次的探测请求中至少有2次是成功的(响应码为200)就判定此后端主机为正常工作状态。


十二、varnish的防盗链功能

varnish的防盗链功能是根据请求首部中的referer来进行判断的。referer首部表示该请求是从哪个页面跳转过来的。

在vcl_recv子例程中添加如下配置段:

if(req.http.referer ~ "http://.*") {

//如下行是定义哪些是不符合的请求

                if(!(req.http.referer ~ "http://.*google\.com" || req.http.referer ~ "http://.*yahoo\.cn" || req.http.referer ~ "http://.*google\.cn" )) {

                        set req.http.host = "www.xsl.com";

                        set req.url = "/default.jpg";

                } else {

                        return(lookup);

                }

}

在这段配置中,利用req.http.referer首部来判断请求报文中是否包含referer字段,如果请求报文中包含referer首部,但是该首部的value不匹配http://.*google\.com、http://.*google\.cn、http://.*yahoo\.cn这些地址的话,就认为是不符合的请求,则重新设定请求报文中的host首部为"www.xsl.com"。且url为"/default.jpg",其实就是实现url重写了。这样请求被定向为http://www.xsl.com/default.jpg。如果请求报文中包含referer首部,且该value匹配http://.*google\.com、http://.*google\.cn、http://.*yahoo\.cn这些地址的话,则直接查询缓存。