varnish-4.0 原理及配置详解

varnish介绍

Varnish Cache是一个web应用程序加速器,也是一个HTTP反向代理软件。放在HTTP或NGINX服务器前端,缓存内容



一、varnish软件包中的关键程序:

1.varnishd
2.varnishadmin

varnishd:

varnishd是主程序启动后生成两个进程,Manager Process(父进程)和Cacher Process(子进程),前者在启动时读入配置文件(稍后会讲到)并fork()出后者,同时具有CLI接口接收管理员的指令以用于管理后者,后者是真正处理缓存业务的进程,其下又可以生成若干线程,这些线程各司其职,完成缓存业务的处理并记录日志。这里的Manager进程和Cacher进程类似于Nginx的Master和Worker

varnishadm:

varnishadm可以用来建立一个CLI来连接varnishd,使用-n名称或者-T和-S参数。如果提供了-n参数,密钥文件和address:port会在共享内存中被查找。如果没有提供的话,那么varnishadm将会查找一个实例而不需要指定一个名字。如果给出一个命令,命令和参数会通过CLI连接发送出去,并且结果会返回给stdout。如果没有命令参数提供,varnishadm会通过cli socket和stdin/stdout来传递命令和输出。

  • -n ident 通过这个名字连接varnishd
  • -S secretfile 指定认证的密钥文件。这个需要和提供给varnishd的-S参数一致。只有它可以读取该文件的内容,并验证这个CLI连接。
  • -t timeout 操作的超时时间。
  • -T 连接指定地址和端口的管理接口

二、varnish配置文件


/etc/varnish/any_name.vcl 

varnish是如何制定缓存策略的呢?(所谓缓存策略就是控制http请求要如何拿到所请求的资源,查不查缓存,怎么查,查不到怎么办等)就是通过varnish自己的语言VCL(Varnish Configuration Language)来控制的,我们使用这种语言把要对报文如何处理的动作写到 .vcl 文件中,然后让 varnishd 加载这个文件就ok了,下文会详细介绍如何使用。使用yum安装varnish后,系统会在/etc/varnish下生成一个default.vcl文件,我们可以把vcl控制语句写在这里面,也可以写在该目录下自建的 .vcl 文件中。该文件的具体配置将在后文详细说明,需要额外强调的是 .vcl 文件需要被gcc编译之后才能够被加载使用,所以要一定要预先安装gcc。

/etc/varnish/varnish.params 

主要定义了启动的时候使用哪个vcl作为启动时的缓存策略,作为反向代理监听在哪个IP的哪个Port上,开启的CLI接口监听在哪个IP的哪个Port上。启动的时候设定Cacher Process有多少线程池,每个线程池有多少线程。还有很多与缓存业务处理相关的参数(如 default_grace),这些参数可以在启动时设定(有缺省值),也可以在启动后修改(启动时也可以指定哪些参数是启动后只读的)。


VCL4相比VCL3语法的改变点:

  • 要在配置文件中指定版本:即在第一行写上 vcl 4.0;

  • vcl_fetch函数被vcl_backend_response代替,且req.*不再适用vcl_backend_response;

  • 后端源服务器组director成为varnish模块,需import directors后再在vcl_init子例程中定义;

  • vcl_error变更为vcl_backend_error,必须使用beresp.*,而不是obj.*。

  • req.request变更为req.method,obj为只读对象了。

  • 自定义的子例程(即一个sub)不能以vcl_开头,调用使用call sub_name;

  • error()函数被synth()替代;

  • return(lookup)被return(hash)替代;

  • 使用beresp.uncacheable创建hit_for_pss对象;

  • 变量req.backend.healty被std.healthy(req.backend)替代;

  • 变量req.backend被req.backend_hint替代;

  • 关键字remove被unset替代;

  • 关键字"purge;"命令,已被去除。在vcl_recv使用return(purge)。

  • vcl_synth采用resp.*,而非原来的obj.* 。


三、VCL内置state engine

vcl_recv
vcl_deliver
vcl_synth

vcl_hash
vcl_hit
vcl_miss
vcl_pass
vcl_pipe
vcl_purge

vcl_backend_fetch
vcl_backend_response
vcl_backend_error

