大纲

   一、Varnish 简介

   二、Varnish 特点

   三、Varnish 与 Squid 对比

   四、Varnish 设计结构

   五、Varnish 工作流程

   六、Varnish 状态引擎(state engine)

   七、安装与配置 Varnish                      


一、Varnish 简介

Varnish是一款高性能的开源HTTP加速器,挪威最大的在线报纸 Verdens Gang 使用3台Varnish代替了原来的12台Squid,性能比以前更好。


Varnish 的作者Poul-Henning Kamp是FreeBSD的内核开发者之一,他认为现在的计算机比起1975年已经复杂许多。在1975年时,储存媒介只有两种:内存与硬盘。但现在计算机系统的内存除了主存外,还包括了CPU内的L1、L2,甚至有L3快取。硬盘上也有自己的快取装置,因此Squid Cache自行处理物件替换的架构不可能得知这些情况而做到最佳化,但操作系统可以得知这些情况,所以这部份的工作应该交给操作系统处理,这就是 Varnish cache设计架构。


Varnish项目是2006年发布的第一个版本0.9.距今已经八年多了,此文档之前也提过varnish还不稳定,那是2007年时候编写的,经过varnish开发团队和网友们的辛苦耕耘,现在的varnish已经很健壮。很多门户网站已经部署了varnish,并且反应都很好,甚至反应比squid还稳定,且效率更高,资源占用更少。相信在反向代理,web加速方面,varnish已经有足够能力代替squid。


二、Varnish 特点

Varnish是一个轻量级的Cache和反向代理软件。先进的设计理念和成熟的设计框架是Varnish的主要特点。现在的Varnish总共代码量不大,虽然功能在不断改进,但是还需要继续丰富和加强。下面总结了Varnish的一些特点。

  • 基于内存进行缓存,重启后数据将消失。

  • 利用虚拟内存方式,I/O性能好。

  • 支持设置0~60秒内的精确缓存时间。

  • VCL配置管理比较灵活。

  • 32位机器上缓存文件大小为最大2GB。

  • 具有强大的管理功能,例如top,stat,admin,list等。

  • 状态机设计巧妙,结构清晰。

  • 利用二叉堆管理缓存文件,达到积极删除目的。


三、Varnish 与 Squid 对比

说到Varnish,不能不提Squid,Squid是一个高性能的代理缓存服务器,它和Varnish之间有诸多的异同点,下面进行分析。

下面是Varnish与Squid之间的相同点。

  • 都是一个反向代理服务器。

  • 都是开源软件。


下面是它们的不同点,也是Varnish的优点。

  • Varnish的稳定性很高。两者在完成相同负荷的工作时,Squid服务器发生故障的几率要高于Varnish,因为Squid需要经常重启

  • Varnish访问速度更快。Varnish采用了“Visual Page Cache”技术,所有缓存数据都直接从内存读取,而Squid是从硬盘读取缓存数据,因此Varnish在访问速度方面会更快。

  • Varnish可以支持更多的并发连接。因为Varnish的TCP连接释放要比Squid快,所以在高并发连接情况下可以支持更多TCP连接。

  • Varnish可以通过管理端口,使用正则表达式批量清除部分缓存,而Squid做不到。 当然,与传统的Squid相比,


Varnish也有缺点。

  • Varnish在高并发状态下CPU、I/O和内存等资源开销都高于Squid。

  • Varnish进程一旦挂起、崩溃或者重启,缓存数据都会从内存中完全释放,此时所有请求都会被发送到后端服务器,在高并发情况下,这会给后端服务器造成很大压力。


四、Varnish 设计结构

wKioL1NvY6-Rl8QWAAE02BxXtwc171.jpg

Varnish主要运行两个进程:Management进程和Child进程(也叫Cache进程)。如上图,


1.Management进程

Management进程主要实现应用新的配置、编译VCL、监控varnish、初始化varnish以及提供一个命令行接口等。Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。

Management 管理接口:

  • CLI interface 命令行接口

  • Telnet interface telnet接口

  • Web interface Web管理接口


2.Child/Cache 进程

