Varnish基础与示例
Varnish 是一款高性能且开源的反向代理服务器和 HTTP 加速器,其采用全新的软件体系机构,和现在的硬件体系紧密配合。
Varnish Configuration Language(VCL)是varnish配置缓存策略的工具,它是一种基于“域”(domain specific)
的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、
允许用户使用set自定义变量、支持if判断语句,也有内置的函数和变量等.使用VCL编写的缓存策略通常保存至.vcl文件中,
其需要编译成二进制的格式后才能由varnish调用。
varnish的程序环境:
/etc/varnish/varnish.params: 配置varnish服务进程的工作特性,例如监听的地址和端口,缓存机制;
/etc/varnish/default.vcl:配置各Child/Cache线程的缓存策略;
主程序:
/usr/sbin/varnishd
CLI interface:
/usr/bin/varnishadm
Shared Memory Log交互工具:
/usr/bin/varnishhist
/usr/bin/varnishlog
/usr/bin/varnishncsa
/usr/bin/varnishstat
/usr/bin/varnishtop
测试工具程序:
/usr/bin/varnishtest
VCL配置文件重载程序:
/usr/sbin/varnish_reload_vcl
Systemd Unit File:
/usr/lib/systemd/system/varnish.service varnish服务
/usr/lib/systemd/system/varnishlog.service 日志服务;
/usr/lib/systemd/system/varnishncsa.service 日志持久的服务;
Varnish.params文件参数说明:
#自动重新装载缓存策略,1表示自动装载
RELOAD_VCL=1
#默认的缓存策略文件路径
VARNISH_VCL_CONF=/etc/varnish/default.vcl
#监听的本机地址与端口
VARNISH_LISTEN_ADDRESS=172.16.253.190
VARNISH_LISTEN_PORT=80
#管理员登录时使用的主机与端口
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082
#管理员登录时使用的密钥文件
VARNISH_SECRET_FILE=/etc/varnish/secret
#缓存类型和大小 可使用malloc,file:使用file类型缓存文件属主属组应为varnish
VARNISH_STORAGE="file,/etc/varnish/cachedir,1G"
#运行时的用户与组
VARNISH_USER=varnish
VARNISH_GROUP=varnish
#运行时参数,可定义线程数等等,-p后可添加运行参数及其值
DAEMON_OPTS="-p thread_pools=3 -p thread_pool_min=50 -p thread_pool_max=2000"
Default.vcl文件暂不说明,示例时会有说明
varnish架构及文件缓存的工作流程
Varnish 处理 HTTP 请求的过程大致分为如下几个步骤。
1,Receive 状态(vcl_recv):也就是请求处理的入口状态,根据 VCL 规则判断该请求应该 pass(vcl_pass)
或是pipe(vcl_pipe),还是进入 lookup(本地查询)。
2,Lookup 状态:进入该状态后,会在 hash 表中查找数据,若找到,则进入 hit(vcl_hit)状态,否则进入 miss(vcl_miss)状态。
3,Pass(vcl_pass)状态:在此状态下,会直接进入后端请求,即进入 fetch(vcl_fetch)状态
4,Fetch(vcl_fetch)状态:在 fetch 状态下,对请求进行后端获取,发送请求,获得数据,并根据设置是否进行本地存储。
5,Deliver(vcl_deliver)状态:将获取到的数据发给客户端,然后完成本次请求。
VCL 内置公共变量
VCL 内置的公共变量可以用在不同的 VCL 函数中,下面根据使用的不同阶段进行介绍
当请求到达时,可以使用以下公共变量
req.backend 指定对应的后端主机
server.ip 表示服务器 IP
client.ip 表示客户端 IP
req.quest 请求的类型,例如 GET、HEAD 等
req.url 指定请求的地址
req.proto 表示客户端发起请求的 HTTP 协议版本
req.http.header 表示对应请求中的 HTTP 头部信息
req.restarts 表示重启次数,默认最大值为 4
req.http.Cookie:客户端的请求报文中Cookie首部的值
req.http.User-Agent:客户端浏览器类型
req.http.host : 客户端主机名称
Varnish 在向后端主机请求时,可以使用以下公共变量
bereq.http.HEADERS :表示对应请求中 HTTP 头部信息
bereq.request:请求方法;
bereq.url:请求的url;
bereq.proto:请求的协议版本;
bereq.backend:指明要调用的后端主机;
Varnish 在向后端主机请求返回响应时,可以使用以下公共变量
beresp.requset 指定请求类型,例如 GET、HEAD 等
beresp.url 表示请求地址
beresp.backend.name:BE主机的主机名;
beresp.status:响应的状态码;
beresp.proto:表示backend server HTTP 协议版本
beresp.http. HEADERS:从backend server 响应报文指定首部
beresp.ttl 表示缓存的生存周期,cache 保留时间(s)
从 cache 或是后端主机获取内容后,可以使用以下公共变量
obj.status 返回内容的请求状态码,例如 200、302、504 等
obj.cacheable 返回的内容是否可以缓存
obj.valid 是否有效的 HTTP 请求
obj.response 返回内容的请求状态信息
obj.proto 返回内容的 HTTP 版本
obj.hits:此对象从缓存中命中的次数
obj.ttl 返回内容的生存周期,也就是缓存时间,单位秒
obj.lastuse 返回上一次请求到现在的时间间隔,单位秒
对客户端应答时,可以使用以下公共变量
resp.status 返回给客户端的 HTTP 代码状态
resp.proto 返回给客户端的 HTTP 协议版本
resp.http.header 返回给客户端的 HTTP 头部消息
resp.response 返回给客户端的 HTTP 头部状态
请求/响应报文所在阶段对应的varnish变量
在default.vcl文件中进行示例操作:(后端服务器指向172.16.252.205:80)
示例1:强制对某类资源的请求不检查缓存:
为方便查看是否命中缓存,需在vcl_deliver中添加以下内容:
在vcl_recv中添加以下内容:
使用varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082进入varnish交互模式,操作如下
请求url包含admin字符串,则将请求传递给pass去后端提起响应内容,因为从未命中,obj.hits为0,返回miss from。
示例2:对于特定类型的资源,例如公开的图片等,取消其私有标识,并强行设定其可以由varnish缓存的(s-maxage:其项在公共缓存中生命周期)
载入配置,使用配置,浏览器测试:
示例3:禁止curl进行访问(禁止某个类型浏览器访问)
示例4:修剪缓存对象purge
使用其他主机访问:
使用172.16.253.190主机进行访问(-X:指定请求类型):
修剪完成后,再次请求:
示例5:修剪缓存对象ban
使用其他主机测试:
示例6:使用多后端主机实现负载均衡
vcl 4.0;
import directors; 导入模块
probe check { 健康检查参数
.url = "/"; 检查的路径
.window = 8; 检查次数
.threshold = 4; 最小健康次数
.interval = 2s; 检查频率2秒一次
.timeout = 1s; 超时时长
}
backend default { 定义后端主机
.host = "172.16.252.205";
.port = "80";
.connect_timeout = 0.5s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
.max_connections = 50;
.probe = check;
}
backend pic {
.host = "172.16.253.145";
.port = "80";
.connect_timeout = 0.5s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
.max_connections = 50;
.probe = check;
}
sub vcl_init {
new grouphost = directors.round_robin();
grouphost.add_backend(default);
grouphost.add_backend(pic);
}
sub vcl_recv {
set req.backend_hint = grouphost.backend();
}
示例7:根据cookie值选择后端主机
vcl 4.0;
import directors; 导入模块
probe check { 健康检查参数
.url = "/"; 检查的路径
.window = 8; 检查次数
.threshold = 4; 最小健康次数
.interval = 2s; 检查频率2秒一次
.timeout = 1s; 超时时长
}
backend default { 定义后端主机
.host = "172.16.252.205";
.port = "80";
.connect_timeout = 0.5s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
.max_connections = 50;
.probe = check;
}
backend pic {
.host = "172.16.253.145";
.port = "80";
.connect_timeout = 0.5s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
.max_connections = 50;
.probe = check;
}
sub vcl_init {
new grouphost = directors.hash();
grouphost.add_backend(default,1);
grouphost.add_backend(pic,1);
}
sub vcl_recv {
set req.backend_hint = grouphost.backend(req.http.cookie);
}
下面是个简单的配置:
# configure
vcl 4.0;
import directors;
probe check {
.url = "/";
.window = 8;
.threshold = 4;
.interval = 2s;
.timeout = 1s;
}
backend default {
.host = "172.16.252.205";
.port = "80";
.connect_timeout = 0.5s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
.max_connections = 50;
.probe = check;
}
backend pic {
.host = "172.16.253.145";
.port = "80";
.connect_timeout = 0.5s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
.max_connections = 50;
.probe = check;
}
sub vcl_init {
new grouphost = directors.round_robin();
grouphost.add_backend(default);
grouphost.add_backend(pic);
}
acl purgers {
"localhost";
"127.0.0.1";
"172.16.253.190";
}
sub vcl_recv {
set req.backend_hint = grouphost.backend();
if (req.url ~ "(?i)^/admin") {
return(pass);
}
if (req.method == "PURGE"){
if (client.ip !~ purgers) {
return (synth(444,"Not enough authority to " + client.ip));
}
return(purge);
}
if (req.method == "BAN") {
if (client.ip !~ purgers) {
return (synth(444,"Not enough authority to " + client.ip));
}
ban("req.http.host == " + req.http.host + " && req.url == " + req.url);
return(synth(200,"Ban Added"));
}
/* if (req.http.User-Agent ~ "(?i)curl") {
return (synth(405,"No good"));
}
*/
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;
}
}
}
sub vcl_backend_response {
if (beresp.http.cache-control !~ "s-maxage") {
if (bereq.url ~ "(?i)\.(jpg|jpeg|gif|png|css|js)$") {
unset beresp.http.Set_Cookie;
set beresp.ttl = 7200s;
}
}
}
sub vcl_deliver {
if (obj.hits>0) {
set resp.http.X-Cache = "HIT from " + server.ip;
} else {
set resp.http.X-Cache = "MISS from " + server.ip;
}
}