分布式系统架构:技术栈详解与快速进阶
上QQ阅读APP看书,第一时间看更新

5.3 Varnish配置

Varnish状态引擎处理流程如图5-2所示。

102-1

图5-2 Varnish状态引擎处理流程图

对图5-2所示的流程分析如下。

vcl_recv:用于接收并处理用户请求,当接收到一个完整的请求时,它会检查并分析是否可以为这个请求服务,判断请求的数据并决定如何处理请求。vcl_recv可以选择多个策略,如1.pipe、2.lookup、3.pass。它会将控制权传递给下游,如vcl_pipe、vcl_hash、vcl_pass。

  • 1.1 vcl_pipe:不会缓存数据,其会进入pipe模式,由管道后端处理数据,直到管道处理完毕后关闭。
  • 2.1 vcl_hash:缓存数据,通过hash处理机制,默认URL可作为key,key的方式可自定义调整,同时可以根据客户端是否区分压缩数据而进一步判断是否存储缓存。
  • 2.2 vcl_hit:一个请求从缓存中命中需要的内容。
  • 2.3 vcl_miss:一个请求从缓存中未命中需要的内容。
  • 2.4/2.5 vcl_pass:对命中或未命中的数据提供数据处理功能。
  • 2.8 vcl_fetch:从后端服务器获得请求目标数据。
  • 2.9 deliver:从后端服务器获得数据后,根据策略检查是否需要缓存起来。
  • 3.1/3.2 vcl_deliver:内容返回给客户端。

对图5-2所示流程中涉及的函数介绍如下。

  • pipe:将请求交给vcl_pipe函数。error code[reason]表示返回code给客户端并放弃请求,code是错误标示,例如200、405等,reason是错误原因。
  • vcl_pipe:该函数在进入pipe模式时被调用,用于将请求直接传递给后端主机,在请求和返回内容没有改变的情况下,将不变的内容返回给客户端,直到这个链接关闭。
  • vcl_pass:该函数在进入pass模式时被调用,用于直接将请求发送给后端主机,后端主机响应后发送给客户端,不进行任何缓存,每次都返回最新内容。
  • vcl_hash:可缓存数据,通过hash机制处理,默认将URL作为key;也可以自定义,根据客户端是否支持处理压缩数据来区分缓存。
  • lookup:在缓存中查找被请求的对象,并且根据查找的结果交给vcl_hit函数(命中)或vcl_miss函数(未命中)处理。
  • vcl_hit:在执行lookup后,如果在缓存中命中对象,该函数将会被自动调用。
  • deliver:表示找到内容并发送给客户端,把控制权交给vcl_deliver函数。
  • vcl_miss:在执行lookup后,如果缓存中没有命中对象,该函数会被调用,可用于判断是否从后端请求内容。
  • fetch:表示从后端获取内容,并把控制权交给vcl_fetch函数。
  • vcl_fetch:从后端主机更新缓存并获取内容后调用该函数,接着判断获取的内容是放入缓存还是直接给客户端。
  • vcl_deliver:将在缓存中找到的内容发送给客户端调用的方法。

下面介绍Varnish的配置文件(default.vcl),如代码清单5-3所示。

代码清单5-3 default.vcl配置文件

probe tz1{
    .url="/demo/xxxx.index.html";    // 检查后端健康页面
    .timeout=0.3s;                   // 过期时间
    .window=8;                       // 检查后端服务次数
    .threshod=3;                     // 检查后端8次访问,若成功3次则认为服务是存活的
    .initial=3;                      // Varnish启动,确保多少个probe正常
    .expected_response=200;          // 期望expected code,默认是200
    .interval=6;                     // 定义probe多久检查一次后端,默认为5s
 }

 backend zachary{
    .host="127.0.0.1";
    .port="2222";
    .connect_timeout=1s;
    .first_byte_timeout=5s;
    .between_byte.timeout=2s;
    .max_connections=1000;
    .probe=tz1;
 }

 backend resource{
    .host="127.0.0.1";
    .port="8099";
    .connect_timeout=2s;             // 定义等待连接后端的时间
    .first_byte_timeout=5s;          // 定义等待从backend传输过来的第一个字节的时间
    .between_byte.timeout=2s;        // 定义两个字节的间隔时间
 }

 director zachary random{            // 随机
    .retries=5;                      // 查找可用后端次数
    {
        .backend=zachary;            // 引用已存在的backend
        .weight=6;
    }
    {
        .backend=resource;           // 引用已存在的backend
        .weight=2;                   // 类似Nginx权重
    }
    {
        .backend={                   // 定义新的backend

        }
        .weight=2;
    }
 }

 director zachary round-robin{       // 轮询
    {
        .backend=zachary;            // 引用已存在的backend

    }
    {
        .backend=resource;           // 引用已存在的backend

    }
    {
        .backend={                   // 定义新的backend
        }

    }
 }

 #权限访问控制列表
 acl purgeallow {
    "127.0.0.1";
    !"192.168.0.102"
 }

 sub vcl_recv{

    if(!req.backend.healthy){
        set req.grace=30m;           // Varnish缓存时间+50分钟返回给客户端
    }else{
        set req.grace=5s;
    }

    if (req.url ~ "\.(css|js|html|htm|bmp|png|gif|jpg|jpeg|ico|gz|tgz|bz2|t
        bz|zip|rar|mp3|mp4|ogg|swf|flv)($|\?)") {
        unset req.http.cookie;
        return (hash);
    }
    if(req.request== "PURGE"){
        (!client.ip ~ purgeallow){
            error 405 "not allowed";
        }
        return(lookup);
    }

    if (req.restarts == 0) {
        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.http.Cache-Control ~ "(?i)no-cache") {
       if (!(req.http.Via || req.http.User-Agent ~ "(?i)bot" || req.http.X-Purge)) {
           return (purge);
       }
    }
