一、前言

   首先来简单认识一下什么是varnish?它能实现什么样的功能?.......

   Varnish是一款高性能的开源HTTP加速器,常见的缓存服务开源解决方案有varnish、nginx、squid 、ats等,Varnish 的作者Poul-Henning Kamp是FreeBSD的内核开 发者之一,varnish项目是2006年发布的第一个版本0.9.距今已经有十年了,此文档之前也提过varnish还不稳定,那是2007年时候编写的,经过varnish开发团队和网友们的辛苦耕耘,现在的varnish已经很健壮。很多门户网站已经部署了varnish,并且反应都很好,甚至反应比squid还稳定, 且效率更高,资源占用更少。挪威最大的在线报纸 Verdens Gang 使用3台Varnish代替了原来的12台Squid,性能比以前更好。相信在反向代理,web加速方 面,varnish已经有足够能力代替squid。


二、varnish有哪些特性

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

   2、Varnish访问速度更快,因为采用了“Page Cache”技术,所有缓存数据都直接从内存读取(映射),而squid是从硬盘读取,因而Varnish在访问速度方面会更快; 

   3、Varnish可以支持更多的并发连接,因为Varnish的TCP连接释放要比Squid快,因而在高并发连接情况下可以支持更多TCP连接; 

   4、Varnish可以通过管理端口,使用正则表达式批量的清除部分缓存,而Squid是做不到的; 

   5、squid属于是单进程使用单核CPU,但Varnish是通过fork形式打开多进程来做处理, 所以可以合理的使用所有核来处理相应的请求。


三、Varnish的安装方式

   Varnish的安装非常简单,常用的两种方式有yum安装和源码包安装,下面一源码安装来简单介绍:
   1、安装前的准备
   获取varnish软件
   Varnish的官方站点为
http://varnish-cache.org,这里面有varnish的最新说明文档,以及版本升级记录,从此站点可以找到varnish在SourceForge的下载链接,目前,varnish的最新版本是Varnish 2.1.2,下载完成后的包名为varnish-2.1.2.tar.gz,此处我们就以此版本为例,进行安装配置。

   2、建立varnish用户以及用户组,并且创建Varnish缓存目录和日志目录:
[root@varnish-server ~]#useradd  -s /sbin/nologin varnish
[root@varnish-server ~]#mkdir /data/varnish/cache
[root@varnish-server ~]#mkdir /data/varnish/log
[root@varnish-server ~]#chown -R varnish:varnish  /data/varnish/cache
[root@varnish-server ~]#chown -R varnish:varnish  /data/varnish/log
   3、安装varnish(在此之前最好安装yum包组和pcre-devel、openssl-devel,否则编译时易出错)
   这里我们将varnish安装编译到/usr/local/目录下,操作如下:
[root@varnish-server ~]#tar -zxvf varnish-2.1.2.tar.gz
[root@varnish-server ~]#cd varnish-2.1.2
[root@varnish-server ~]#./configure --prefix=/usr/local/varnish \
 >--enable-dependency-trackin 
>--enable-debugging-symbols 
>--enable-developer-warnings 
[root@varnish-server ~]#make
[root@varnish-server ~]#make install
   至此,varnish安装完毕。


四、配置Varnish

   VCL使用说明
   VCL,即为Varnish Configuation Language,用来定义varnish的存取策略,VCL语法比较简单,跟C和perl比较相似,可以使用指定运算符“=”,比较运算符“==”,逻辑运算符“!,&&,!!”等形式。还支持正则表达样和用“~”进行ACL匹配运算,同时还可以使用“set”这样的关键字来指定变量。
需要注意的是,“\”字符在VCL里没有特别的含义,这点与其它语言略有不同,另外,VCL只是配置,并不是真正的编程语言,没有循环,也没有自定义变量。 
   在讲述Varnish配置之前,首先需要了解下varnish的配置语法,即VCL,下面对VCL常用的一些内置函数和公用变量进行详细介绍。