----------------------------------------------------------------------------------------------------------------------------------


vcl_recv
vcl_recv是客户端请求报文被解析后第一个被执行的subroutine,在该subroutine我们可以添加vcl代码完成诸多功能,如:
1.缓存未命中的话去后端哪台主机请求资源    2.控制缓存策略,如仅仅针对某些url做缓存    3.完成url重写    等功能。
在vcl_recv中我们可以return如下action:
pass:对于http请求,跳过缓存查找这一步去后端server请求资源,虽然没有跳过了查缓存这一步,但是后续的步骤该走的还是得走,该过的subroutine还得过。这种情况下从后端server拿到的资源不会被缓存。
pipe:对于http请求,跳过所有步骤,也就是不用过任何subtoutine的处理,直接去后端server请求资源,这种情况下拿到的资源也不缓存,就像经过varnish搭建了一条客户端到服务器的一条管道。且后续同一个tcp连接的所有request都会直接被送进管道处理。被pipe处理的请求不记录日志。
hash:进行哈希计算并查找缓存。
purge:在缓存中查找缓存对象并清除之。
synth:合成http响应报文,通常是错误报文,此外,synth也可以用于重定向客户端请求

实例A:
sub vcl_recv {
  if (req.httpd.User-Agent ~ "iPad"
  req.httpd.User-Agent ~ "iPhone"
  req.httpd.User-Agent ~ "Android") {

  set req.http.X-Device = "mobile";
  } else {
  set req.http.X-Device = "desktop";
  }
}
作用:根据用户请求报文中的User-Agent判断用户的浏览器类型为mobile或desktop,然后在请求报文中加入名为X-Device的头部,并设置其值为mobile或desktop,表示客户端平台。响应报文(无论来自缓存还是后端server)可以根据客户端平台类型构造并发送给客户端。

实例B:
sub vcl_recv {
  set req.http.host = regsub(req.http.host,"^www\.","");
}
作用:把 www.xxx.yyy 按照xxx.yyy处理,如 www.web1.com 按照 web1.com 处理

实例C:
sub vcl_recv {
  if (req.http.host == "sport.web1.com") {
  set req.http.host = "web1.com";
  set req.url = "/sport" + req.url;
  }
}
作用:重写 http://sport.web1.com/ 到 http://web1.com/soprt/ 

实例D:
sub vcl_recv {
  if (req.http.host ~ "^sport\.") {
  set req.http.host = regsub(req.http.host,"^sport\.","");
  set req.url = regsub(req.url,"^","/sport");
  }
}
作用:重写 http://sport.xxxx/ 到 http://xxxx/sport/

--------------------------------

vcl_pass
当一个subroutine执行return(pass)时,即跳到vcl_pass执行处理逻辑,vcl_pass会把一个请求设置为pass模式,在vcl_pass中我们可以return: 1.fetch 2.synth 3.restart。 当return一个fetch时,被设置为pass模式的请求得到的对象不会被缓存而直接响应给客户端。返回synth时进入vcl_synth合成响应报文,返回restart则从状态机开始处再开启一轮处理请求报文的动作。
--------------------------------
补充:hit-for-pass
有些请求得到的响应对象不应该被缓存,一个典型的例子就是当响应报文中含有Set-Cookie头部时,因为该响应对象仅仅是针对单个用户的。这种场景下,我们可以设置varnish生成一个hit-for-pass对象,然后缓存之,而不是直接缓存响应对象。
当一个从后端server拿到的对象不需要被缓存时,我们可以 set bereq.uncacheable = true 这样的话Cacher Process就会维护一个指向hit-for-pass的键值对,当再次有请求查到该哈希键时,会找到一个hit-for-pass的缓存对象,然后跳转到vcl_pass处理,在vcl_pass中请求被设置为pass模式。
和正常的缓存对象一样,hit-for-pass也有其ttl,当ttl一过时一样会被从缓存区清理掉。

--------------------------------

vcl_backend_fetch

sub vcl_backend_fetch {
  return (fetch);
}