if(req.http.host ~"^(www.)?zachary.cn$"){
        set req.backnd=zachary;
    }

    if(req.request== "GET" && req.url ~ "\.(jpg|png|gif|swf|flv|ico|jpeg)$"){
        unset req.http.cookie;
    }

    if(req.request== "GET" && req.url ~ "(?i)\.jsp($|\?)"){
        set req.backnd=resource;
        return pass(pass);
    }
 }

 sub vcl_fetch{

    set beresp.grace=30m;              // 后端服务器返回Varnish缓存时间+50分钟
    if(req.request== "GET" req.url ~ "\.(jpg|png|gif|swf|flv|ico|jpeg)$"){
        set beresp.ttl=1d;
    }

    if(req.url ~ "^.*/zachary/demo/.*"){
        set beresp.ttl=1d;
        return(deliver);
    }

 }

 sub vcl_hit{
    std.log("url hit,your need to check it; it's url ="+req.url);
    if(req.request=='PURGE'){
        set obj.ttl=0s;                // 清除缓存
        error 200 "Purged.";
    }
    return(fetch);
 }

 sub vcl_miss{
    std.log("url miss,your need to check it; it's url ="+req.url);
    if(req.request=='PURGE'){
        error 200 "Purged.";
    }

    return(fetch);
 }

对上述代码中的重点流程介绍如下。

1)1个请求对应1个backend,可配置连接后端服务。其中,connect_timeout表示连接后端超时时间,first_byte_timeout表示传输第一个字节的时间,between_byte.timeout表示第二个和第一个中间传输所用的时间,max_connections表示连接后端服务的最大限制数。

2)backend有多种配置策略,如随机、循环、DNS等,可参考代码清单5-3中的相关配置。

3)probe用于配置健康检查。

4)acl用于设置权限列表,可配置相关IP。

当多个客户端请求同时访问一个页面时,Varnish只会发送一次请求到后端,其他请求会被挂起以等待返回结果,体验较差。当服务器请求流量高时,比如在秒杀活动中、同时产生数千万点击率时等,用户不可能挂起等待结果。

针对以上问题,Varnish提供了Grace模式来延长缓存失效时间,即上次过期数据结果在失效时间之后延期多长时间,具体时间需根据系统斟酌设置,当后端服务器出现问题时,负载过高后,Varnish不访问后端直接返回旧缓存数据到客户端。

VCL返回策略:

  • return(pass):不缓存,直接调用服务器。
  • return(lookup):先从缓存获取,缓存中没有数据再从服务器获取。
  • return(pipe):当前连接未关闭前,所有的请求都直接由服务器处理,Varnish不处理请求。
  • return(deliver):请求目标被缓存,然后返回客户端。

5.4 Varnish核心指令

5.4.1 Varnish核心指令之backend

backend是用于定义后端服务器的子例程。其具体使用方法如代码清单5-4所示。

代码清单5-4 backend定义子例程

backend zachary{
    .host="127.0.0.1";   // 指明后端主机
    .port="2222";
    .connect_timeout=1s;
    .first_byte_timeout=5s;
    .between_byte.timeout=2s;
    .max_connections=1000;
    .probe=tz1;
 }

 backend resource{
    .host="192.168.0.1"; // 指明后端主机
    .port="8099";
    .connect_timeout=1s;
    .first_byte_timeout=5s;
    .between_byte.timeout=2s;

 }

注意

Varnish允许定义多个backend后端服务器。