Child/Cache 进程包含多种类型的线程,常见的如:

  • Accept 线程:接收新的连接请求并响应;

  • Worker 线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;

  • Object Expiry 线程:从缓存中清理过期内容;

  • Commad line 线程 : 管理接口

  • Storage/hashing 线程 :缓存存储

  • Log/stats 线程:日志管理线程

  • Backend Communication 线程:管理后端主机线程

Varnish依赖“工作区(workspace)”以降低线程在申请或修改内存时出现竞争的可能性。在varnish内部有多种不同的工作区,其中最关键的当属用于管理会话数据的session工作区。


3.Varnish日志

为了与系统的其它部分进行交互,Child进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个worker线程都使用了日志数据缓存。共享内存日志大小一般为90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。varnish提供了多个不同的工具如varnishlog、varnishncsa或varnishstat等来分析共享内存日志中的信息并能够以指定的方式进行显示。


4.VCL(Varnish Configuation Language)简介

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命令完成。


5.Varnish 的后端存储

Varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。后端存储的类型包括:

  • file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);

  • malloc:使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象;

  • 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 工作流程

wKiom1NvZBTSh_POAAMZtFoJlSU657.jpg

Varnish处理HTTP请求的过程大致分为如下几个步骤:

  • Receive状态,也就是请求处理的入口状态,根据VCL规则判断该请求应该Pass或Pipe,还是进入Lookup(本地查询)。

  • Lookup状态,进入此状态后,会在hash表中查找数据,若找到,则进入Hit状态,否则进入miss状态。

  • Pass状态,在此状态下,会进入后端请求,即进入Fetch状态。

  • Fetch状态,在Fetch状态下,对请求进行后端获取,发送请求,获得数据,并进行本地存储。

  • Deliver状态, 将获取到的数据发送给客户端,然后完成本次请求。


六、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内置函数

(1).vcl_recv函数 用于接收和处理请求。当请求到达并被成功接收后被调用,通过判断请求的数据来决定如何处理请求。 此函数一般以如下几个关键字结束。

  • pass:表示进入pass模式,把请求控制权交给vcl_pass函数。

  • pipe:表示进入pipe模式,把请求控制权交给vcl_pipe函数。

  • error code [reason]:表示返回“code”给客户端,并放弃处理该请求。“code”是错误标识,例如200和405等,“reason”是错误提示信息。

(2).vcl_pipe函数 此函数在进入pipe模式时被调用,用于将请求直接传递至后端主机,在请求和返回的内容没有改变的情况下,将不变的内容返回给客户端,直到这个链接被关闭。 此函数一般以如下几个关键字结束。

  • error code [reason]

  • pipe

(3).vcl_pass函数 此函数在进入pass模式时被调用,用于将请求直接传递至后端主机,后端主机在应答数据后将应答数据发送给客户端,但不进行任何缓存,在当前连接下每次都返回最新的内容。 此函数一般以如下几个关键字结束。

  • error code [reason] 。

  • pass。

(4).lookup 表示在缓存中查找被请求的对象,并且根据查找的结果把控制权交给函数vcl_hit或函数vcl_miss。

(5).vcl_hit函数 在执行lookup指令后,在缓存中找到请求的内容后将自动调用该函数。 此函数一般以如下几个关键字结束。

  • deliver:表示将找到的内容发送给客户端,并把控制权交给函数vcl_deliver。

  • error code [reason] 。

  • pass。

(6).vcl_miss函数 在执行lookup指令后,在缓存中没有找到请求的内容时自动调用该方法,此函数可用于判断是否需要从后端服务器获取内容。 此函数一般以如下几个关键字结束。

  • fetch:表示从后端获取请求的内容,并把控制权交给vcl_fetch函数。

  • error code [reason] 。

  • pass。

(7).vcl_fetch 函数
在后端主机更新缓存并且获取内容后调用该方法,接着,通过判断获取的内容来决定将内容放入缓存,还是直接返回给客户端。此函数一般以如下几个关键字结束。\

  • rror code [reason]

  • pass

  • deliver

(8).vcl_deliver 函数
将在缓存中找到请求的内容发送给客户端前调用此方法。此函数一般以如下几个关键字结束。

  • error code [reason]

  • deliver

(9).vcl_timeout 函数
在缓存内容到期前调用此函数。此函数一般以如下几个关键字结束。

  • discard:表示从缓存中清除该内容。

  • fetch。

