1 Varnish简介


Varnish是高性能且开源的反向代理服务器和HTTP加速器(cache server)。其开发者Poul-Henning Kamp是FreeBSD核心的开发人员之一。Varnish采用全新的软件体系结构,和现在的硬件体系配合比较紧密。

当前计算机系统的内存除了主存外,还包括CPU的L1级缓存、L2级缓存,甚至还包括L3级缓存。硬盘也有缓存,而Squid的架构导致其无法做到最佳存取,但操作系统可以实现这部分功能,所以这部分工作应该交给操作系统来处理,这就是Varnish Cache设计架构。挪威最大的在线报纸Verdens Gang(vg.no)使用了3台Varnish服务器代替了原来的12台Squid服务器,而且性能比以前更好,这是Varnish最成功的应用案例之一。目前,Varnish可以在FreeBSD6.0/7.0、Solaris和Linux 2.6内核上运行。


2 Varnish的结构特点


Varnish把数据存放在服务器的内存中,这种模式的效率是最高的,不过重启后数据会消失,官方透露3.0版本可以解决这个问题。Varnish可以设置0~60秒的精确缓存时间,不过32位的机器支持的缓存文件最大为2 GB。Varnish采用VCL的配置,而且具有强大的管理功能,如top、stat、admin、lis,所以管理方式比较灵活。Varnish的状态机设计不仅巧妙,结构也很清晰,利用二叉堆管理缓存文件,即可达到随时删除的目的。


与传统的Squid 相比,Varnish具有性能更高、速度更快、管理更加方便等诸多优点:

  • Varnish采用了“Visual Page Cache”技术,所有缓存的数据都直接从内存读取,而Squid从硬盘读取缓存的数据,它避免了Squid频繁在内存、磁盘中交换文件,性能要比Squid高。

  • Varnish稳定性比Squid高,宕机率很低。

  • 通过Varnish管理端口,可以使用正则表达式快速、批量地清除部分缓存,这一点是Squid不能具备的。

  • Varnish可以支持更多的并发连接。因为Varnish的TCP连接与释放比Squid快,所以在高并发连接情况下可以支持更多的TCP连接。


不足:Varnish在高并发状态下,CPU、I/O和内存等资源的开销高于Squid。Varnish的进程一旦挂起、崩溃或者重启,缓存的数据都会从内存中释放出来。此时的所有请求都会被发送到后端应用服务器上,在高并发的情况下,就会给后端服务器造成很大压力。


3 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 程序去控制。



Varnish工作流程图如下:

wKioL1NNOh2D5SwcAAVgD9k6EVY051.jpg


4 Varnish实践部署


4.1 Varnish编译安装


1. 安装前准备:

建立Varnish用户及用户组来运行Varnish,并创建Varnish缓存目录和日志目录,赋予varnish用户拥有者权限:

#useradd -s /sbin/nologin varnish

#mkdir -p /var/varnish/cache

#mkdir -p /var/varnish/log

#chown -R varnish.varnish /var/varnish/cache

#chown -R varnish.varnish /var/varnish/log


安装依赖包:

automake、autoconf、libtool、ncurses-devel、pkgconfig、readline-devel

注解:不安装报错:error:readline/readline.h: No such file or directory)


2. 编译安装pcre:Varnish使用pcre来兼容正则表达式

#tar xvf pcre-8.33.tar.bz2; cd pcre-8.33

#./configure --prefix=/usr/local/pcre

#make && make install


3. 编译安装Varnish

#tar zxvf varnish-3.0.4.tar.gz

#cd varnish-3.0.4

#export PKG_CONFIG_PATH=/usr/local/pcre/lib/pkgconfig

#./configure --prefix=/usr/local/varnish --enable-debugging-symbols --enable-developer-warnings --enable-dependency-tracking

#make && make install

注解:export PKG_CONFIG_PATH=/usr/local/pcre/lib/pkgconfig这一行用于指定Varnish查找PCRE库的路径,如果PCRE安装到其他路径下,在这里指定即可,Varnish默认查找PCRE库的路径为/usr/local/lib/pkgconfig。


4. 配置: /usr/local/varnish/etc/varnish/default.vcl

Varnish需要在多台服务器上缓存数据,就需要Varnish映射所有的URL到一台单独的主机。

