高性能缓存服务器Varnish解析
文/刘鑫
Varnish是一款高性能、开源的反向代理服务器和缓存服务器,其开发者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内核上运行。
Varnish的结构特点
Varnish的先进设计理念和成熟的设计框架是其主要特点。Varnish把数据存放在服务器的内存中,这种模式的效率是最高的,不过重启后数据会消失,官方透露3.0版本可以解决这个问题。Varnish可以设置0~60秒的精确缓存时间,不过32位的机器支持的缓存文件最大为2 GB。Varnish采用VCL的配置,而且具有强大的管理功能,如top、stat、admin、lis,所以管理方式比较灵活。Varnish的状态机设计不仅巧妙,结构也很清晰,利用二叉堆管理缓存文件,即可达到随时删除的目的。
Varnish和Squid的对比
Squid 也是一种开源的代理缓存软件,下面对比 Varnish 和 Squid 的不同点。
Varnish的稳定性很好。两者在完成相同负载的工作时,Squid服务器发生故障的几率要高于Varnish,因此Squid需要经常重启。Varnish访问速度更快。Varnish采用了 Visual Page Cache技术,所有缓存的数据都直接从内存读取,而Squid从硬盘读取缓存的数据,所以Varnish在访问速度方面会更快一些。Varnish可以支持更多的并发连接。因为Varnish的TCP连接与释放比Squid快,所以在高并发连接情况下可以支持更多的TCP连接。Varnish可以通过管理端口来管理缓存,使用正则表达式就可以批量清除部分缓存,而Squid做不到这一点。
当然,和传统的Squid相比,Varnish也有缺点。
Varnish在高并发状态下,CPU、I/O和内存等资源的开销高于Squid。Varnish的进程一旦挂起、崩溃或者重启,缓存的数据都会从内存中释放出来。此时的所有请求都会被发送到后端应用服务器上,在高并发的情况下,就会给后端服务器造成很大压力。
Varnish实践部署
Varnish编译安装
首先需要建立Varnish用户以及用户组来运行Varnish,并且创建Varnish缓存目录和日志目录。
useradd -s /sbin/nologin varnish
mkdir /data/varnish/cache
mkdir /data/varnish/log
chown -R varnish:varnish /data/varnish/cache
chown -R varnish:varnish /data/varnish/log
Varnish的官方网址为 http://varnish-cache.org,可以在这里下载最新版本的软件。在安装Varnish前需要安装PCRE库。如果没有安装该库,在Varnish 2以上版本编译时,就会提示找不到PCRE库。PCRE库则可以兼容正则表达式,所以必须先安装。下面介绍其安装过程。
tar zxvf pcre-XXX.tar.gz
cd pcre-XXX/
./configure –prefix=/usr/local/pcre/
make && make install
安装完PCRE库以后,接下来安装Varnish。
tar -zxvf varnish-2.1.X.tar.gz
cd varnish-2.1.X
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
./configure -prefix=/app/soft/varnish -enable-debugging-symbols -enable- deve loper-warnings -enable-dependency-tracking
make
make install
cp redhat/varnish.initrc /etc/init.d/varnish
cp redhat/varnish.sysconfig /etc/sysconfig/varnish
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig 这一行一定要有,不然在编译的时候会报错。这一行用于指定Varnish查找PCRE库的路径,如果PCRE安装到其他路径下,在这里指定即可,Varnish默认查找PCRE库的路径为/usr/local/lib/pkgconfig。
最后面的两行是复制Varnish的相关脚本,用于脚本的初始化、启动、停止等。
Varnish安装完毕。
Varnish配置文件详解
Varnish需要在多台服务器上缓存数据,就需要Varnish映射所有的URL到一台单独的主机。
backend webserver {
.host = ”127.0.0.1″;
.port = ”80″;
.connect_timeout = 4s;
.first_byte_timeout = 5s;
.between_bytes_timeout = 20s;
}
该块配置用于定义一台Varnish默认访问的后端服务器,当Varnish需要从后端服务器获取数据时,就会访问自己的80端口。
当然Varnish也可以定义多台后端服务器实现负载均衡的目的。
.connect_timeout定义的是等待连接后端的时间
.first_byte_timeout定义的是等待从backend传输过来的第一个字节的时间
.between_bytes_timeout 定义的是两个字节的间隔时间
当然还可以增加一个backend,用于访问本机的8090端口,假设通过该端口提供图片服务。
backend img {
.host = ”127.0.0.1″;
.port = ”8090″;
}
当匹配img的URL时,需把请求发送到上面定义的backend img,其他的请求发送到backend webserver。
sub vcl_recv {
if (req.url ~ ”^/img/”) {
set req.backend = img;
} else {
set req.backend = webserver.
}
}
Varnish不仅仅可以定义多个backend,还可以把多个backend合成一个组,使用循环的方式把请求分配给组中的backends。并且Varnish会根据健康检查情况来判断后端服务器是否正常提供服务。
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)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 ~ ”^/images”) {
unset req.http.cookie;
}
这条规则的意思是清除服务器上/images目录下的所有缓存,当这个请求在后端服务器生效时,如果访问的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);
}
最后这个模块定义了访问错误页面时的返回信息。
现在varnish配置基本完成,可以在8080端口上启动varnish,并进行一些基本的测试。
Varnish启动等管理工具
一般情况下,启动Varnish的命令为:
varnishd -f /etc/varnish/default.vcl -s malloc,2G -T 127.0.0.1:2000 -a 0. 0.0.0:8080
-f / etc/varnish/default.vcl
–f选项用于指定Varnishd使用的配置文件的路径。
-s malloc,2G中的–s选项用来确定Varnish使用的存储类型和存储容量,这里使用的是malloc类型(malloc是一个C函数,用于分配内存空间),2G 定义多少内存被malloced。
-T 127.0.0.1:2000是Varnish基于文本方式的一个管理接口,启动后可以在不停止Varnish的情况下来管理Varnish。管理端口2000可以指定。因为不是任何人都可以访问Varnish管理端口,所以这里推荐只监听本机端口。
-a 0.0.0.0:80中-a选项表示Varnish监听所有IP发给80端口的HTTP请求。
varnishd -n /app/soft/varnish/cache -f /app/soft/varnish/etc/varnish/vcl.conf -a 0.0.0.0:80 -s file,/app/soft/varnish/cache/varnish_cache.data,5G -u daemon -w 2,65536,60 -T 127.0.0.1:3600 -p thread_pool_min=200 -p thread_pool_max=4000 -p thread_pools=4 -p thread_pool_add_delay=2 -p listen_depth=4096 -p lru_interval=20
varnishncsa -n /data/apps/varnish/cache -a -w /var/log/vlogs &启动varnishncsa用来将Varnish访问日志写入日志文件。
-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管理地址及其端口
现在Varnish已经正常运行,以上主要解释了使用内存作为存储方式启动命令行,也是比较常用的一种方式。接下来我们来看一下有哪些常用的工具。
Varnishtop
这个工具用于读取共享内存的日志,适当使用一些过滤选项如–I,-i,-X和-x,可以连续不断地显示大部分普通日志。Varnishtop可以按照使用要求显示请求的内容、客户端、浏览器等一些其他日志里的信息。比如:
使用varnishtop -i rxurl查看客户端请求的url次数;
使用Varnishtop -i txurl查看请求后端服务器的url次数;
使用Varnishtop -i Rxheader –I Accept-Encoding查看接收到的头信息中有多少次包含
Accept-Encoding。
Varnishhist
用于读取Varnishd共享内存段的日志,并生成一个连续的柱状图。Varnishhist用于显示最后N个请求的处理情况。如果缓存命中则标记“|”,如果缓存没有命中则标记“#”符号。
Varnishsizes
Varnishsizes和Varnishhist相似,可以查看服务对象的大致大小。
Varnishstat
用于查看Varnish计数丢失率、命中率、存储信息、创建线程、删除对象等。
FAQ
Q:某些HTML页面的http头信息中常带有no-cache头,如何缓存?
A:常规的配置无法实现缓存,需要修改Varnish配置文件,要去掉http头信息中的里no-cache头,修改如下内容:
sub vcl_fetch {
if (req.url ~ ”html$”) {
set beresp.ttl = 10m;
set beresp.do_gzip = true;
unset beresp.http.Cache-Control;
unset beresp.http.Pragma;
set beresp.http.Cache-Control = ”max-age=60″;
unset beresp.http.Expires;
}
}
如果html页面带有cookie,还需要在sub vcl_recv { } 配置中添加如下内容:
sub vcl_recv {
if (req.request == ”GET” && req.url ~ ”\.(js|css|html|jpg|png|gif|swf|jpeg| ico)$”) {
unset req.http.cookie;
}
}
Q:可以在32位机器上运行Varnish吗?
A:可以,不过32位机器不支持大于2GB的文件存储,所以推荐使用64位机器。
Q:Varnish可以做正向代理吗?
A:不可以,Varnish需要配置所有后端服务器到VCL。
Q:怎样做才能在后端服务器记录客户端的IP地址?
A:这是缓存服务器常常遇到的问题。X-Forwarded-For的相关解释已经在前面讲述,只要在后面的Web服务器日志格式中加上相关参数即可。Apache的日志格式定义类似,为Log Format ”%{X-Forwarded-For}i %l %u %t /”%r/” %>s %b /”%{Referer}i/” /”% {User-Agent}i/”" varnishlog
CustomLog /var/log/apache2.log varnishlog
Q:Varnish可以加速https吗?
A:目前还不行,欲知相关信息请密切关注官方网站。
Q:可以查看Varnish缓存了哪些内容吗?
A:目前不可能,如果一个命令列出所有缓存的内容,那么缓存的内容是上千万或者更多,这样会导致因系统资源紧张而使Varnish暂停服务。
刘鑫,运维工程师,从事IT培训、技术支持、高性能网站架构等相关技术的研究工作,ChinaUnix社区集群和高可用版块资深版主。
本文节选自《高性能网站构建实战》一书,部分内容有删减。刘鑫著,由人民邮电出版社出版。