Varnish缓存服务详解及应用实现

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

   Varnish与一般服务器软件类似,就是一个web缓存代理服务器,分为master(management)进程和child(worker,主要 做cache的工作)进程。master进程读入命令,进行一些初始化,然后fork并监控child进程。child进程分配若干线程进行工作,主要包 括一些管理线程和很多woker线程。

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

   Child进程包含多种类型的线程,常见的如:
       Acceptor线程:接收新的连接请求并响应;
       Worker线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;
       Expiry线程:从缓存中清理过期内容;

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

   进程的工作过程原理及过程:

   wKioL1N3LNmBY7lcAAJafMlNjYg162.jpg

2、varnish与squid的区别
   varnish和squid在中小规模的应用上,varnish足够轻量级,足够好用,但是在巨大的并发请求来说,单个varnish所能够承载的并发 访问量大概在5000个连接请求左右,超出5000个可能就就得不稳定了;而在这里squid就能表现出良好的性能了,因此在大规模的企业级应用中仍然是 以squid居多,而在中小规模的自己公司的反向代理缓存中varnish居多;

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

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


5、varnish的后端存储
   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自身也会占去不小的内存空间。

6、varnish的工作原理及工作流程
   官方提供的工作流程图:

   wKiom1N3Bp6DgfV-AAIqXsglWKg749.jpg
vcl的工作方式是基于状态引擎(state engine)来实现的;上图说明:
   vcl_recv的结果如果可以查询缓存并可以识别,那就要到vcl_hash这步了,如果无法识别那就通过pipe(管道)送给vcl_pipe,如 果能识别,但不是一个可缓存的对象,那就通过pass送到vcl_pass去,vcl_hash之后就可查看缓存中有没有了,有这个请求的对象就表示命中 (vcl_hit),如果没有那就表示未命中(vcl_miss),如果命中的就可以直接通过deliver直接送给vcl_deliver响应了,如果 未命中就通过fetch交给vcl_fatch去后端服务器上去取数据,取回数据之后如果数据可以缓存就缓存(cache),本地缓存完之后再构建响应, 如果不可以缓存就不做缓存交给vcl_deliver响应了;而如果命中了交给vcl_pass,交给pass之后就要到Fetch objet from backend后端服务器上去取数据了,这是因为这个命中的对象可能是过期或者是要做单独立额外的处理的;这就是vcl的状态引擎过程。

 

一、安装实现过程:

# 安装varnish,版本是3.0.4-1.el6

[root@node0 ~]# rpm -ivh varnish-3.0.4-1.el6.x86_64.rpm varnish-docs-3.0.4-1.el6.x86_64.rpm varnish-libs-3.0.4-1.el6.x86_64.rpm

[root@node0 ~]# rpm -ql varnish  # 查看varnish的安装文件
[root@node0 ~]# vim /etc/sysconfig/varnish  # 查看配置文件
NFILES=131072        # 所能够打开的最大文件数
MEMLOCK=82000        # 用多大内存空间保存日志信息
DAEMON_COREFILE_LIMIT="unlimited"    # 进程核心转储所使用的内存空间,unlimited表示无上限
RELOAD_VCL=1        # 重新启动服务时是否重新读取VCL并重新编译的
VARNISH_VCL_CONF=/etc/varnish/default.vcl    # 默认读取的VCL文件
VARNISH_LISTEN_PORT=80    # 监听的端口,默认监听6081
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1    # 管理接口监听的地址
VARNISH_ADMIN_LISTEN_PORT=6082    # 管理接口监听的端口
VARNISH_SECRET_FILE=/etc/varnish/secret    # 使用的密钥文件
VARNISH_MIN_THREADS=1    # 最少线程数
VARNISH_MAX_THREADS=1000    # 最大线程数
VARNISH_THREAD_TIMEOUT=120    # 线程的超时时间
VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin    # 基于文件存储时的文件路径
VARNISH_STORAGE_SIZE=1G    # 存储文件的大小
VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}"    # 存储的文件格式
VARNISH_TTL=120    # 联系后端服务器的超时时间
DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \
            -f ${VARNISH_VCL_CONF} \
            -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \
            -t ${VARNISH_TTL} \
            -w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} \
            -u varnish -g varnish \
            -S ${VARNISH_SECRET_FILE} \
            -s ${VARNISH_STORAGE}"    # 使用定义的各高级配置的参数

# 定义后端服务器
[root@node0 sysconfig]# cd /etc/varnish/

[root@node0 varnish]# cp default.vcl default.vcl.bak