backend webserver {

   .host = ”127.0.0.1″;

   .port = ”80″;

.connect_timeout = 4s;

.first_byte_timeout = 5s;

.between_bytes_timeout = 20s;

}

注解:

1 该配置用于定义一台Varnish默认访问的后端服务器,当Varnish需要从后端服务器获取数据时,就会访问自己的80端口。当然Varnish也可以定义多台后端服务器实现负载均衡的目的。

2 .connect_timeout定义的是等待连接后端的时间

3 .first_byte_timeout定义的是等待从backend传输过来的第一个字节的时间

4 .between_bytes_timeout 定义的是两个字节的间隔时间

5 Varnish不仅仅可以定义多个backend,backend表示Varnish要加速的服务器。还可以把多个backend合成一个组,使用循环的方式把请求分配给组中的backends并且Varnish会根据健康检查情况来判断后端服务器是否正常提供服务。


5. 启动Varnish

/usr/local/varnish/sbin/varnishd -f /usr/local/varnish/etc/varnish/default.vcl -s file,/var/varnish/cache.data,1G -T 127.0.0.1:2000 -a 0.0.0.0:80


启动varnishncsa用来将Varnish访问日志写入日志文件:

/usr/local/varnish/bin/varnishncsa -n /var/varnish/cache -w /var/varnish/log/cache.log &


varnishd命令主要的选项和参数:

-a address:port # Varnishd命令用于指定监听的地址及其端口

-b address:port #命令用于指定后台服务器地址及其端口

-d # 使用debug模式

-f file # varnishd服务器存取规则文件

-F # 在后台运行

-P file # PID文件

-p param=value # 服务器参数,用来优化性能

-s kind[,storageoptions] # 缓存内容存放方式

-s file使用文件做为缓存,其路径、大小等

-T address:port # telnet管理地址及其端口


6. 配置开机自启

#vim /etc/rc.local

#add flowing entry at the bottom

/usr/local/varnish/sbin/varnishd -f /usr/local/varnish/etc/varnish/default.vcl -s file,/var/varnish/cache.data,1G -T 127.0.0.1:2000 -a 0.0.0.0:80

/usr/lo

net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_keepalive_time = 300

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.ip_local_port_range = 5000   65000

cal/varnish/bin/varnishncsa -n /var/varnish/cache -a -w /var/varnish/log/cache.log &

注解:需要考虑文件描述符的问题:“ulimit”


7. 优化Linux内核参数

vim /etc/sysctl.conf

在末尾增加以下内容:

net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_keepalive_time = 300

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.ip_local_port_range = 5000   65000


4.2 Varnish常用工具


1. varnishtop

这个工具用于读取共享内存的日志,适当使用一些过滤选项如–I,-i,-X和-x,可以连续不断地显示大部分普通日志。varnishtop可以按照使用要求显示请求的内容、客户端、浏览器等一些其他日志里的信息。比如:

使用varnishtop -i rxurl :查看客户端请求的url次数;

使用Varnishtop -i txurl:查看请求后端服务器的url次数;

使用Varnishtop -i Rxheader –I Accept-Encoding:查看接收到的头信息中有多少次包含Accept-Encoding。


2. varnishhist

用于读取Varnishd共享内存段的日志,并生成一个连续的柱状图。Varnishhist用于显示最后N个请求的处理情况。如果缓存命中则标记“|”,如果缓存没有命中则标记“#”符号。


3. varnishsizes

VarnishsizesVarnishhist相似,可以查看服务对象的大致大小。


4. varnishstat

用于查看Varnish计数丢失率、命中率、存储信息、创建线程、删除对象等。


5 Varnish使用语言VCL


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)Roulette  ist ein sehr geselliges Spiel, alle halten gleicherma?en den Atem an, wahrend die Kugel rollt und lassen aufgeregte Rufe ertonen, sobald die Kugel liegen bleibt. New Roman;”>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);

}

这两个规则定义了允许哪些主机通过HTTP来执行PURG进行缓存删除。如果不是指定的IP,就会出现HTTP 405错误,提示Not allowed错误字样。


       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 ~ ”^/p_w_picpaths”) {

unset req.http.cookie;

}

这条规则的意思是清除服务器上/p_w_picpaths目录下的所有缓存,当这个请求在后端服务器生效时,如果访问的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 req.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);

}

最后这个模块定义了访问错误页面时的返回信息。

                                                                                                                               ——游响云停