Varnish代理缓存详解

时间:2022-10-21 19:40:08

Varnish理论知识


一、Varnish 简介

Varnish 是一款高性能且开源的反向代理服务器和 HTTP 加速器,其采用全新的软件体系机构,和现在的硬件体系紧密配合,与传统的 squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点,很多大型的网站都开始尝试使用 varnish 来替换 squid,这些都促进 varnish 迅速发展起来。

挪威的最大的在线报纸 Verdens Gang(vg.no) 使用 3 台 Varnish 代替了原来的 12 台 Squid,性能比以前更好,这是 Varnish 最成功的应用案例。目前,Varnish可以在FreeBSD6.0/7.0、solaris、 Linux2.6内核上运行。

二、Varnish与Squid的对比

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

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


四、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:表示进入pipe模式,把请求控制权交给vcl_pipe函数。
(3)vcl_hit函数
在执行lookup指令后,如果在缓存中找到请求的内容,将自动调用该函数。
此函数一般以如下几个关键字结束:
deliver:表示将找到的内容发送给客户端,并把控制权交给函数vcl_deliver。
error code [reason]
pass
(4)vcl_miss函数
在执行lookup指令后,如果没有在缓存中找到请求的内容时自动调用该方法,此函数可以用于判断是否需要从后端服务器取内容。
此函数一般以如下几个关键字结束:
fetch:表示从后端获取请求的内容,并把控制权交给vcl_fetch函数。
error code [reason]
pass

(5)vcl_pass函数
此函数在进入pass模式时被调用,用于将请求直接传递至后端主机,后端主机应答数据后送给客户端,但不进行任何缓存,在当前连接下每次都返回最新的内容。
此函数一般以如下几个关键字结束:
error code [reason]
pass
lookup表示在缓存里查找被请求的对象,并且根据查找的结果把控制权交给函数vcl_hit或者函数vcl_miss。
(6)vcl_fetch函数
在从后端主机更新缓存并且获取内容后调用该方法,接着,通过判断获取的内容来决定是否将内容放入缓存,还是直接返回给客户端。
此函数一般以如下几个关键字结束:
error code [reason]
pass
deliver
(7)vcl_deliver函数
在缓存中找到请求的内容后,发送给客户端前调用此方法。此函数一般以如下几个关键字结束:
error code [reason]
deliver
(8)vcl_timeout 函数
此函数在缓存内容到期前调用。一般以如下几个关键字结束:
discard:表示从缓存中清除该内容。
fetch
(9)vcl_discard函数
在缓存内容到期后或缓存空间不够时,自动调用该方法,一般以如下几个关键字结束:
keep:表示将内容继续保留在缓存中。
discard

看到这么多的函数是不是有点头晕呢。。。其实它们彼此之间是有关系的,下面我们看一下它们的流程图。。

五、VCL处理流程图

Varnish代理缓存详解


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

六、内置公用变量
VCL内置的公用变量可以用在不同的VCL函数中,根据这些公用变量使用的不同阶段,下面依次介绍。
当请求到达后,可以使用的公用变量如表2所示:
表2
公用变量名称 含义
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 在向后端主机请求时,可以使用的公用变量如表3所示:
表3
公用变量名称 含义
beresp.request 指定请求的类型,例如GET、HEAD等
beresp.url 指定请求的地址
beresp .proto 表示客户端发起请求的HTTP协议版本
beresp .http.header 表示对应请求中的http头部信息
beresp .ttl 表示缓存的生存周期,也就是cache保留多长时间,单位是秒
从cache或者后端主机获取内容后,可以使用的公用变量如表4所示:
表4
公用变量名称 含义
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 表示返回上一次请求到现在的间隔时间,单位是秒

对客户端应答时,可以使用的公用变量如表5所示:
表5
公用变量名称 含义
resp.status 表示返回给客户端的HTTP状态代码
resp.proto 表示返回给客户端的HTTP协议版本
resp.http.header 表示返回给客户端的HTTP头部信息
resp.response 表示返回给客户端的HTTP状态信息
以上变量只是varnish的常用变量,如需更多变量请访问Varnish官网

七、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的director支持的挑选方法中比较简单的有round-robinrandom两种。其中,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,当其倾向于使用后端主机不可用时将会重新挑选新的后端其机。