(10).vcl_discard 函数
在缓存内容到期后或缓存空间不够时,自动调用该函数。该函数一般以如下几个关键字结束。

  • keep:表示将内容继续保留在缓存中。

  • discard。

4.内置公用变量

VCL内置的公用变量可以用在不同的VCL函数中。下面根据这些公用变量使用的不同阶段依次进行介绍。

(1).当请求到达后,可以使用的公用变量

  • req.backend 指定对应的后端主机

  • server.ip 表示服务器端IP

  • client.ip 表示客户端IP

  • req.request 指定请求的类型,例如GET、HEAD和POST等

  • req.url 指定请求的地址

  • req.proto 表示客户端发起请求的HTTP协议版本

  • req.http.header 表示对应请求中的HTTP头部信息

  • req. restarts ;/.l表示请求重启的次数,默认最大值为4

(2).Varnish在向后端主机请求时,可以使用的公用变量

  • beresp.request 指定请求的类型,例如GET合HEAD等

  • beresp.url 指定请求的地址

  • beresp .proto 表示客户端发起请求的HTTP协议版本

  • beresp .http.header 表示对应请求中的HTTP头部信息

  • beresp .ttl 表示缓存的生存周期,也就是cache保留多长时间,单位是秒

(3).从cache或后端主机获取内容后,可以使用的公用变量

  • obj.status 表示返回内容的请求状态代码,例如200、302和504等

  • obj.cacheable 表示返回的内容是否可以缓存,也就是说,如果HTTP返回的是200、203、300、301、302、404或410等,并且有非0的生存期,则可以缓存

  • obj.valid 表示是否是有效的HTTP应答

  • obj.response 表示返回内容的请求状态信息

  • obj.proto 表示返回内容的HTTP协议版本

  • obj.ttl 表示返回内容的生存周期,也就是缓存时间,单位是秒

  • obj.lastuse 表示返回上一次请求到现在的间隔时间,单位是秒

(4).对客户端应答时,可以使用的公用变量

  • resp.status 表示返回给客户端的HTTP状态代码

  • resp.proto 表示返回给客户端的HTTP协议版本

  • resp.http.header 表示返回给客户端的HTTP头部信息

  • resp.response 表示返回给客户端的HTTP状态信息

在上面的讲述中,只介绍了常用的VCL内置公用变量,如果需要了解和使用更多的公用变量信息,请登录varnish官方网站查阅。https://www.varnish-cache.org/docs/3.0/


5.常用内置函数

(1).vcl_recv

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

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

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

  • 为某web应用程序执行URL重写规则;

  • 挑选合适的后端Web服务器;

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

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

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

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

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

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

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

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

  • 不缓存任何用户特有的数据;

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

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

1
2
3
4
5
6
7
8
9
sub vcl_recv {
if(req.http.User-Agent ~ "iPad"||
req.http.User-Agent ~ "iPhone"||
req.http.User-Agent ~ "Android") {
setreq.http.X-Device = "mobile";
} else{
setreq.http.X-Device = "desktop";
}
}

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

(2).vcl_fetch

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

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

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

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

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

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


6.其它内置函数

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参数则用于限定最大重启次数。


七、安装配置varnish

           clients
      (IP:172.16.41.254)          
              |
              |
              |
      (WAN:172.16.41.1)
           varnish
      (LAN:192.168.100.1)
              |
              |
              |
    +--------------------+
    |                    |
    |                    |
  web1                  web2

(192.168.100.51)     (192.168.100.52)