[root@node0 varnish]# mv default.vcl test.vcl

[root@node0 varnish]# vim test.vcl
backend webserver {
 .host = "172.16.27.1";  # 后端服务器的地址
 .port = "80";           # 后端服务监听的端口

}

# 启动服务

[root@node0 sysconfig]# service varnish start
Starting varnish HTTP accelerator:                         [  OK  ]
[root@node0 sysconfig]#

 

# 可以进入varnish的命令操作,进去后直接输入help就可以查看帮助信息;
[root@node0 varnish]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
200 201    
-----------------------------
Varnish Cache CLI 1.0
-----------------------------
Linux,2.6.32-431.el6.x86_64,x86_64,-smalloc,-hcritbit

Type 'help' for command list.
Type 'quit' to close CLI session.

varnish> help
200 377    
help [command]
ping [timestamp]
auth response
quit
banner
status
start
stop
stats
vcl.load <configname> <filename>
vcl.inline <configname> <quoted_VCLstring>
vcl.use <configname>
vcl.discard <configname>
vcl.list
vcl.show <configname>
param.show [-l] [<param>]
param.set <param> <value>
purge.url <regexp>
purge <field> <operator> <arg> [&& <field> <oper> <arg>]...
purge.list

   安装配置好后端web服务器并启动,地址为172.16.27.1,而后通过varnish服务器地址访问后端服务器,这里仅仅只是定义一指向后端服务 器,也就说现在也只能工作起来,但是还没有定义相关的缓存属性等信息,那就先通过varnish服务器端访问一下先吧:

[root@node1 ~]# yum -y install httpd php php-mysql

[root@node1 ~]# cd /var/www/html

[root@node1 html]# vim index.html

<h1>www.tanxw.com and varnish fo backend</h1>
<h2>node1.tanxw.com</h2>

wKioL1N3LPuirP0DAADyvwScqLY595.jpg

 

二、设置响应是否命中,接着继续编写配置文件:

[root@node0 varnish]# vim test.vcl

sub vcl_deliver {            # 定义子例程

 if (obj.hits > 0){         # 判断如果命中了就在http响应首部设置X-Cache为HIT

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

 } else {                   # 否则就在http响应首部设置X-Cache为MISS

   set resp.http.X-Cache = "MISS";
 }

}

# 在varnish的命令行中重新编译重新加载配置文件

varnish> vcl.load test1 /etc/varnish/test.vcl

200 13      
VCL compiled.
vcl.use test1
200 0      

varnish> vcl.show test1
200 191

 

backend webserver {
 .host = "172.16.27.1";
 .port = "80";
}

sub vcl_deliver {
 if (obj.hits > 0){
   set resp.http.X-Cache = "HIT";
 } else {
   set resp.http.X-Cache = "MISS";
 }
}

[root@node0 varnish]# curl -I http://172.16.27.88/index.html  # 也可以在命令行请求

然后再到页面上访问看一下是否已经生效:

wKiom1N3LTag8tGIAAPzTTH4mcM097.jpg

 

三、指定某些文件不能查缓存,断续添加配置文件

[root@node0 varnish]# vim test.vcl   # 添加如下代码

sub vcl_recv {     # 定义请求的文件中如果匹配test.html就pass,就不查缓存

 if (req.url ~ "test.html"){
   return(pass);
 }
 return(lookup);

}

# 再到varnish的命令行中重新加载配置文件并应用

varnish> vcl.load test4 /etc/varnish/test.vcl
200 13      
VCL compiled.
vcl.use test4
200 0

# 在命令行请求看一下缓存,不管怎么请求X-Cache都是MISS

[root@node0 varnish]# curl -I http://172.16.27.88/test.html
HTTP/1.1 200 OK
Server: Apache/2.2.15 (CentOS)
Last-Modified: Sat, 17 May 2014 09:51:07 GMT
ETag: "120905-1a-4f99578180449"
Accept-Ranges: bytes
Content-Length: 26
Content-Type: text/html; charset=UTF-8
Date: Sat, 17 May 2014 10:01:27 GMT
X-Varnish: 1309371381
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Cache: MISS

而后再请求test.html页面;

wKioL1N3bEWxPpfMAAIddvy86EY909.jpg

 

四、设定缓存时长和定义图片防盗链

[root@node0 varnish]# vim default.vcl

sub vcl_fetch {

 if (req.url ~ "\.(jpg|jpeg|gif|png)$") {  # 如果url是以图片格式结尾的缓存2小时
   set beresp.ttl = 7200s;
 }
 if (req.url ~ "\.(html|css|js)$") {   # 如果url是以html|css|js结尾的缓存20分钟
   set beresp.ttl = 1200s;
 }

}