Varnish配置实战


一、Varnish的安装与配置

1、Varnish安装(rpm包软件)

rpm --nosignature -i http://repo.varnish-cache.org/redhat/varnish-3.0/el6/noarch/varnish-release-3.0-1.el6.noarch.rpmyum install varnish  varnish-libs-devel -y

如有安装疑惑请访问varnish官网https://www.varnish-cache.org/installation/redha

2、Varnish目录结构

/etc/sysconfig/varnish    #存放Varnish运行参数设置/etc/varnish/             #存放Varnish VCL配置文件

3、Varnish配置基本实例

3.1、修改 /etc/sysconfig/varnish

vim /etc/sysconfig/varnish#VARNISH_VCL_CONF=/etc/varnish/default.vclVARNISH_VCL_CONF=/etc/varnish/test.vcl           #调用test.vcl文件VARNISH_LISTEN_PORT=80                           #改为80端口VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1           #监听地址VARNISH_ADMIN_LISTEN_PORT=6082                   #管理端口VARNISH_SECRET_FILE=/etc/varnish/secret          #密钥文件

3.2、提供/etc/varnish/test.vcl

cd /etc/varnish/cp default.vcl test.vcl    #复制默认配置文件

3.3、配置/etc/varnish/test.vcl

############定义健康状态检测###############probe healthcheck {    .url = "/";                    #定义健康检查的页面    .interval = 6s;                #探测请求的发送周期,默认为5秒;    .timeout = 0.3 s;              #每次探测请求的过期时长    .window = 8;                #设定在判定后端主机健康状态时基于最近多少次的探测进行    .threshold = 3;               #在.window中指定的次数中,至少有多少次是成功的才判定后端主机正健康运行    .initial = 3;                  #Varnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold;}############定义两组服务器##############backend web1 {  .host = "172.16.10.7";           #服务器主机  .port = "80";                   #服务器端口  .probe = healthcheck;           #健康状态检测}backend web2 {  .host = "172.16.10.3";  .port = "80";  .probe = healthcheck;}backend app1 {  .host = "172.16.10.7";  .port = "8080";  .probe = healthcheck;}backend app2 {  .host = "172.16.10.3";  .port = "8080";  .probe = healthcheck;}############定义集群,调用服务器##############director webserver  random {     #定义一个名为webserver的directory,由web1,和web2分但请求,使用random算法,处理静态请求    {.backend = web1;          .weight  = 2;               #设置权重为2    }    {.backend = web2;     .weight = 5;    }}director appserver  random {     #定义一个名为appserver的directory,由app1,和app2分但请求,使用random算法,处理动态请求        {.backend = app1;     .weight = 2;}        {.backend = app2;     .weight = 5;}}############定义Acl##############acl purgers {                         #设置清理缓存的IP    "127.0.0.1";    "172.16.10.0"/16;}############vcl_recv函数段##############sub vcl_recv {#############使得后端服务能记录访问者的真实IP############if (req.http.x-forwarded-for) {                             #添加首部信息           set req.http.X-Forwarded-For =                          req.http.X-Forwarded-For + ", " + client.ip;                  } else {                      set req.http.X-Forwarded-For = client.ip;                 }#############配置动静分离#####################             if (req.url ~ "\.php$"){    set req.backend = appserver;                            #php结尾的交给appserver服务器组处理,否则交给webserver服务器组处理}else{    set req.backend = webserver;}                       #############不正常的访问不缓存############  if (req.request != "GET" &&       req.request != "HEAD" &&              req.request != "PUT" &&              req.request != "POST" &&              req.request != "TRACE" &&              req.request != "OPTIONS" &&              req.request != "DELETE") {                /* Non-RFC2616 or CONNECT which is weird. */                return (pipe);            }            if (req.request != "GET" && req.request != "HEAD") {                /* We only deal with GET and HEAD by default */                return (pass);            }#############不缓存认证信息和Cookie############            if (req.http.Authorization || req.http.Cookie) {                /* Not cacheable by default */                return (pass);            }                                      #############定义清楚缓存IP,调用上面的Acl############    if (req.request == "PURGE"){                  #使用PURGE命令清除缓存           if(!client.ip ~ purgers){              #非ACl定义的IP,则不能清除缓存                  error 405 "Method not allowed";    }     return (lookup);    }############支持压缩功能###################if (req.http.Accept-Encoding) {                  if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {# No point in compressing theseremove req.http.Accept-Encoding;} else if (req.http.Accept-Encoding ~ "gzip") {             set req.http.Accept-Encoding = "gzip";} else if (req.http.Accept-Encoding ~ "deflate") {             set req.http.Accept-Encoding = "deflate";} else {             remove req.http.Accept-Encoding;}        } }############vcl_hit函数段##############sub vcl_hit {    if (req.request == "PURGE"){                  #请求方法是PURGE,这清理缓存        purge;        error 200 "Purged";}}############vcl_miss函数段##############sub vcl_miss {    if (req.request == "PURGE"){          purge;        error 404 "Not in cache";}}############vcl_pass函数段##############sub vcl_pass {    if (req.request == "PURGE"){        error 502 "PURGE on a passed object";}}############vcl_fetch函数段##############sub vcl_fetch{#############定义缓存时长############    if (req.request == "GET" && req.url ~ "\.html$"){    set beresp.ttl = 300s;                         #超时时长为300秒  if (req.request == "GET" && req.url ~ "\.(png|xsl|xml|pdf|ppt|doc|docx|chm|rar|zip|bmp|jpeg|swf|ico|mp3|mp4|rmvb|ogg|mov|avi|wmv|swf|txt|png|gif|jpg|css|js|html|htm)$") {       set beresp.ttl = 600s;    }    return (deliver);}}############vcl_deliver函数段##############sub vcl_deliver {#########定义Header标识,判断缓存是否命中#########    if (obj.hits > 0) {     set resp.http.X-Cache = "HIT";                 #命中则返回HIT   } else {     set resp.http.X-Cache = "MISS";                #未命中则返回MISS}}

