actions主要有以下动作pass \请求转发后端服务器不缓存。lookup \当请求在 vcl_recv 中被 lookup 后,varnish 将从缓存中提取数据,如果缓存中没有数据,将被设置为 pass,不能在 vcl_fetch 中设置 lookup。pipe \pipe 和 pass 相似,都要访问后端服务器,不过当进入 pipe 模式后,在此连接未关闭前,后续的所有请求都发到后端服务器(这句是我自己理解后简化的,有能力的朋友可以看看官方文档,给我提修改建议) 。deliver \请求的目标被缓存,然后发送给客户端esi \ESI-process the fetched document(我理解的就是 vcl 中包换一段 html
.probe = {.url = "/";.interval = 5s;.timeout = 1 s;.window = 5;.threshold = 3;}
既然Varnish需要在多台服务器上缓存数据,就需要Varnish映射所有的URL到一台单独的主机。
- backend webserver {
- .host = "127.0.0.1";
- .port = "80";
- .connect_timeout = 4s;
- .first_byte_timeout = 5s;
- .between_bytes_timeout = 20s;
- }
该块配置用于定义一台Varnish默认访问的后端服务器,当Varnish需要从后端服务器获取数据时,就会访问自己的80端口。
当然Varnish也可以定义多台后端服务器实现负载均衡的目的。
.connect_timeout定义的是等待连接后端的时间
.first_byte_timeout定义的是等待从backend传输过来的第一个字节的时间
.between_bytes_timeout 定义的是两个字节的间隔时间
当然还可以增加一个backend,用于访问本机的8090端口,假设通过该端口提供图片服务。
- backend img {
- .host = "127.0.0.1";
- .port = "8090";
- }
当匹配img的URL时,需把请求发送到上面定义的backend img,其他的请求发送到backend webserver。
- sub vcl_recv {
- if (req.url ~ "^/img/") {
- set req.backend = img;
- } else {
- set req.backend = webserver.
- }
- }
Varnish不仅仅可以定义多个backend,还可以把多个backend合成一个组,使用循环的方式把请求分配给组中的backends。并且Varnish会根据健康检查情况来判断后端服务器是否正常提供服务。
Varnish使用区域语言VCL来管理定义Varnish的存取策略。VCL语法简单,跟Perl比较相似,可以使用多种运算符如"="、"=="、"!,&&,!!"等形式;也可以使用正则表达式来进行匹配,还可以使用"set"来指定变量。当执行VCL时,Varnish会先把VCL转换成二进制代码。
有一点要注意,""字符在VCL里没有什么特别的含义,这点和其他语言不同。另外,VCL只是配置语言,并不是真正的编程语言,所以没有循环和自定义变量。
为了可以更好地对Varnish进行配置调整,需要了解Varnish的配置语法,也就是VCL语言。下面对VCL常用的一些函数和变量进行介绍。
(1)vcl_recv模块
用于接收和处理请求。当请求成功被调用后,Varnish通过判断请求的数据来决定如何处理请求。此模块一般以如下几个关键字结束。
pass:表示进入pass模式,把请求交给vcl_pass模块处理。
pipe:表示进入pipe模式,把请求交给vcl_pipe模块处理。
error code [reason]:表示把错误标识返回给客户端,并放弃处理该请求。错误标识包括200、405等。"reason"是对错误的提示信息。
(2)vcl_pipe模块
此模块在请求进入pipe模式时被调用,用于将请求直接传递至后端主机,在请求和返回的内容没有改变的情况下,也就是在当前连接未关闭时,服务器将不变的内容返回给客户端,直到该连接被关闭。
(3)vcl_pass模块
此模块表示当请求被pass后,用于将请求直接传递至后端应用服务器。后端应用服务器在接收请求后将数据发送给客户端,但不进行任何数据的缓存,在当前连接下每次都返回最新的内容。
(4)lookup
一个请求在vcl_recv中被lookup后,Varnish将在缓存中提取数据。如果缓存中有相应的数据,就把控制权交给vcl_hit模块;如果缓存中没有相应的数据,请求将被设置为pass并将其交给vcl_miss模块。
(5)vcl_hit模块
执行lookup指令后,Varnish在缓存中找到请求的内容后将自动调用该模块。
在此模块中,deliver表示将找到的数据发送给客户端,并把控制权交给vcl_deliver模块。
(6)vcl_miss模块
执行lookup后,Varnish在缓存中没有找到请求的内容时会自动调用该方法。此模块可以用于判断是否需要从后端服务器获取内容。
在此模块中,fetch表示从后端获取请求的数据,并把控制权交给vcl_fetch模块。
(7)vcl_fetch模块
在后端主机更新缓存并且获取内容后调用该方法,接着,通过判断获取的内容来决定是将内容放入缓存,还是直接返回给客户端。
(8)vcl_deliver模块
当一个没有被缓存的数据交付给客户端的时候被调用。
(9)vcl_timeout 模块
在缓存数据到期前调用此模块。
在此模块中,discard表示从缓存中清除到期数据。
(10)vcl_discard模块
在缓存数据到期后或缓存空间不够时,自动调用该模块。
在此模块中keep表示将数据继续保留在缓存中。
- acl purge {
- "localhost";
- "127.0.0.1";
- "18.81.12.10";
- }
- if (req.request == "PURGE") {
- if (!client.ip ~ purge) {
- error 405 "Not allowed.";
- }
- return(lookup);
- }
- if (req.http.host ~ "^(read)?.aaa.com$") {
- set req.backend = webserver;
- if (req.request != "GET" && req.request != "HEAD") {
- return(pipe);
- }
- else {
- return(lookup);
- }
- }
- else {
- error 404 " Cache Server";
- return(lookup);
- }
这段条件判断用于对aaa.com域名进行缓存加速,aaa.com是泛指概念,也就是说所有以aaa.com结尾的域名都进行缓存。而if (req.request != "GET" && req.request != "HEAD") 表示"如果请求的类型不是GET与HEAD",则返回错误码404。
- if (req.url ~ "^/images") {
- unset req.http.cookie;
- }
这条规则的意思是清除服务器上/images目录下的所有缓存,当这个请求在后端服务器生效时,如果访问的URL匹配这个规则,那么头信息中的cookie就会被删除。
- if (req.request == "GET" && req.url ~ ". (png|swf|txt|png|gif|jpg|css|js|htm| html)$") {
- unset req.http.cookie;
- }
- if (req.http.x-forwarded-for) {
- set reqreq.http.X-Forwarded-For =
- req.http.X-Forwarded-For ", " client.ip; }
- else { set req.http.X-Forwarded-For = client.ip; }
因为Squid、Varnish都会把客户端的IP地址放在HTTP_X_FORWARDED_FOR里面传给后端的Web服务器,所以后端的Web程序都要对其进行调用。
- if (req.request != "GET" &&
- req.request != "HEAD" &&
- req.request != "PUT" &&
- req.request != "POST" &&
- req.request != "TRACE" &&
- req.request != "OPTIONS" &&
- req.request != "DELETE") {
- return (pipe);
- }
该if判断表示如果请求的类型不是GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE时,则进入pipe模式。注意这里的"&&"是与的关系。
- if (req.request == "GET" && req.url ~ ". (png|swf|txt|png|gif|jpg|css|js|htm| html)") {
- set beresp.ttl = 180s;
- }
- else {
- set beresp.ttl = 30d;
- }
- return (deliver);
- }
该if判断用于对请求类型是GET,并且请求的URL以png、swf、txt、gif、css、js等结尾时,则进行缓存,缓存时间为180秒。其他缓存为30天。
- sub vcl_deliver {
- set resp.http.x-hits = obj.hits ;
- if (obj.hits > 0) {
- set resp.http.X-Cache = "HIT read.easouu.com";
- }
- else {
- set resp.http.X-Cache = "MISS read.easou.com";
- }
这个模块定义的是添加一个Header标识,以判断缓存是否命中。
- sub vcl_error {
- set obj.http.Content-Type = "text/html; charset=utf-8";
- synthetic {"
- <?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>"} obj.status " " obj.response {"</title>
- </head>
- <body>
- <h1>Error "} obj.status " " obj.response {"</h1>
- <p>"} obj.response {"</p>
- <h3>Guru Meditation:</h3>
- <p>XID: "} req.xid {"</p>
- <hr>
- <address>
- <a href="http://read.easou.com/">read.easou.com</a>
- </address>
- </body>
- </html>
- "};
- return (deliver);
- }
最后这个模块定义了访问错误页面时的返回信息。
现在varnish配置基本完成,可以在8080端口上启动varnish,并进行一些基本的测试。
Varnish缓存的配置
简单的配置文件内容如下:
-
- backend webserver {
- .host = "127.0.0.1";
- .port = "8090";
- .connect_timeout = 4s;
- .first_byte_timeout = 5s;
- .between_bytes_timeout = 20s;
- }
- acl purge {
- "localhost";
- "127.0.0.1";
- "18.81.12.10";
- }
- sub vcl_recv {
- if (req.request == "PURGE") {
- if (!client.ip ~ purge) {
- error 405 "Not allowed.";
- }
- return(lookup);
- }
- if (req.http.host ~ "^(read)?.easou.com$") {
- set req.backend = webserver;
- if (req.request != "GET" && req.request != "HEAD") {
- return(pipe);
- }
- else {
- return(lookup);
- }
- }
- else {
- error 404 " Cache Server";
- return(lookup);
- }
- if (req.request == "GET" && req.url ~ ".(png|swf|txt|png|gif|jpg|css|js| htm|html)$") {
- unset req.http.cookie;
- }
- if (req.url ~ "^/images") {
- unset req.http.cookie;
- }
- if (req.http.Cache-Control ~ "(no-cache|max-age=0)") {
- purge_url(req.url);
- }
- return (lookup);
- if (req.http.x-forwarded-for) {
- set reqreq.http.X-Forwarded-For =
- req.http.X-Forwarded-For ", " client.ip; }
- else { set req.http.X-Forwarded-For = client.ip; }
- if (req.request != "GET" &&
- req.request != "HEAD" &&
- req.request != "PUT" &&
- req.request != "POST" &&
- req.request != "TRACE" &&
- req.request != "OPTIONS" &&
- req.request != "DELETE") {
- return (pipe);
- }
- if (req.request != "GET" && req.request != "HEAD") {
- return (pass);
- }
- if (req.http.Authorization || req.http.Cookie) {
- return (pass);
- }
- }
- sub vcl_pipe {
- # set req.http.connection = "close";
- return (pipe);}
- sub vcl_hit {
- if (!obj.cacheable) {
- return (pass);
- }
- if (req.request == "PURGE") {
- set obj.ttl = 0s;
- error 200 "Purged.";
- }
- return (deliver);
- }
- sub vcl_miss {
- return (fetch);
- }
- sub vcl_fetch {
- if (!beresp.cacheable) {
- return (pass);
- }
- if (beresp.http.Set-Cookie) {
- return (pass);
- }
- if (beresp.http.Pragma ~ "no-cache" ||
- beresp.http.Cache-Control ~ "no-cache" ||
- beresp.http.Cache-Control ~ "private") {
- return (pass);
- }
- if (req.url ~ "^/cover/") {
- set beresp.ttl = 1800s;
- }
- else {
- set beresp.ttl = 30d;
- }
- return (deliver);
- # if (req.request == "GET" && req.url ~ ".(png|swf|txt|png|gif|jpg|css|js| htm|html|jsp)") {
- # set beresp.ttl = 180s;
- # }
- # else {
- # set beresp.ttl = 30d;
- # }
- # return (deliver);
- }
- sub vcl_deliver {
- set resp.http.x-hits = obj.hits ;
- if (obj.hits > 0) {
- set resp.http.X-Cache = "HIT read.easouu.com";
- }
- else {
- set resp.http.X-Cache = "MISS read.easou.com";
- }
- }
- sub vcl_error {
- set obj.http.Content-Type = "text/html; charset=utf-8";
- synthetic {"
- <?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>"} obj.status " " obj.response {"</title>
- </head>
- <body>
- <h1>Error "} obj.status " " obj.response {"</h1>
- <p>"} obj.response {"</p>
- <h3>Guru Meditation:</h3>
- <p>XID: "} req.xid {"</p>
- <hr>
- <address>
- <a href="http://read.easou.com/">read.easou.com</a>
- </address>
- </body>
- </html>
- "};
- return (deliver);
- }
Varnish 是一款高性能且开源的反向代理服务器和 HTTP 加速器,其采用全新的软件体系机构,和现在的硬件体系紧密配合,与传统的 squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点,很多大型的网站都开始尝试使用 varnish 来替换 squid,这些都促进 varnish 迅速发展起来。
挪威的最大的在线报纸 Verdens Gang(vg.no) 使用 3 台 Varnish 代替了原来的 12 台 Squid,性能比以前更好,这是 Varnish 最成功的应用案例。
Varnish 与一般服务器软件类似,分为 master 进程和 child 进程。Master 进程读入存储配置文件,调用合适的存储类型,然后创建 / 读入相应大小的缓存文件,接着 master 初始化管理该存储空间的结构体,然后 fork 并监控 child 进程。Child 进程在主线程的初始化的过程中,将前面打开的存储文件整个 mmap 到内存中,此时创建并初始化空闲结构体,挂到存储管理结构体,以待分配。Child 进程分配若干线程进行工作,主要包括一些管理线程和很多 worker 线程。
接着,开始真正的工作,varnish 的某个负责接收新 HTTP 连接线程开始等待用户,如果有新的 HTTP 连接过来,它总负责接收,然后唤醒某个等待中的线程,并把具体的处理过程交给它。Worker 线程读入 HTTP 请求的 URI,查找已有的 object,如果命中则直接返回并回复用户。如果没有命中,则需要将所请求的内容,从后端服务器中取过来,存到缓存中,然后再回复。
分配缓存的过程是这样的:它根据所读到 object 的大小,创建相应大小的缓存文件。为了读写方便,程序会把每个 object 的大小变为最接近其大小的内存页面倍数。然后从现有的空闲存储结构体中查找,找到最合适的大小的空闲存储块,分配给它。如果空闲块没有用完,就把多余的内存另外组成一个空闲存储块,挂到管理结构体上。如果缓存已满,就根据 LRU 机制,把最旧的 object 释放掉。
释放缓存的过程是这样的:有一个超时线程,检测缓存中所有 object 的生存期,如果超初设定的 TTL(Time To Live)没有被访问,就删除之,并且释放相应的结构体及存储内存。注意释放时会检查该存储内存块前面或后面的空闲内存块,如果前面或后面的空闲内存和该释放内存是连续的,就将它们合并成更大一块内存。
整个文件缓存的管理,没有考虑文件与内存的关系,实际上是将所有的 object 都考虑是在内存中,如果系统内存不足,系统会自动将其换到 swap 空间,而不需要 varnish 程序去控制。