五、VCL状态

   1、VCL有多个状态引擎,状态之间存在相关性,但状态引擎彼此间互相隔离;每个状 态引擎可使用return(x)指明关联至哪个下一级引擎;每个状态引擎对应于vcl文件中的一个配置段   

   vcl_recv:接受用户请求进varnish的入口的引擎,接受到结果之后,利用 return(lookup),将请求转交给vcl_hash引擎进行处理 

   vcl_hash:接受到用户请求后,对用户请求的URL进行hash计算,根据请求的首 部信息,以及hash结果进行下一步处理的引擎 

   vcl_hit:经过vcl_hash引擎处理后,发现用户请求的资源本地有缓存,则 vcl_hash引擎通过return(hit)将请求交给vcl_hit引擎进行处理,vcl_hit引擎处理后 将请求交给vcl_deliver引擎,vcl_deliver引擎构建响应报文,响应给用户 

   vcl_miss:经过vcl_hash引擎处理后,发现用户请求的资源本地没有缓存,则 vcl_hash引擎通过return(miss)将请求交给vcl_miss引擎进行处理

   vcl_purge:经过vcl_hash引擎处理后,发现请求是对缓存的内容进行修剪时,则通过 return(purge)交给vcl_purge引擎进行处理,vcl_purge引擎处理后,利用vcl_synth引擎将处 理的结果告知给用户 

   vcl_pipe:经过vcl_hash引擎处理后,发现用户请求的报文varnish无法理解,则通过 return(pipe),将请求交给vcl_pipe引擎,pipe引擎直接将请求交给后端真实服务器 

   vcl_pass:当请求经过vcl_hash处理后,发现请求报文不让从缓存中进行响应或其他原因没办 法查询缓存,则由return(pass)或return(hit-for-pass)交由vcl_pass引擎进行处理 

   vcl_backend_fetch:当发现缓存未命中或由vcl_pass传递过来的某些不能查询缓存的请求, 交由vcl_backend_fetch引擎处理,vcl_backend_fetch引擎会向后端真实web服务器发送请 求报文,请求对应的资源 

   vcl_backend_response:当后端发送响应报文到varnish后,会由vcl_backend_resonse引 擎进行处理,如:判断响应的内容是否可缓存,如果能缓存,则缓存下来后,交给vcl_deliver 引擎,如果不能缓存,则直接交给vcl_deliver引擎,vcl_deliver引擎构建响应报文给客户端

    varnish4.0版本的两个特殊的引擎 

    vcl_init:在处理任何请求之前要执行的vcl的代码,主要用于初始化VMOD,可 用在后端主机有多台时,借助此引擎完成多台主机的负载均衡效果 

    vcl_fini:所有的请求都已经结束,在vcl配置被丢弃时调用;主要用于清理VMOD

   2、VCL处理流程图
   通过上面对VCL的介绍,读者对各个函数实现的功能已经有了一个了解,其实每个函数之间都是相互关联的,下图列出了varnish处理HTTP请求的一个运行流程图。

0d6b36c4807af2aa5e90bf0df1347236.png

VCL处理流程图

   处理过程大致分为如下几个步骤:
(1)Receive状态,也就是请求处理的入口状态,根据VCL规则判断该请求应该是Pass或Pipe,或者进入Lookup(本地查询)。
(2)Lookup状态,进入此状态后,会在hash表中查找数据,若找到,则进入Hit状态,否则进入miss状态。
(3)Pass状态,在此状态下,会进入后端请求,即进入fetch状态。 
(4)Fetch状态,在Fetch状态下,对请求进行后端的获取,发送请求,获得数据,并进行本地的存储。
(5)Deliver状态, 将获取到的数据发送给客户端,然后完成本次请求。

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


当请求到达后,可以使用的公用变量如表1所示:
表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         表示请求重启的次数,默认最大值为4


Varnish               在向后端主机请求时,可以使用的公用变量如表2所示:

表2
公用变量名称 含义
beresp.request 指定请求的类型,例如GET、HEAD等
beresp.url 指定请求的地址
beresp .proto 表示客户端发起请求的HTTP协议版本
beresp .http.header 表示对应请求中的http头部信息
beresp .ttl 表示缓存的生存周期,也就是cache保留多长时间,单位是秒


从cache或者后端主机获取内容后,可以使用的公用变量如表3所示:
表3
公用变量名称 含义
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所示:
表4
公用变量名称 含义
resp.status 表示返回给客户端的HTTP状态代码
resp.proto 表示返回给客户端的HTTP协议版本
resp.http.header 表示返回给客户端的HTTP头部信息
resp.response 表示返回给客户端的HTTP状态信息
在上面的讲述中,我们只是介绍了常用的VCL内置公用变量,如果需要了解和使用更多的公用变量信息,请登录varnish官方网站查阅。


