5 缓存加速器varnish安装

5.1 安装

这里采用的是apt-get安装

#apt-get install varnish

5.2 修改主配置

修改配置文件 /etc/varnish/default.vcl

# This is a basic VCL configuration file for varnish.  See the vcl(7)

# man page for details on VCL syntax and semantics.

#

# Default backend definition.  Set this to point to your content

# server.

#

backend tornado8001 {

   .host = "127.0.0.1";

   .port = "8001";

   .probe = {

     .timeout = 5s;        

     .interval = 2s;          

     .window = 10;        

     .threshold = 8;  

   }

}

backend tornado8002 {

   .host = "127.0.0.1";

   .port = "8002";

   .probe = {

     .timeout = 5s;        

     .interval = 2s;          

     .window = 10;        

     .threshold = 8;  

   }

}

backend tornado8003 {

   .host = "127.0.0.1";

   .port = "8003";

   .probe = {

     .timeout = 5s;        

     .interval = 2s;          

     .window = 10;        

     .threshold = 8;  

   }

}

backend tornado8004 {

   .host = "127.0.0.1";

   .port = "8004";

   .probe = {

     .timeout = 5s;        

     .interval = 2s;          

     .window = 10;        

     .threshold = 8;  

   }

}

director yxm random

       { .retries = 4;

           { .backend = tornado8001;

             .weight = 4;

          }

          { .backend = tornado8002;

              .weight = 4;

           }

           { .backend = tornado8003;

             .weight = 4;

            }

           { .backend = tornado8004;

              .weight = 4;

           }

       }

acl purge {

       "localhost";

       "127.0.0.1";

   }

#

# Below is a commented-out copy of the default VCL logic.  If you

# redefine any of these subroutines, the built-in logic will be

# appended to your code.

sub vcl_recv {

if (req.request == "PURGE") {

       if (!client.ip ~ purge) {

       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.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);

}

if (req.request == "GET" && req.url ~ "\.(py)($|\?)") {

       set req.backend = yxm;

    }

    if (req.http.Authorization || req.http.Cookie) {

        /* Not cacheable by default */

        return (pass);

    }

    return (lookup);

}

#

sub vcl_pipe {

#     # Note that only the first request to the backend will have

#     # X-Forwarded-For set.  If you use X-Forwarded-For and want to

#     # have it set for all requests, make sure to have:

#     # set bereq.http.connection = "close";

#     # here.  It is not set by default as it might break some broken web

#     # applications, like IIS with NTLM authentication.

    return (pipe);

}

#

sub vcl_pass {

    return (pass);

}

#

sub vcl_hash {

    hash_data(req.url);

    if (req.http.host) {

        hash_data(req.http.host);

    } else {

        hash_data(server.ip);

    }

    return (hash);

}

#

sub vcl_hit {

    return (deliver);

}

sub vcl_miss {

    return (fetch);

}

sub vcl_fetch {

    if (beresp.ttl <= 0s ||

        beresp.http.Set-Cookie ||

        beresp.http.Vary == "*") {

               /*

                * Mark as "Hit-For-Pass" for the next 2 minutes

                */

               set beresp.ttl = 120 s;

               return (hit_for_pass);

    }

if (req.request == "GET" && req.url ~ "\.(js|css|mp3|jpg|png|gif|swf|jpeg|ico)$")

        { set beresp.ttl = 7d; }

    return (deliver);

}

#

sub vcl_deliver {

    set resp.http.x-hits = obj.hits ;

      if (obj.hits > 0)

             { set resp.http.X-Cache = "HIT cqtel-bbs"; }

      else { set resp.http.X-Cache = "MISS cqtel-bbs"; }

}

#

# sub vcl_error {

#     set obj.http.Content-Type = "text/html; charset=utf-8";

#     set obj.http.Retry-After = "5";

#     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>

#     <p>Varnish cache server</p>

#   </body>

# </html>

# "};

#     return (deliver);

# }

#

sub vcl_init {

       return (ok);

}

sub vcl_fini {

       return (ok);

}

说明:根据官方文档得知,在转发后端请求时,如果配置服务器组,会线将请求转发到dirctor,然后根据director的规则来就行选中后端的具体服务器:

5.3 原理:

spacer.gif

(1) Receive状态,也就是请求处理的入口状态,根据VCL规则判断该请求应该是Pass或Pipe,或者进入Lookup(本地查询)。

(2) Lookup状态,进入此状态后,会在hash表中查找数据,若找到,则进入Hit状态,否则进入miss状态。

(3) Pass状态,在此状态下,会进入后端请求,即进入fetch状态。

(4) Fetch状态,在Fetch状态下,对请求进行后端的获取,发送请求,获得数据,并进行本地的存储。

(5) Deliver状态, 将获取到的数据发送给客户端,然后完成本次请求。

5.4 VCL内置函数:

(1)vcl_recv函数

用于接收和处理请求,当请求到达并成功接收后被调用,通过判断请求的数据来决定如何处理请求。

此函数一般以如下几个关键字结束:

q pass:表示进入pass模式,把请求控制权交给vcl_pass函数。