1.使用yum包的安装(默认为2系列的最新版)
[root@varnish ~]# yum install -y varnish
[root@varnish ~]# yum install -y varnish
2.配置配置文件:
[root@varnish ~]# vim /etc/sysconfig/varnish
VARNISH_LISTEN_PORT=80    #--->修改监听端口为80
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1    #--->管理监听IP
VARNISH_ADMIN_LISTEN_PORT=6082  #--->管理监听端口
VARNISH_STORAGE="malloc,500M"#--->修改缓存类型为malloc,并制定缓存空间大小
VARNISH_VCL_CONF=/etc/varnish/test.vcl  #--->vcl配置文件配置文件绝对路径
保存退出
(1)创建vcl配置文件:    
[root@varnish ~]# vim /etc/varnish/test.vcl
backend default {
  .host = "192.168.100.51"; #--->将该IP改为后端web服务器的地址
  .port = "80";
}
保存退出,重启一下服务(注意这里只是测试环境,生产环境下不能时常重启,因为所有的缓存都是保存于内存中的)
(2)编译vcl文件并使用自定义的vcl配置文件:
这里我们需要连接管理varnish(有两种方式连接)
其中我们要是使用到"varnishadm"命令来管理:
格式
wKiom1NvXS-AUbV9AAB91nGB4nU078.jpg
交互式模式
命令行模式
我们用第一种:
    但是如果你是远程连接的话我们需要用-S 来指定密钥文件(可拷贝varnish服务器中的/etc/varnish/secert到你的远程主机中),-T 指定你的远程管理IP和端口,但是在本地主机后面的这些参数都可以省略...
[root@varnish ~]# varnishadm
200   
-----------------------------
Varnish Cache CLI 1.0
-----------------------------
Linux,2.6.32-431.el6.x86_64,x86_64,-smalloc,-smalloc,-hcritbit
varnish-3.0.4 revision 9f83e8f
Type 'help' for command list.
Type 'quit' to close CLI session.
varnish> #--->这里可以输入help来获取帮助信息的.
下面继续我们的操作:
varnish> vcl.load test1 /etc/varnish/test.vcl
200   
VCL compiled.
    test1 :自定义,后面要指定你的vcl配置文件,
    这里我们在使用vcl.load的时候,只要你的配置文件修改过,那么就需要更改相应的自定义配置文件例如:test1,test2,test3....这表示我修改了3次,相当于版本升级一次的概念.后续就不再提起!
加载完成后我们就可以使用了:
varnish> vcl.use test1
200   
varnish> quit
测试:
    需要保证你的varnish监听在80port上面,后端的web服务也要启动起来;
spacer.gif
相应报文中的信息:
spacer.gif
1.但是我们如何知道这个请求是命中缓存的呢?
下面我们就需要去修改vcl文件
加入下面这行:
sub vcl_deliver {   #--->在响应报文中添加
   if (obj.hits > 0){   #--->使用obj.hits内置变量去判断(对于某个对象)
   set resp.http.X-Cache = "HIT";  #--->如果大于0就命中缓存
   } else {
   set resp.http.X-Cache = "MISS";   #--->否则就未命中
   }
}
只要更改了vcl配置文件配置文件就需要重新装载和使用,后续的操作步骤略.直接检验效果即可!
第一次访问:

wKioL1NvXZrQxaPdAAAqzlA4RUw860.jpg

第二次,第N次访问就是MISS:
spacer.gifwKiom1NvXcWhiYctAAAwysRmDiM630.jpg

2.如何让客户端知道是通过哪个节点命中缓存的?
(1)更改test.vcl配置文件在原来的配置中加入<绿色部分>:
sub vcl_deliver {   #--->在响应报文中添加
   if (obj.hits > 0){   #--->使用obj.hits内置变量去判断(对于某个对象)
   set resp.http.X-Cache = "HITfrom "+ srever.ip;  #--->如果大于0就命中缓存,并在varnish构建响应报文时附加上自己(服务器)的IP地址.
   } else {
   set resp.http.X-Cache = "MISS";   #--->否则就未命中
   }
}
###############################################
#注意:varnish2版本中和3版本中的配置语法有差异!
(2)从新编译使用(略)
(3)测试:
第一次
spacer.gifwKiom1NvXkDQx1SgAAAf9WdLKjQ583.jpg
第二次,第N次
spacer.gifwKioL1NvXhXC2uVNAABMdnWopPQ750.jpg
这样,在客户端就可以看出来缓存命中的是哪个节点了.


3.有两个网页文件,一个是index.html.一个是private.html.期望客户端访问第一个网页时是通过缓存去取得内容,