vcl_backend_fetch可以在vcl_miss或vcl_pass中被调用(当然是通过return),当其被vcl_miss调用时在后端server拿到的对象会被缓存,而当其被vcl_pass调用时在后端拿到的对象就不会被缓存,即使对象的obj.ttl和obj.keep变量值大于0。
还有一个与缓存相关的变量 bereq.uncacheable, 该变量指明了后端返回的被请求的资源对象是否被缓存。然而,从pass中的请求得到的对象会忽略bereq.uncacheable的值,而不缓存之,上文也提到了。
vcl_backend_fetch中我们自己加入的代码可以return到fetch或者abandon。前者会将请求代理发送到后端,而后者会调用vcl_synth。vcl_backend_fetch中的缺省代码return的是fetch。来自后端的响应会被vcl_backend_response或vcl_backend_error处理,这取决于响应报文。
如果varnish接收到一个语法正确的http响应(含5xx错误码的http响应),则进入vcl_backend_response处理。若varnish没有收到http响应则进入vcl_backend_error处理。

--------------------------------

vcl_hash
vcl_hash的作用是对一个http请求做哈希计算。
缺省的vcl_recv代码是跳到vcl_hash的,任何subroutine都可以通过return(hash)跳转到vcl_hash。
内置的vcl_hash代码如下:
sub vcl_hash {
  hash_date(req.url);
  if (req.http.host) {
  hash_data(req.http.host);
  }
  else {
  hash_data(server.ip);
  }
  return (lookup);
}
vcl_hash为将要缓存的对象定义一个哈希键,缓存对象的键是特有的,不同的缓存对象具有不同的键。vcl_hash中的内置代码使用请求的url和hostname/IP来计算哈希键。
vcl_hash的一个用途就是使用user-name来计算哈希键以标识一个特定用户的数据,然而此功能应该谨慎使用。一个更好的替代方案是基于session来哈希某个对象。
vcl_hash运行到最后会return到lookup,lookup并不是subroutine,而只是一个操作。vcl_hash之后进入哪个subroutine处理,取决于lookup在缓存中找到了什么。
当lookup没有匹配到任何哈希键,它会创建一个具有busy标志的对象并把它扔到缓存区。之后跳到vcl_miss处理http请求,当http请求被后端处理后,缓存对象内容就会被后端返回的内容更新,同时busy标志也会被移除。
若一个请求命中了有busy标志的缓存对象,则该请求会被送到waiting list,waiting list是为了提高响应性能而设计的。

--------------------------------

vcl_hit
lookup若匹配到哈希键,则会跳转到vcl_hit。vcl_hit的默认代码如下:
sub vcl_hit {
  if (obj.ttl >= 0s) {
  return (deliver);
  }
  if (obj.ttl + obj.grace > 0s) {
  return (deliver);
  }
  return (fetch);
}

vcl_hit执行到最后一般都是return: deliver,restart或synth。
deliver会使处理流程跳转到vcl_deliver,如果该对象的ttl+grace没有过时的话。

restart是把http请求扔到状态机的入口当作一个新的http请求重新处理,同时restart counter这个计数器加一,还记得存放log的内存分为两部分吗?所有的counter都存储在第一部分。当restart counter的值高于 max_restarts 时,varnish会抛出一个guru meditation错误。(max_restarts是一个参数,可以通过varnishadm param.show max_restarts查看其值)

synth(status_code,reason)会丢弃本次request,并返回一个指定的http状态码给客户端。

--------------------------------

vcl_miss
lookup若没有匹配到哈希键,则会跳转到vcl_miss。
vcl_miss中可以加入代码,以决定是否去后端server请求资源,去哪台后端server请求资源。
vcl_miss的默认代码如下:
sub vcl_miss {
  return (fetch);
}

我们很少在vcl_hit和vcl_miss这两个subroutine中添加自己的处理逻辑,因为对http请求头部的修改通常都是在vcl_recv中完成的。然而,如果不想让X-Varnish这个http头部发送给后端server的花,可以在vcl_miss或vcl_pass中通过 unset bereq.http.x-varnish; 来实现。

--------------------------------

