Varnish是一款高性能的开源HTTP加速器,挪威最大的在线报纸 Verdens Gang 使用3台Varnish代替了原来的12台Squid,性能比以前更好。作者Poul-Henning Kamp是FreeBSD的内核开发者之一,他认为现在的计算机比起1975年已经复杂许多。在1975年时,储存媒介只有两种:内存与硬盘。但现在计算机系统的内存除了主存外,还包括了CPU内的L1、L2,甚至有L3快取。硬盘上也有自己的快取装置,因此Squid Cache自行处理物件替换的架构不可能得知这些情况而做到最佳化,但操作系统可以得知这些情况,所以这部份的工作应该交给操作系统处理,这就是 Varnish cache设计架构。
Varnish项目是2006年发布的第一个版本0.9.距今已经有十多年了,此文档之前也提过varnish还不稳定,那是2007年时候编写的,经过varnish开发团队和网友们的辛苦耕耘,现在的varnish已经很健壮。很多门户网站已经部署了varnish,并且反应都很好,甚至反应比squid还稳定,且效率更高,资源占用更少。相信在反向代理,web加速方面,varnish已经有足够能力代替squid。
Varnish与一般服务器软件类似,分为master(management)进程和child(worker,主要做cache的工作)进程。master进程读入命令,进行一些初始化,然后fork并监控child进程。child进程分配若干线程进行工作,主要包括一些管理线程和很多woker线程。
针对文件缓存部分,master读入存储配置(-s file[,path[,size[,granularity]]] ),调用合适的存储类型,然后创建/读入相应大小的缓存大文件。接着,master初始化管理该存储空间的结构体。这些变量都是全局变量,在fork以后会被child进程所继承(包括文件描述符)。
在child进程主线程初始化过程中,将前面打开的存储大文件整个mmap到内存中(如果超出系统的虚拟内存,mmap失败,进程会减少原来的配置mmap大小,然后继续mmap),此时创建并初始化空闲存储结构体,挂到存储管理结构体,以待分配。
接着,真正的工作开始,Varnish的某个负责接受新HTTP连接的线程开始等待用户,如果有新的HTTP连接过来,它总负责接收,然后叫醒某个等待中的线程,并把具体的处理过程交给它。Worker线程读入HTTP请求的URI,查找已有的object,如果命中则直接返回并回复用户。如果没有命中,则需要将所请求的内容,从后端服务器中取过来,存到缓存中,然后再回复。
分配缓存的过程是这样的:它根据所读到object的大小,创建相应大小的缓存文件。为了读写方便,程序会把每个object的大小变为最接近其大小的内存页面倍数。然后从现有的空闲存储结构体中查找,找到最合适的大小的空闲存储块,分配给它。如果空闲块没有用完,就把多余的内存另外组成一个空闲存储块,挂到管理结构体上。如果缓存已满,就根据LRU机制,把最旧的object释放掉。
释放缓存的过程是这样的:有一个超时线程,检测缓存中所有object的生存期,如果超初设定的TTL(Time To Live)没有被访问,就删除之,并且释放相应的结构体及存储内存。注意释放时会检查该存储内存块前面或后面的空闲内存块,如果前面或后面的空闲内存和该释放内存是连续的,就将它们合并成更大一块内存。
整个文件缓存的管理,没有考虑文件与内存的关系,实际上是将所有的object都考虑是在内存中,如果系统内存不足,系统会自动将其换到swap空间,而不需要varnish程序去控制。
Varnish特点与Squid的对比
Varnish特点:
基于内存缓存,重启后数据将消失。
利用虚拟内存方式,I/O性能好。
支持设置0~60秒内的精确缓存时间。
VCL(全称varnish config language,这是Varnish自己领域的特定语言)配置管理比较灵活。
32位机器上缓存文件大小为最大2G。
具有强大的管理功能,例如top,stat,admin,list等。
状态机设计巧妙,结构清晰。
利用二叉堆管理缓存文件,达到积极删除目的。
Varnish与Squid的对比:
相同点:
都是一个反向代理服务器;
都是开源软件;
Varnish相较于Squid的优点:
Varnish的稳定性很高,两者在完成相同负荷的工作时,Squid服务器发生故障的几率要高于Varnish,因为使用Squid要经常重启;
Varnish访问速度更快,Varnish采用了“Visual Page Cache”技术,所有缓存数据都直接从内存读取,而Squid是从硬盘读取,因此Varnish在访问速度方面会更快;
Varnish可以支持更多的并发连接,因为Varnish的TCP连接释放要比Squid快,所以在高并发连接情况下可以支持更多TCP连接;
Varnish可以通过管理端口,使用正则表达式批量的清除部分缓存,而Squid是做不到的;
Squid属于是单进程使用单核CPU,但Varnish是通过fork形式打开多进程来做处理,所以是合理的使用所有核来处理相应的请求;
Varnish相较于Squid的缺点:
Varnish在高并发状态下CPU、I/O和内存等资源开销都高于Squid;
Varnish进程一旦Hang(挂起)、Crash(崩溃)或者重启,缓存数据都会从内存中完全释放,此时所有请求都会发送到后端服务器,在高并发情况下,会给后端服务器造成很大压力;
在Varnish使用中如果单个url的请求通过HA/F5(负载均衡)每次请求不同的varnish服务器中,被请求varnish服务器都会被穿透到后端,而且同样的请求会在多台服务器上缓存,也会造成Varnish的缓存的资源浪费,也会造成性能下降。
实验环境
- 192.168.1.2 CentOS 7 Varnish
-
Web1 192.168.1.3 CentOS 7 Nginx+PHP
-
Web2 192.168.1.4 CentOS 7 Nginx+PHP
-
Web3 192.168.1.5 CentOS 7 Nginx+PHP
-
Varnish官网地址:http://varnish-cache.org/
-
Yum 安装方式,https://packagecloud.io/varnishcache
-
curl -s https://packagecloud.io/install/repositories/varnishcache/varnish64/script.rpm.sh | sudo bash
-
yum -y install varnish
-
varnishd -V 查看版本信息
-
/etc/varnish #varnish工作目录
/etc/varnish/default.vcl #默认配置各Child/Cache线程的缓存策略
/usr/bin/varnishadm #varnish内置的管理工具,端口默认为 6082
/usr/bin/varnishhist #Shared Memory Log交互工具
/usr/bin/varnishlog
/usr/bin/varnishncsa
/usr/bin/varnishstat
/usr/bin/varnishtop
/usr/bin/varnishtest #内置测试工具程序
/usr/lib/systemd/system/varnish.service #varnish服务启动脚本
/usr/lib/systemd/system/varnishncsa.service #日志持久服务启动脚本 -
/usr/sbin/varnishreload #VCL配置文件重载程序
- varnish 主配置文件 /etc/varnish/default.vcl varnish使用了一种叫VCL的语言去对其进行配置
- vcl_recv 函数: 接收和处理请求,当请求到达并成功接收后被调用,判断请求的数据并决定如何处理请求;
pass 表示进入pass模式,将请求交给vcl_pass 函数
pipe 表示进入pipe模式,将请求交个vcl_pipe函数
error code[reason] 表示返回'code'给客户端并放弃请求, 'code'是错误标示例如200 405等,'reason'是错误原因
- vcl_pipe函数:该函数在进入pipe模式时被调用,用于将请求直接传递给后端主机,在请求和返回内容没有改变的情况下,将不变的内容返回给客户端,直到这个链接关闭;该函数有这么些返回值=>
error code[reason] 同vcl_recv
pipe
- vcl_pass函数: 该函数进入pass模式被调用,用于直接将请求发送给后端主机,后端主机响应后发送给客户端,不进行任何缓存,每次都返回最新内容,该函数有几个返回值=>
error code[reason] 同vcl_recv
pass
- lookup函数: 表示在缓存中查找到被请求的对象,并且根据查找的结果交给vcl_hit函数(命中)或vcl_miss(未命中)函数处理
-
vcl_hit函数:在执行lookup后,如果在缓存中找到对象,该函数将会被自动调用,该函数有以下几个返回值 =>
deliver 表示找到内容发送给客户端,把控制权交给vcl_deliver函数
error code[reason] 同vcl_recv
pass
- vcl_miss函数: 在执行lookup后,在缓存中没有找到对象,该函数被调用,该函数可用于判断是否从后端请求内容,该函数有以下几个返回值 =>
fetch 表示从后端获取内容,并把控制权交给vcl_fetch函数
error code[reason] 同vcl_recv
pass
- vcl_fetch函数:从后端主机更新缓存获取内容后调用该函数,接着判断获取的内容是放入缓存还是直接给客户端,该函数有以下几个返回值=》
error code[reason] 同vcl_recv
pass
deliver
- vcl_deliver函数:将在缓存中找到的内容发送给客户端调用的方法,该函数有以下几个返回值=》
error code[reason] 同vcl_recv
deliver
- vcl_timeout函数:在缓存内容到期前调用该函数,该函数有以下几个返回值=》
discard 表示中缓存中清除该内容
fetch
- vcl_discard函数:缓存内容到期后或者缓存空间不够时自动被调用,该函数有以下几个返回值=》
keep 表示在内容中保留该缓存
discard
- VCL处理流程可以用下面这张图表示
Receive状态:请求处理的入口状态,根据VCL规则判断该请求应该在Pass或Pipe,还是进入Lookup(到本地缓存中查询)
Lookup状态:进入此状态后,会在hash表中查询数据,如果找到则进入Hit状态,否则进入Miss状态
Pass状态:在此状态下会进入后端获取内容,既进入Fetch状态
Fetch状态:从后端获取请求,发送请求,获得数据,并存储到本地
Deliver状态:将获取的数据发送给客户端,然后完成本次请求
- VCL里面还有些变量,可以用在VCL函数中,用于逻辑处理
请求到达后可以使用的内置公用变量:
req.backend => 指定对应的后端主机
server.ip => 表示服务器端IP
client.ip => 表示客户端IP
req.request => 指定请求的类型,如GET POST PUT DELETE
req.url => 知道请求的URL
req.proto => 表示客户端发起请求的HTTP协议版本
req.http.header => 表示请求中的头部信息
req.restarts => 表示请求重启的次数,默认最大值为4
- VCL向后端主机请求时使用的内置变量
beresp.request => 指定请求的类型,如GET POST
beresp.url => 指定请求的地址
beresp.proto => 指定请求的协议版本号
beresp.http.header => 对应请求的HTTP头信息
beresp.ttl => 表示缓存的周期,单位秒
- 从cache或后端主机获取内容后可以使用的内置变量
obj.status => 表示返回的状态码,如200 404 500 等
obj.cacheable => 表示返回的内容是否可以缓存
obj.valid => 表示是否是有效的HTTP应答
obj.response => 表示应答的状态信息
obj.ttl => 表示返回内容的生存周期,单位秒
obj.lastuse => 表示上次请求到现在的间隔,单位秒
- 客户端应答时可以使用的变量
resp.status => 表示返回给客户端的状态码
resp.proto => 表示返回给客户端的HTTP协议版本
resp.http.header => 表示返回给客户端的头信息
resp.response => 表示返回给客户端的状态信息
- Varnish可以检测后端主机的健康状态,在判定后端主机失效时能自动将其从可用后端主机列表中移除,而一旦其重新变得可用还可以自动将其设定为可 用。为了避免误判,Varnish在探测后端主机的健康状态发生转变时(比如某次探测时某后端主机突然成为不可用状态),通常需要连续执行几次探测均为新 状态才将其标记为转换后的状态。
每个后端服务器当前探测的健康状态探测方法通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的Backend_health查看或varnishadm的debug.health查看。
.probe中的探测指令常用的有:
(1) .url:探测后端主机健康状态时请求的URL,默认为“/”;
(2) .request: 探测后端主机健康状态时所请求内容的详细格式,定义后,它会替换.url指定的探测方式;比如:
.request =
"GET /.healthtest.html HTTP/1.1"
"Host: www.myweb.com"
"Connection: close";
(3) .window:设定在判定后端主机健康状态时基于最近多少次的探测进行,默认是8;
(4) .threshold:在.window中指定的次数中,至少有多少次是成功的才判定后端主机正健康运行;默认是3;
(5) .initial:Varnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold;
(6) .expected_response:期望后端主机响应的状态码,默认为200;
(7) .interval:探测请求的发送周期,默认为5秒;
(8) .timeout:每次探测请求的过期时长,默认为2秒;
-
设置后台健康状态检查
-
vim /etc/varnish/health_check.vcl
-
probe chk {
.interval = 5s; # 每隔1秒钟尝试一次
.timeout = 3s; #每次探测请求的过期时长
.window = 10; # 最多尝试10次,判断采样的样本
.threshold = 8; # 如果采样10次,如果有8次是错误的,就认为是失败的,上线也是一样
.request =
"GET /.test.html HTTP/1.1" #测试根目录下的 .test.html ,这个文件必须存在且能够访问
"Host: www.myweb.com"
"Connection: close"
"Accept-Encoding: foo/bar";
} -
后端服务器地址池配置
vim /etc/varnish/backends.vcl -
import directors;
include "health_check.vcl";backend web1 {
.host = "192.168.1.3";
.port = "80";
.probe = chk;
}backend web2 {
.host = "192.168.1.4";
.port = "80";
.probe = chk;
}backend web3 {
.host = "192.168.1.5";
.port = "80";
.probe = chk;
}
## 负载均衡池
sub vcl_init {
new web = directors.round_robin(); #以轮询方式调度
web.add_backend(web1);
web.add_backend(web2);
web.add_backend(web3);
} -
缓存规则主配置
# vim /etc/varnish/default.vcl -
vcl 4.1;
import std;
include "backends.vcl";
acl purge_ip { # 定义acl访问控制,只允许以下网段或主机执行purgers操作
"127.0.0.1";
"192.168.0.0"/16;
}
sub vcl_recv {
if (req.method == "PURGE"){ # 如果请求方法是PURGE,并且客户端IP在上面定义的网段内的就允许执行PURGE操作,否则生成一个错误页面返回给用户
if (!client.ip ~ purge_ip) {
return(synth(405,"Purging not allowed for "+client.ip));
}
return(purge);
}
if (req.http.host ~ "^(www.)?myweb.com") {
set req.http.host = "www.myweb.com";
set req.backend_hint = web.backend();
}
}sub vcl_purge{
return (synth(200,"success"));
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT cache" + " " + server.ip;
}
else {
set resp.http.X-Cache = "Miss cache";
}
}sub vcl_pass {
if (req.method == "PURGE") {
return (synth(502, "PURGE on a passed object."));
}
} -
为了方便测试,将 varnish 默认启动端口 6081 更改为 80
-
编辑 vim /usr/lib/systemd/system/varnish.service
-
[Unit]
Description=Varnish Cache, a high-performance HTTP accelerator
After=network-online.target[Service]
Type=forking
KillMode=process# Maximum number of open files (for ulimit -n)
LimitNOFILE=131072# Locked shared memory - should suffice to lock the shared memory log
# (varnishd -l argument)
# Default log size is 80MB vsl + 1M vsm + header -> 82MB
# unit is bytes
LimitMEMLOCK=85983232# Enable this to avoid "fork failed" on reload.
TasksMax=infinity# Maximum size of the corefile.
LimitCORE=infinity#ExecStart=/usr/sbin/varnishd -a :6081 -f /etc/varnish/default.vcl -s malloc,256m
ExecStart=/usr/sbin/varnishd -a :80 -f /etc/varnish/default.vcl -s malloc,256m #修改默认监听端口为 :80
ExecReload=/usr/sbin/varnishreload[Install]
WantedBy=multi-user.target -
systemctl daemon-reload
-
systemctl restart varnish
- 使用 varnishadm 进行后端健康检查
- backend.list
200
Backend name Admin Probe Health Last change
boot.web1 probe 10/10 healthy Sat, 28 Mar 2020 08:03:18 GMT
boot.web2 probe 10/10 healthy Sat, 28 Mar 2020 08:01:58 GMT
boot.web3 probe 10/10 healthy Sat, 28 Mar 2020 08:01:58 GMT
boot.web probe 3/3 healthy Sat, 28 Mar 2020 08:03:18 GMT
- 编辑 /etc/hosts 添加 192.168.1.2 www.myweb.com
- curl www.myweb.com
- X-Cache: HIT cache192.168.1.2
- 清除缓存 curl -X PURGE www.myweb.com
- curl www.myweb.com
- 将其中一台的 web 服务停掉
- systemctl stop nginx
- web2 的状态为 sick 不可用
- 重新启动 web2 的nginx