但是private.html属于敏感信息,不期望它存入缓存中.如何做?
(1)在后端服务器添加一个private.html文件.
[root@web1 ~]# echo "<h1>privarte</h1>" >/var/www/html/private.html
(2)修改test.vcl;在backend这个域下方添加:
sub vcl_recv {
   if (req.url ~ "private.html") {   #--->通过请求的url进行匹配判断.
     return(pass);
   }
     return(lookup);
}
(3)重新编译.应用<略>
(4)测试:
第一次访问index.html.结果为未命中,第2次开始访问命中缓存
第一次访问private.html,未命中,第2次,第N次都不会命中.
    如果不能静态化的网页文件,通通都pass,因为动态内容没有必要存入缓存中.


4.如何根据请求内容进行缓存时长的设定?

(1)vcl配置文件中添加以下内容:
sub vcl_fetch {
   if (req.url ~ "\.(jpg|jpeg|gif|png)$") {   #--->如果请求的内容是图片,则缓存7200s
    set bereps.ttl = 7200s;
   }
   if (req.url ~ "\.(html|css|js)$") {    #---->如果请求的是html等文档,则缓存1200s
    set bereps.ttl = 1200s;
   }
}
(3)在后端服务器中添加一张图片
(4)重新编译.应用<略>
(5)测试:
    通过测试的确两种网页内容的缓存时间在规定时间内是有效的.


5.如果后端服务器更新了内容,而varnish设定的缓存时间过长,那么就需要手动清理缓存

    purge用于清理缓存中的某特定对象及其变种(variants),因此,在有着明确要修剪的缓存对象时可以使用此种方式。HTTP协议的PURGE方法可以实现purge功能,不过,其仅能用于vcl_hit和vcl_miss中,它会释放内存工作并移除指定缓存对象的所有Vary:-变种,并等待下一个针对此内容的客户端请求到达时刷新此内容。另外,其一般要与return(restart)一起使用。下面是个在VCL中配置的示例。
(1) ok.为了更清晰配置文件.我这里全部贴出来:
backend default {
  .host = "192.168.100.51";
  .port = "80";
}
acl purgers {     #---->对于修剪缓存对象时的访问控制;"purgers"是自定义的名字,用于后面调用;
    "127.0.0.1";
    "172.16.0.0"/16;
}
sub vcl_recv {   #---->在刚进入varnish时
   if (req.request == "PURGE") {     #---->如果请求中包含了PURGE这个方法
      if (!client.ip ~ purgers){     #---->在进行判断客户端的IP是否属于访问控制中定义的,这里是取反.
        error 405 "Method not allowed";
      }
   }
   if (req.url ~ "private.html") {
     return(pass);
   }
     return(lookup);
}
sub vcl_hit {  #---->如果请求的内容命中了,就可执行修剪操作.
   if (req.request == "PURGE"){
   purge;
   error 200 "Purge Finished"; #---->构建错误响应报文(这里虽然用的是error来定义的,但是给来code)通知完成修剪
  }
}
sub vcl_miss {   #---->如果未命中缓存,即使请求首部包含了PURGE这个方法,也没有多大的意义.
   if (req.request == "PURGE"){
   purge;
   error 404 "Not in  cache";  #---->构建错误响应报文相应客户端.
  }
}
sub vcl_pass {   #---->如果请求被发往pass那就更没有必要去做这个操作了.
   if (req.request == "PURGE"){
   error 502 "Purged on a passed object";
  }
}
sub vcl_fetch {
   if (req.url ~ "\.(jpg|jpeg|gif|png)$") {
    set beresp.ttl = 60s;
   }
   if (req.url ~ "\.(html|css|js)$") {
    set beresp.ttl = 1200s;
   }
}
sub vcl_deliver {
   if (obj.hits > 0){
   set resp.http.X-Cache = "HIT from " + server.ip;
   } else {
   set resp.http.X-Cache = "MISS";
   }
}
(2)重新编译.应用<略>
(3)测试:<这里使用Linux命令行工具去测试>
[root@client ~]# curl -I http://172.16.41.1/index.html
    多次使用该命令去访问后,内容都是来自于缓存中的,此刻期望将缓存清除:
[root@client ~]# curl -X PURGE http://172.16.41.1/index.html  #---->在请求首部直接附加方法为"PURGE"
<?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 Purge Finished</title>
  </head>
  <body>
    <h1>Error 200 Purge Finished</h1>
    <p>Purge Finished</p>
    <h3>Guru Meditation:</h3>
    <p>XID: 1582592688</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