vcl_deliver
通常来说,对于一个http请求流程,vcl_deliver都是最后一个处理动作。但是经由vcl_pipe代理转发到后端server的请求除外。 该subroutine常常被用来删除debug-headers。
vcl_deliver的默认代码如下:
sub vcl_deliver {
  return (deliver);
}

如果我们想修改响应给客户端的报文头部,如删除或增加一个新头部且不想改动后被缓存,则可以在此操作。在vcl_deliver中我们常常用到 rest.http.* , resp.status , resp.reason , obj.hits , req.restarts 

------------------------------

vcl_synth
生成含有指定内容的http响应报文,并通过return(deliver)发送给客户端。vcl_synth的默认代码如下:
sub vcl_synth {
  set resp.http.Content-Type = "text/html; charset=utf-8";
  set resp.http.Retry-After = "5";
  synthetic( {"<!DOCTYPE html>
            <html>
               <head>
                  <title>"} + resp.status + " " + resp.reason + {"</title>
               </head>
               <body>
                  <h1>Error "} + resp.status + " " + resp.reason + {"</h1>
                  <p>"} + resp.reason + {"</p>
                  <h3>Guru Meditation:</h3>
                  <p>XID: "} + req.xid + {"</p>
                  <hr>
                  <p>Varnish cache server</p>
               </body>
           </html>            "} );
  return (deliver);
}
解释:设置http头部,然后调用synthetic()函数合成一个页面,然后通过return到deliver把页面发送到客户端。我们可以通过在指定的subroutine中return(synth(status_code,"reason_phrase")); 来调用vcl_synth并设置resp.http.status和resp.http.reason。需要注意的是这里return的不是keyword,而是一个内置的具有参数的函数。
{“ 和 ”}用来指示多行字符串
vcl_synth定义的页面对象不会被缓存,而vcl_backend_error定义的页面对象会被缓存。


default.vcl:

probe backend_healthcheck {   #健康状况监测
        .url = "/test1.html";
        .timeout   = 1s;
        .interval  = 10s;
        .window    = 5;
        .threshold = 2;
}
backend web1 {   #创建后端主机
    .host = "192.168.50.138";
    .port = "80";
    .probe = backend_healthcheck;
}
backend web2 {
    .host = "192.168.50.139";
    .port = "80";
    .probe = backend_healthcheck;
}

import directors;
sub vcl_init {  #创建后端主机组,基于round_robin轮转
    new web_cluster = directors.round_robin();
    web_cluster.add_backend(web1);
    web_cluster.add_backend(web2);
}

acl purgers {  #定义PURGE方法访问来源IP
    "localhost";
    "127.0.0.1";
    "192.168.50.0"/24;
}

sub vcl_recv {
    if (req.url ~ "test.html") {  #测试页面不缓存
    return(pass);
    }
    if (req.method == "PURGE") { #当发送PURGE请求的客户端不再acl中指定地址时,返回405状态代码,并提示Not allowed.
       if (!client.ip ~ purgers) {
          return(synth(405,"Not allowed"));
       }
       return(hash);
    }
    if (req.http.X-Forward-For) { #为后端主机添加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.backend_hint = web_cluster.backend();
    return(hash);
}

sub vcl_hit { #如果请求的是PURGE方法,命中的话返回200状态码。
    if (req.method == "PURGE") {
    return(synth(200,"Purged"));
    }
}

sub vcl_miss {
    if (req.method == "PURGE") {
    return(synth(404,"Not in cache"));
    }
}

sub vcl_pass {
    if (req.method == "PURGE") {
    return(synth(502,"PURGE on a passed object"));
    }
}

sub vcl_backend_response {
    if (bereq.url ~ "\.(jpg|jpeg|gif|png)$") {
    set beresp.ttl = 6000s;
    }
    if (bereq.url ~ "\.(html|css|js)$") {
    set beresp.ttl = 6000s;
    }
    if (beresp.http.Set-Cookie) {
    return(deliver);
    }
}
sub vcl_deliver {  
        if (obj.hits >0) {   #判断请求的资源是否缓存命中
                set resp.http.X-Cache="Hit from"+" "+server.ip;
        }
        else {
                set resp.http.X-Cache="Miss from"+" "+server.ip;
        }
}

参考:点击打开链接 

          点击打开链接


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值