六、Varnish 的 Storage 方式可分为两种:

  (1)Malloc 通过 malloc 获取内存;

  (2)Mmap file 创建大文件,通过二分法分段映射成 1G 以内的大块。


   以 Mmap file 的缓存方式启动 I/O 也会形成瓶颈,原因主要是 Varnish 缓存的数据先会刷到磁盘上,然后在一次性读到内存中,这在访问量大的时候同时也会对 I/O 造成很大的压力。Malloc 缓存方式虽然对 I/O 没有压力,因所有缓存数据都写到内存中。


七、配置一个简单的Varnish实例
   由于版本的不同,Varnish配置文件的写法也存在一定差异,这里讲述的版本是基于centos 7.3版本的varnish4.0,但是该配置文件一定要以非注释行vcl 4.0开始,否则服务将不能识别。

   Varnish安装完成后,默认的配置文件为/usr/local/varnish/etc/varnish/default.vcl,配置完成的default.vcl文件如下:

vcl 4.0;
##############启用负载均衡模块###############
import directors;
##############配置健康状态探测##############
probe backend_healthcheck {
    .url = "/index.html";
    .window = 5;      #窗口
    .threshold = 2;   #门槛
    .interval = 3s;
    .timeout  = 1s;
}

#############添加后端主机################
backend web1 { 
    .host = "172.17.254.17";
    .port = "80";
    .probe = backend_healthcheck;
}

backend img1 {
    .host = "172.17.254.107";
    .port = "80";
    .probe = backend_healthcheck;
}


#############定义负载均衡及算法###############
sub vcl_init {
    new web_cluster = directors.random();
    web_cluster.add_backend(web1,10);
    new img_cluster = directors.round_robin();
    img_cluster.add_backend(img1);
}
################定义Purge-ACL控制#######################
acl purgers {    # 定义可访问来源IP
        "127.0.0.1";
        "172.17.0.0"/16;
}

################定义vcl_recv函数段######################
sub vcl_recv {
    if (req.method == "GET" && req.http.cookie) {  
        return(hash);
}
#####如果请求不是GET或者HEAD,不缓存######
if(req.method!="GET"&&req.method!="HEAD"){
return(pass);
}
#####不正常的请求不缓存#####
   if (req.method != "GET" &&
        req.method != "HEAD" &&
        req.method != "PUT" &&
        req.method != "POST" &&
        req.method != "TRACE" &&
        req.method != "OPTIONS" &&
        req.method != "PURGE" &&
        req.method != "DELETE") {
         return (pipe);
   }
   
    if (req.url ~ "index.php") {   
        return(pass);
    }
    if (req.method == "PURGE") {    # PURGE请求的处理
        if (client.ip ~ purgers) {
          #  return(synth(405,"Method not allowed"));
          return(purge);
        }
    }
##为发往后端主机的请求添加X-Forward-For首部##
    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;
    }
#####实现动静分离#####
set req.http.X-Forward-For=client.ip;
        if(req.url~"(?i)\.(jpeg|jpg|png|jif)($|\?)"){
                set req.backend_hint=img_cluster.backend();
}   


####################定义vcl_hash函数段#################
    if (req.http.host ~ "(?i)^(www.)?aaa.com$") {    # 根据不同的访问域名,分发至不同的后端主机组
        set req.backend_hint = web_cluster.backend();
      } elsif (req.http.host ~ "(?i)^images.aaa.com$") {
            set req.backend_hint = img_cluster.backend();
      }
        return(hash);
}


sub vcl_hash {
     hash_data(req.url);
}

##############设置资源缓存时长#################
sub vcl_backend_response { # 自定义缓存文件的缓存时长,即TTL值
    if (bereq.url ~ "\.(jpg|jpeg|gif|png)$") {
        set beresp.ttl = 10s;
    }
    if (bereq.url ~ "\.(html|css|js)$") {
        set beresp.ttl = 1200s;
    }
#    if (beresp.http.Set-Cookie) { # 定义带Set-Cookie首部的后端响应不缓存,直接返回给客户端
     set beresp.grace = 30m;
        return(deliver);
#    }
}
sub vcl_deliver {
    if (obj.hits > 0) {    # 为响应添加X-Cache首部,显示缓存是否命中
        set resp.http.X-Cache = "HIT from " + server.ip;
    } else {
        set resp.http.X-Cache = "MISS";
    }
        unset resp.http.X-Powered-By;
        unset resp.http.Via;
}

在经过以上的配置之后会出现一下测试界面:

464c9dc6177301679dc868e5a8af19a7.jpg

    以上只是个人见解,有什么错误还望各位大神前来指教哦j_0058.gif