[root@varnish ~]#
再次用[root@client ~]# curl -I http://172.16.41.1/index.html
去访问
[root@client ~]# curl -I http://172.16.41.1/index.html
HTTP/1.1 200 OK
Server: Apache/2.2.15 (CentOS)
Last-Modified: Sun, 27 Apr 2014 11:15:23 GMT
ETag: "120567-e-4f80450add2ae"
Content-Type: text/html; charset=UTF-8
Content-Length: 14
Accept-Ranges: bytes
Date: Mon, 05 May 2014 21:51:17 GMT
X-Varnish: 1582592689
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Cache:MISS  #---->缓存已清除..
[root@varnish ~]#
后面进行了下面的操作
#---->清除一个缓存过期的资源
[root@varnish ~]# curl -X PURGE http://172.16.41.1/test.jpg
........
........
    <h1>Error 404 Not in  cache</h1>
........
........
[root@varnish ~]#
#---->清除一个根本就不会存在于缓存中的内容:
[root@varnish ~]# curl -X PURGE http://172.16.41.1/private.html
........
........
    <h1>Error 502 Purged on a passed object</h1>
........
........
[root@varnish ~]#
#---->关于没有权限执行的操作(略)

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

每个后端服务器当前探测的健康状态探测方法通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的Backend_health查看或varnishadm的debug.health查看。
backend web1 {
     .host = "www.magedu.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) .initial:Varnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold;
(6) .expected_response:期望后端主机响应的状态码,默认为200;
(7) .interval:探测请求的发送周期,默认为5秒;
(8) .timeout:每次探测请求的过期时长,默认为2秒;

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

如果Varnish在某时刻没有任何可用的后端主机,它将尝试使用缓存对象的“宽容副本”(graced copy),当然,此时VCL中的各种规则依然有效。因此,更好的办法是在VCL规则中判断req.backend.healthy变量显示某后端主机不可用时,为此后端主机增大req.grace变量的值以设定适用的宽容期限长度。
后边一起演示。


7.如何实现Varnish使用多台后端主机

(1)基本理论:
Varnish中可以使用director指令将一个或多个近似的后端主机定义为一个逻辑组,并可以指定的调度方式(也叫挑选方法)来轮流将请求发送至这些主机上。不同的director可以使用同一个后端主机,而某director也可以使用“匿名”后端主机(在director中直接进行定义)。每个director都必须有其专用名,且在定义后必须在VCL中进行调用,VCL中任何可以指定后端主机的位置均可以按需将其替换为调用某已定义的director。
#示例
backend web1 {
     .host = "backweb1.maoqiu.com";
     .port = "80";
}
director webservers random {
  .retries = 5;
  {
    .backend = web1;
    .weight  = 2;
  }
  {
    .backend  = {
      .host = "backweb2.maoqiu.com";
       .port = "80";
    }
       .weight = 3;
  }
}

如上示例中,web1为显式定义的后端主机,而webservers这个directors还包含了一个“匿名”后端主机(backweb2.magedu.com)。webservers从这两个后端主机中挑选一个主机的方法为random,即以随机方式挑选。

    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默认为client.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.
}
(2)实现方式:
<1>添加我们的第2台web server.并给出测试页面文件
[root@web2 ~]# echo "<h1>web2</h1>" > /var/www/html/index.html
[root@web2 ~]# service httpd start
Starting httpd:                                [  OK  ]
[root@web2 ~]#
<2>修改配置文件如下:
backend web1 {    #---->定义的后端web1
  .host = "192.168.100.51";
  .port = "80";
  .probe = {#---->定义健康状态检测
     .url = "/index.html";
     .window = 5;
     .threshold = 2;
     .interval = 3s;
}
}
backend web2 {  #---->定义的后端web2
  .host = "192.168.100.52";
  .port = "80";
  .probe = {   #---->定义健康状态检测
     .url = "/index.html";
     .window = 5;
     .threshold = 2;
     .interval = 3s;
}
}
director websrvs random {  #---->后端主机定义好后可以定义到一个directory中,类似于nginx中的upstream.
  { .backend = web1;
     weight = 2;  #---->权重
  }
  { .backend = web2;
     weight = 1;#---->权重
  }
}
#---->定义好后端后,要应用这个director.在sub vcl_recv中添加以下内容:
set req.backend = websrvs;
(3)测试:
    如果第一次访问到了web1那么后续的访问都还是web1,因为一直都命中了缓存,如果将缓存清除了,很有可能访问到web2,如果停掉了后端某个节点的服务后也有可能回访问到另外一个节点,应为在上面定义了它回到后端服务器中任意挑选主机获得资源并且缓存下来后响应客户端!