q pipe:表示进入pipe模式,把请求控制权交给vcl_pipe函数。

q error code [reason]:表示返回“code”给客户端,并放弃处理该请求,“code”是错误标识,例如200、405等,“reason”是错误提示信息。

(2)vcl_pipe函数

此函数在进入pipe模式时被调用,用于将请求直接传递至后端主机,在请求和返回的内容没有改变的情况下,将不变的内容返回给客户端,直到这个链接关闭。

此函数一般以如下几个关键字结束:

q error code [reason]

q pipe

(3)vcl_pass函数

此函数在进入pass模式时被调用,用于将请求直接传递至后端主机,后端主机应答数据后送给客户端,但不进行任何缓存,在当前连接下每次都返回最新的内容。

此函数一般以如下几个关键字结束:

q error code [reason]

q pass

(4)lookup

表示在缓存里查找被请求的对象,并且根据查找的结果把控制权交给函数vcl_hit或者函数vcl_miss。

(5)vcl_hit函数

在执行lookup指令后,如果在缓存中找到请求的内容,将自动调用该函数。

此函数一般以如下几个关键字结束:

q deliver:表示将找到的内容发送给客户端,并把控制权交给函数vcl_deliver。

q error code [reason]

q pass

(6)vcl_miss函数

在执行lookup指令后,如果没有在缓存中找到请求的内容时自动调用该方法,此函数可以用于判断是否需要从后端服务器取内容。

此函数一般以如下几个关键字结束:

q fetch:表示从后端获取请求的内容,并把控制权交给vcl_fetch函数。

q error code [reason]

q pass

(7)vcl_fetch函数

在从后端主机更新缓存并且获取内容后调用该方法,接着,通过判断获取的内容来决定是否将内容放入缓存,还是直接返回给客户端。

此函数一般以如下几个关键字结束:

q error code [reason]

q pass

q deliver

(8)vcl_deliver函数

在缓存中找到请求的内容后,发送给客户端前调用此方法。此函数一般以如下几个关键字结束:

q error code [reason]

q deliver

(9)vcl_timeout 函数

此函数在缓存内容到期前调用。一般以如下几个关键字结束:

q discard:表示从缓存中清除该内容。

q fetch

(10)vcl_discard函数

在缓存内容到期后或缓存空间不够时,自动调用该方法,一般以如下几个关键字结束:

q keep:表示将内容继续保留在缓存中。

q discard

5.5 VCL内置全局变量:

公用变量名称    含义

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               在向后端主机请求时,可以使用的公用变量如所示:

公用变量名称 含义

beresp.request 指定请求的类型,例如GET、HEAD等

beresp.url 指定请求的地址

beresp .proto 表示客户端发起请求的HTTP协议版本

beresp .http.header 表示对应请求中的http头部信息

beresp .ttl 表示缓存的生存周期,也就是cache保留多长时间,单位是秒

从cache或者后端主机获取内容后,可以使用的公用变量如所示:

公用变量名称 含义

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 表示返回上一次请求到现在的间隔时间,单位是秒

对客户端应答时,可以使用的公用变量如所示:

公用变量名称 含义

resp.status 表示返回给客户端的HTTP状态代码

resp.proto 表示返回给客户端的HTTP协议版本

resp.http.header 表示返回给客户端的HTTP头部信息

resp.response 表示返回给客户端的HTTP状态信息

5.6 副配置文件调整

通过启动脚本可以看到,varnish在启动的时候会调用启动参数文件 /etc/default/varnish,调整如下:

DAEMON_OPTS="-a :6081 \

            -T localhost:6082 \

                -n /var/varnish_dir \

            -f /etc/varnish/default.vcl \

            -S /etc/varnish/secret \

            -s file,/var/varnish_dir/varnish_cache.data,5120m \

            -u root \

            -g root"

-a 程序监听端口

-T 程序管理端口

-f 程序启动定义的VCL规则文件

-S 加密文件

-s file(将缓存在swap上)|malloc(缓存在内存上),缓存文件名,缓存大小

-u 启动用户名

-g 启动用户组

5.7 启动程序:

#service varnish start

由于使用了varnish缓存做代理,所以nginx的反向代理也需要相应调整,只需调整upstream即可:如下:

upstream tornado {

       server 127.0.0.1:6081;

       }

5.8访问测试:

spacer.gif

多点击刷新几次可以,看到已经缓存命中:

5.9 Varnish 状态分析:

spacer.gif

几个重要指标:

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

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

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

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

q “N struct object”表示当前被缓存的数量。

q “N expired objects”表示过期的缓存内容数量。

q “N LRU moved objects”表示被淘汰的缓存内容个数。

从上面的结果可以看到缓存命中率:31/38*100%=81%

5.10 管理varnish日志:

1 通过自带的varnishlog指令可以获得varnish详细的系统运行日志。

spacer.gif

2 通过自带的varnishncsa指令得到类似apache的combined输出格式的日志。

root@debian:/etc/logrotate.d# varnishncsa -n /var/varnish_dir/ -w /var/log/varnish/varnish.log

^C

root@debian:/etc/logrotate.d# cat /var/log/varnish/varnish.log