3、4启动Varnish服务

/etc/init.d/varnish start

4、访问测试(查看是否命中)

Varnish代理缓存详解

缓存命中率的高低,直接反映Varnish的运行状态,以下通过varnishstat命令查看状态信息

[root@nginx2 varnish]# varnishstatitrate ratio:        1        1        1Hitrate avg:     0.6771   0.6771   0.6771         368         0.00         0.01 client_conn - Client connections accepted         824         0.00         0.02 client_req - Client requests received         369         0.00         0.01 cache_hit - Cache hits         176         0.00         0.01 cache_miss - Cache misses          95         0.00         0.00 backend_conn - Backend conn. success          83         0.00         0.00 backend_unhealthy - Backend conn. not attempted          33         0.00         0.00 backend_fail - Backend conn. failures         260         0.00         0.01 backend_reuse - Backend conn. reuses          51         0.00         0.00 backend_toolate - Backend conn. was closed         315         0.00         0.01 backend_recycle - Backend conn. recycles         222         0.00         0.01 fetch_length - Fetch with Length          20         0.00         0.00 fetch_chunked - Fetch chunked          96         0.00         0.00 fetch_304 - Fetch no body (304)          16          .            .   n_sess_mem - N struct sess_mem           3          .            .   n_objectcore - N struct objectcore           3          .            .   n_objecthead - N struct objecthead           4          .            .   n_waitinglist - N struct waitinglist           4          .            .   n_vbc - N struct vbc         100          .            .   n_wrk - N worker threads         100         0.00         0.00 n_wrk_create - N worker threads created           7          .            .   n_backend - N backends          86          .            .   n_expired - N expired objects         130          .            .   n_lru_moved - N LRU moved objects         453         0.00         0.01 n_objwrite - Objects sent with write         368         0.00         0.01 s_sess - Total Sessions         824         0.00         0.02 s_req - Total Requests          17         0.00         0.00 s_pipe - Total pipe         252         0.00         0.01 s_pass - Total pass         338         0.00         0.01 s_fetch - Total fetch      221522         0.00         6.37 s_hdrbytes - Total header bytes