(4)如何配置根据请求内容的不同,varnish到后端获取不同的数据到不同的后端去获取?
    <1>配置如下:
#---->两个后端web服务的定义
backend web1 {
  .host = "192.168.100.51";
  .port = "80";
  .probe = {
     .url = "/index.html";
     .window = 5;
     .threshold = 2;
     .interval = 3s;
}
}
backend web2 {
  .host = "192.168.100.52";
  .port = "80";
  .probe = {
     .url = "/index.html";
     .window = 5;
     .threshold = 2;
     .interval = 3s;
}
}
#---->对于刚进入varnish的请求做判断:
sub vcl_recv {
   if (req.url ~ "\.(html|css|js)$") {   #---->如果请求的 url中包含了这些内容,如果在没有缓存的情况下,就转到后端web1
   set req.backend = web1;
   } else {    #---->否则就转到web2
   set req.backend = web2;
   }
    根据测试:当varnish中没有缓存的情况下,访问的不同内容都调到了后端不同的服务器上.
    这样的设定根据不同内容选择不同的服务器组


8.如何在后端服务器上查看真实客户端请求IP地址

#修改配置文件:
[root@varnish ~]# vim /etc/varnish/test.vcl
sub vcl_recv {
  if (req.http.X-Forward-For) {
     set req.http.X-Forward-For = req.http.X-Forward-For + "," + client.ip ;
   } else {
        set req.http.X-Forward-For = client.ip;
   }
#修改后端服务器日志记录格式:
[root@web2 ~]# vim /etc/httpd/conf/httpd.conf
LogFormat "%{X-Forward-For}i%l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

9.利用Varnish实现图片防盗链
if (req.http.referer ~ "http://.*") {
         if (  !(req.http.referer ~ "http://.*ixdba\.net"
             || req.http.referer ~ "http://.*google\.com"
             || req.http.referer ~ "http://.*yahoo\.cn"et
             || req.http.referer ~ "http://.*google\.cn"
             )) {
                set req.http.host = "www.maoqiuonline.net";
                 set req.url = "/templets/default/images/logo.gif";
         }
                       return (lookup);
         }

   在这段配置中,用了一个内置变量req.http.referer,防盗链就是通过referer来实现的。其实,referer是http header的一部分,当浏览器向Web服务器发送请求的时候,一般会带上一个referer标识,用来告诉服务器请求是从哪个页面链接过来的服务器根据这个标识就可以获取信息来源,进而进行相应的处理。

   这段配置的含义为:Varnish服务器对接收或发送的请求进行判断,如果referer标识存在,且referer标识不匹配下面域名列表中的任意一个,就将请求重定向到www.ixdba.net域名下的/templets/default/images/logo.gif图片,而对找到匹配域名的请求执行lookup操作。


10.varnish常用管理命令:

varnishstat中应该关注几个参数:
client_conn
client_req
cache_hit
cache_miss
backend_conn
n_wrk
n_wrk_create
n_backend
n_expired
n_lru_moved
s_hdrbytes
s_bodybytes

命令率:
文档命中率
字节命中率

命中率低的原因?
缓存空间太小
不存在明显的热点数据
缓存服务器的可用性
源文件更新过于频繁

varnishstat
-1:
-l: 列出所有参数;
-f f1,f2,...: 仅列出指定的参数;

varnishtop:实时显示日志中的信息
-i tag: 仅显示指定的tag,如RxHeader
-I regexp: 以模式匹配tag对应值;
-C:正则表达式匹配时不区分字符大小写;

varnishreplay: 日志重放工具,用于实现缓存预热;