sub vcl_recv {
 if (req.url ~ "test.html"){
   return(pass);
 }

# 图片防盗链
 if (req.http.referer ~ "http://.*") {
   if (!(req.http.referer ~ "http://.*tanxw\.com"
      ||req.http.referer ~ "http://.*google\.com"
      ||req.http.referer ~ "http://.*yahoo\.com"
      ||req.http.referer ~ "http://.*google\.cn"
      ||req.http.referer ~ "http://.*baidu\.com"
      )) {
       set req.http.host = "www.tanxw.com";
       set req.url = "/templets/default/images/logl.gif";
   }
       return (lookup);
 }
 return(lookup);
}

 

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

[root@node0 varnish]# vim default.vcl

acl purgers {    # 定义acl访问控制,只允许以下网段或主机执行purgers操作

   "127.0.0.1";
   "172.16.0.0"/16;
}

sub vcl_recv {
   if (req.request == "PURGE") {   # 如果请求方法是PURGE,并且客户端IP在上面定义的网段内的就允许执行PURGE操作,否则生成一个错误页面返回给用户
       if (!client.ip ~ purgers) {
           error 405 "Method not allowed";
       }
       return (lookup);
   }
}
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") {   # 如在pass中要清除缓存,直接返回错误码
       error 502 "PURGE on a passed object";
   }

}

# 保存退出,而后直接在命令行中进行测试一下:

[root@node0 varnish]# curl -I http://172.16.27.88/index.html
HTTP/1.1 200 OK
Server: Apache/2.2.15 (CentOS)
Last-Modified: Sat, 17 May 2014 08:07:17 GMT
ETag: "120904-47-4f99404c6fde2"
Content-Type: text/html; charset=UTF-8
Content-Length: 71
Accept-Ranges: bytes
Date: Sat, 17 May 2014 13:38:51 GMT
X-Varnish: 364681188 364681185
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Cache: HIT from 172.16.27.88  # 缓存命中

[root@node0 varnish]# curl -X PURGE http://172.16.27.88/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 OK.</title>   # 缓存清除成功
 </head>
 <body>
   <h1>Error 200 Purged OK.</h1>
   <p>Purged OK.</p>
   <h3>Guru Meditation:</h3>
   <p>XID: 364681189</p>
   <hr>
   <p>Varnish cache server</p>
 </body>
</html>

[root@node0 varnish]# curl -I http://172.16.27.88/index.html
HTTP/1.1 200 OK
Server: Apache/2.2.15 (CentOS)
Last-Modified: Sat, 17 May 2014 08:07:17 GMT
ETag: "120904-47-4f99404c6fde2"
Content-Type: text/html; charset=UTF-8
Content-Length: 71
Accept-Ranges: bytes
Date: Sat, 17 May 2014 13:42:39 GMT
X-Varnish: 364681194
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Cache: MISS   # 清除后缓存MISS了

 

 

六、Varnish检测后端主机的健康状态
   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.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秒;

backend webserver {
   .host = "www.magedu.com";    # 定义后端主机
   .probe = {

      .url = "/.healthtest.html";  # 向后端主机获取这个页面

      .interval = 1s;   # 每隔1秒钟尝试一次

      .window = 5;      # 最多尝试5次,判断采样的样本

      .threshold = 2;   # 如果采样5次,如果有2次是错误的,就认为是失败的,上线也是一样

   }
}

 

七、Varnish的命令行工具

varnishadm命令语法:varnishadm [-t timeout] [-S secret_file] [-T address:port] [-n name] [command [...]]

通过命令行的方式连接至varnishd进行管理操作的工具,指定要连接的varnish实例的方法有两种:
-n name —— 连接至名称为“name”的实例;
-T address:port —— 连接至指定套接字上的实例;
其运行模式有两种,当不在命令行中给出要执行的"command"时,其将进入交互式模式;否则,varnishadm将执行指定的"command"并退出。要查看本地启用的缓存,可使用如下命令进行。
# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 storage.list

 

总结:

   varnish是一个很强大的缓存服务器,还可以做动静分离,这里限于篇幅,在这里就不一一例举了,有关信息可以参数官方文档,还可以做后端服务器的调度等,于是这里总结得过于仓促,有做得不到之处还望大神多多指出,如果有什么问题可以留言交流学习。

posted @ 2014-07-05 13:58  xiao_v  阅读(7099)  评论(0编辑  收藏  举报