127.0.0.1 - - [12/Sep/2013:13:51:05 +0800] "GET http://10.15.62.202/ HTTP/1.0" 304 0 "-" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"

127.0.0.1 - - [12/Sep/2013:13:51:06 +0800] "GET http://10.15.62.202/ HTTP/1.0" 304 0 "-" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"

127.0.0.1 - - [12/Sep/2013:13:51:07 +0800] "GET http://10.15.62.202/ HTTP/1.0" 304 0 "-" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"

127.0.0.1 - - [12/Sep/2013:13:51:07 +0800] "GET http://10.15.62.202/ HTTP/1.0" 304 0 "-" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"

root@debian:/etc/logrotate.d#

下面编写一个名为varnishncsa的shell脚本,每小时生成,

#!/bin/sh  

if [ "$1" = "start" ];then  

/usr/local/varnish/bin/varnishncsa -n /data/varnish/cache  -f |/usr/sbin/rotatelogs /data/varnish/log/varnish.%Y.%m.%d.%H.log 3600 480 &  

elif [ "$1" = "stop" ];then  

     killall varnishncsa  

else  

               echo $0 "{start|stop}"  

fi  

由于时间关系,对于日志管理先写到这(有时间可以加强这一块)

5.11 管理varnish缓存

清除所有缓存:

#/usr/bin/varnishadm -T 127.0.0.1:6082ban.url  ^.*$

直观查看varnish的命中率,这里在网上找到了一个php的页面:

<?php

// This is just a code snippet written by Jason "Foxdie" Gaunt, its not meant to be executed, it may work as-is, it may not.  

// I freely acknowledge this code is unoptimised but it has worked in practice for 6 months :)  

// Lets define our connection details  

$adminHost = "127.0.0.1"; // varnish服务器的IP地址  

$adminPort = "3500"; // varnish服务器管理端口  

// pollServer(str) - this function connects to the management port, sends the command and returns the results, or an error on failure  

function pollServer($command) {  

global $adminHost, $adminPort;  

$socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname("tcp"));  

if ((!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, Array("sec" => "5", "usec" => "0"))) OR (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, Array("sec" => "5", "usec" => "0"))))  {  

 die("Unable to set socket timeout");  

}  

if (@socket_connect($socket, $adminHost, $adminPort)) {  

 $data = "";  

 if (!$socket) {  

  die("Unable to open socket to " . $server . ":" . $port . "\n");  

 }  

 socket_write($socket, $command . "\n");  

 socket_recv($socket, $buffer, 65536, 0);  

 $data .= $buffer;    

 socket_close($socket);  

 return $data;  

}  

else {  

 return "Unable to connect: " . socket_strerror(socket_last_error()) . "\n";  

}  

}  

// byteReduce(str) - this function converts a numeric value of bytes to a human readable format and returns the result  

function byteReduce($bytes) {  

// Terabytes  

if ($bytes > 1099511627776) {  

 return round($bytes / 1099511627776) . "TB";  

}  

else if ($bytes > 1073741824) {  

 return round($bytes / 1073741824) . "GB";  

}  

else if ($bytes > 1048576) {  

 return round($bytes / 1048576) . "MB";  

}  

else if ($bytes > 1024) {  

 return round($bytes / 1024) . "KB";  

}  

else {  

 return $bytes . "B";  

}  

}  

// This is where our main code starts  

echo "<div class=\"inner\"><br />Statistics since last reset:<ul>";  

$stats = pollServer("stats");  

if (substr($stats, 0, 3) == "200") { // If request was legitimate  

// Clear all excessive white space and split by lines  

$stats = preg_replace("/ {2,}/", "|", $stats);  

$stats = preg_replace("/\n\|/", "\n", $stats);  

$statsArray = explode("\n", $stats);  

// Removes the first call return value and splits by pipe  

array_shift($statsArray);  

$statistics = array();  

foreach ($statsArray as $stat) {  

 @$statVal = explode("|", $stat);  

 @$statistics[$statVal[1]] = $statVal[0];  

}  

unset($stats, $statsArray, $stat, $statVal);  

// Start outputting statistics  

echo "<li>" . $statistics["Client connections accepted"] . " clients served over " . $statistics["Client requests received"] . " requests";  

echo "<li>" . round(($statistics["Cache hits"] / $statistics["Client requests received"]) * 100) . "% of requests served from cache";  

echo "<li>" . byteReduce($statistics["Total header bytes"] + $statistics["Total body bytes"]) . " served (" . byteReduce($statistics["Total header bytes"]) . " headers, " . byteReduce($statistics["Total body bytes"]) . " content)";  

// The following line is commented out because it only works when using file storage, I switched to malloc and this broke  

// echo "<li>" . byteReduce($statistics["bytes allocated"]) . " out of " . byteReduce($statistics["bytes allocated"] + $statistics["bytes free"]) . " used (" . round(($statistics["bytes allocated"] / ($statistics["bytes allocated"] + $statistics["bytes free"])) * 100) . "% usage)";  

}  

else {  

echo "Unable to get stats, error was: \"" . $stats;  

}  

echo "</ul></div>";  

?>