这里采用的是apt-get安装
#apt-get install varnish
修改配置文件 /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的规则来就行选中后端的具体服务器:
(1) Receive状态,也就是请求处理的入口状态,根据VCL规则判断该请求应该是Pass或Pipe,或者进入Lookup(本地查询)。
(2) Lookup状态,进入此状态后,会在hash表中查找数据,若找到,则进入Hit状态,否则进入miss状态。
(3) Pass状态,在此状态下,会进入后端请求,即进入fetch状态。
(4) Fetch状态,在Fetch状态下,对请求进行后端的获取,发送请求,获得数据,并进行本地的存储。
(5) Deliver状态, 将获取到的数据发送给客户端,然后完成本次请求。
(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
公用变量名称 含义
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状态信息
通过启动脚本可以看到,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 启动用户组
#service varnish start
由于使用了varnish缓存做代理,所以nginx的反向代理也需要相应调整,只需调整upstream即可:如下:
upstream tornado {
server 127.0.0.1:6081;
}
多点击刷新几次可以,看到已经缓存命中:
几个重要指标:
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%
1 通过自带的varnishlog指令可以获得varnish详细的系统运行日志。
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
由于时间关系,对于日志管理先写到这(有时间可以加强这一块)
清除所有缓存:
#/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>";
?>