注释:

  • Client connections accepted:表示客户端向反向代理服务器成功发送HTTP请求的总数量

  • Client requests received: 表示到现在为止,浏览器向反向代理服务器发送HTTP请求的累积次数,由于可能会使用长连接,所以这个值一般会大 于“Client connections accepted”。

  • Cache hits:表示反向代理服务器在缓存区中查找并且命中缓存的次数。

  • Cache misses:表示直接访问后端主机的请求数量,也就是非命中数。

  • N struct object:表示当前被缓存的数量。

  • N expired objects:表示过期的缓存内容数量。

  • N LRU moved objects:表示被淘汰的缓存内容个数


二、Varnish 管理

1、手动清除缓存

[root@nginx2 varnish]# curl -X PURGE http://172.16.10.2/index.html<?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: 1003529630</p>    <hr>    <p>Varnish cache server</p>  </body></html>

2、查看varnish日志

[root@nginx2 varnish]# varnishlog    0 Backend_health - app Still healthy 4--X-RH 8 3 8 0.011716 0.014211 HTTP/1.1 200 OK    0 Backend_health - app1 Still healthy 4--X-RH 8 3 8 0.016078 0.014856 HTTP/1.1 200 OK

3、 varnishadm 命令

[root@nginx2 varnish]# varnishadmvarnish> help200  help [command]ping [timestamp]auth responsequitbannerstatus                                        #查看状态start                                         #启动varnishstop                                          #关闭varnishvcl.load <configname> <filename>              #动态加载vclvcl.inline <configname> <quoted_VCLstring>vcl.use <configname>                          #动态使用vclvcl.discard <configname>vcl.list                                      #查看使用的vcl                   vcl.show <configname>                         #查看vcl的配置param.show [-l] [<param>]param.set <param> <value>panic.showpanic.clearstorage.listbackend.listbackend.set_health matcher stateban.url <regexp>ban <field> <operator> <arg> [&& <field> <oper> <arg>]...ban.list

三、Varnish性能优化

Varnish有许多参数,虽然大多数场景中这些参数的默认值都可以工作得很好,然而特定的工作场景中要想有着更好的性能的表现,则需要调整某些参数。可以在管理接口中使用param.show命令查看这些参数,而使用param.set则能修改这些参数的值。然而,在命令行接口中进行的修改不会保存至任何位置,因此,重启varnish后这些设定会消失。此时,可以通过启动脚本使用-p选项在varnishd启动时为其设定参数的值。然而,除非特别需要对其进行修改,保持这些参数为默认值可以有效降低管理复杂度,由于Varnish的参数有很多,此处指对影响较大的参数进行介绍需要更加详细的介绍需查看官方文档

[root@nginx2 varnish]# varnishadmvarnish> param.showlisten_depth                1024 [connections]lru_interval                2 [seconds]thread_pool_max             1000 [threads]thread_pool_min             50 [threads]thread_pool_timeout         120 [seconds]thread_pools                2 [pools]varnish>

注释

  • thread_pools: 用来设置线程池的数量,一般认为这个值和系统CPU的数目相同最好,设置过多的pools,varnish的并发处理能力会更强,但是也会消耗更多的CPU和内存。
  • thread_pool_min:用来设置每个pools的最小threads数,当pools接收到可用的请求后,就会将请求分配给空闲的threads来处理。
  • thread_pool_max:表示所有pools对应的threads数总和的最大值,此值不能太大,可以设置为系统峰值的90%左右即可,设置过大,会导致进程hung住。
  • thread_pool_timeout:表示threads的超时过期时间,当threads数大于thread_pool_min设定值时,threads空闲超过thread_pool_timeout设定的时间时,thread就会被释放 掉。
  • lru_interval 20 [seconds]
  • listen_depth 1024 [connections]
  • lru_interval:这是个时间参数,大概意思是说如果有一个对象在内存中超过了此参数设定的时间还没有被重用时,就把这个对象从LRU(Least Recently Used)队列中移除。 这其实是 缓存系统的一个常用算法,合理的设置这个时间,可以提高系统的运行效率。
  • listen_depth:这个参数是设置TCP连接队列的长度,设置大一点可以提高并发处理能力。
对于这些优化参数,最好的方式是加到varnish的启动脚本中,通过varnishd的“-p”参数调用即可。


本博客至此结束,如有不足之处,望广大博友多提宝贵意见。。。



本文出自 “小栋―HOME” 博客,请务必保留此出处http://xiaodong88.blog.51cto.com/1492564/1304988