Varnish 进阶配置与实例
注意:使用varnishadm命令的前提是在varnish的default.vcl文件所在目录下
yum install varnish
cd /etc/varnish
cp default.vcl{,.bak}
cp varnish.params{,.bak}
vim varnish.params
RELOAD_VCL=1
VARNISH_VCL_CONF=/etc/varnish/default.vcl
VARNISH_LISTEN_PORT=6081
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082
VARNISH_SECRET_FILE=/etc/varnish/secret
#VARNISH_STORAGE="malloc,256M"
VARNISH_STORAGE="file,/date/varnish/cache,1g"
VARNISH_USER=varnish
VARNISH_GROUP=varnish
#DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300"
- vcl语法格式及特点
配置文件必须以 “vcl 4.0” 开头
注释方法:
单行注释:”// ” 或 “#”
多行注释:”/*foo*/”
所有subroutine(子例程)必须经由sub关键字声明,如:sub_vcl_recv{…}
vcl不支持循环,且受限于状态引擎相关的内建变量,各内建变量有自己的使用上下文
状态引擎转换的实现:每个状态引擎需要以 “return(ACTION)” 结束并指明下一步应执行的状态引擎
所有配置都是区域专用的,一般只应用于某一个域内
所有自定义的规则都是附加在内建的默认规则之上的,且默认规则不可修改。
三类语法
sub subroutine {
…
}if CONDITION {
…
}return(ACTION),hash_data()
VCL Built-in Functions(内建函数) and Keywords(关键字)
函数:
regsub(str, regex, sub) :正则表达式替换
regsuball(str, regex, sub) :正则表达式全局替换
ban(boolean expression) :
hash_data(input) :对数据进行hash运算
synthetic(str) :自定义合成响应报文
Keywords:
call subroutine, return(action),new,set,unset
操作符: ==, !=, ~, >, >=, <, <= 逻辑操作符:&&, ||, ! 变量赋值:=
变量类型:
内建变量:
req.*:request,表示由客户端发来的请求报文相关;
req.http.* req.http.User-Agent, req.http.Referer, ...
bereq.*:由varnish发往BE主机的httpd请求相关;
bereq.http.*
beresp.*:由BE主机响应给varnish的响应报文相关;
beresp.http.*
resp.*:由varnish响应给client相关;
obj.*:存储在缓存空间中的缓存对象的属性;只读;
常用变量:
bereq.*, req.*:
bereq.http.HEADERS bereq.request:请求方法; bereq.url:请求的url; bereq.proto:请求的协议版本; bereq.backend:指明要调用的后端主机; req.http.Cookie:客户端的请求报文中Cookie首部的值; req.http.User-Agent ~ "chrome"
beresp.*, resp.*:
beresp.http.HEADERS beresp.status:响应的状态码; reresp.proto:协议版本; beresp.backend.name:BE主机的主机名; beresp.ttl:BE主机响应的内容的余下的可缓存时长;
obj.*
obj.hits:此对象从缓存中命中的次数; obj.ttl:对象的ttl值
server.*
server.ip server.hostname
client.*
client.ip
用户自定义:
setunset
varnish的缓存相关功能示例解析
1. 使用内建变量 obj.hits 该变量用于保存缓存项从缓存中命中的次数,在响应报文的首部 X-cache 一项中显示是否命中缓存以及经由哪个服务器的IP地址
sub vcl_deliver {
if (obj.hits>0) {
set resp.http.X-cache = "Hit via " + server.ip;
} else {
set resp.http.X-cache = "Miss from " + server.ip;
}
#测试结果
第一次访问172.16.50.1默认页面,缓存未命中:X-cache:Miss via 172.16.50.1
第二次访问172.16.50.1默认页面,缓存命中 :X-cache:Hit via 172.16.50.1
2. 对URL中含有admin或login等关键字可能为私密的信息不缓存
#在后端web服务器上创建admin和login两个资源页面
mkdir /web/nginx/html/vhost1/{admin,login}
vim /web/nginx/html/vhost1/admin/index.html
#添加访问路径作为内容
/web/nginx/html/vhost1/admin/index.html
vim /web/nginx/html/vhost1/login/index.html
#添加访问路径作为内容
/web/nginx/html/vhost1/login/index.html
- 配置缓存策略
#在sub vcl_recv {} 配置段中添加如下内容
if (req.url ~* "(?i)^/(admin|login)") {
return(pass);
}
- 使用varnishadm命令加载缓存策略使其生效
注意:varnishadm要在缓存策略配置文件所在目录下才能正常编译策略
cd /etc/varnish
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
#进入交互模式
vcl.list
vcl.load test1 default.vcl
vcl.use test1
- 测试结果:URL中以 http://172.16.50.1:6081/admin 下的资源不使用缓存,在响应报文首部的 X-cache 条目中总会显示 Miss 。
3. 定义访问控制策略
- 停止向curl浏览器提供资源访问服务
#在sub vcl_recv {}配置段中添加一下内容并使用varnishadm命令加载该缓存策略
if (req.http.User-Agent ~* "(?i)curl") {
return(synth(403));
}
4. 根据请求响应资源的类型来定义缓存策略
后端服务器经常会对一些不会变动的静态资源(比如一些图片)在其首部中设定cookie,从而会让varnish误认为该资源是私有资源,无法存在公共缓存中,导致资源响应较慢.
- 我们可以根据资源的类型来定义是否将其加入公共缓存从而提高用户访问体验
缓存策略如下:
#在sub vcl_backend_response {} 配置段中增加:
if (beresp.http.Cache-Control !~ "(?i)s-maxage") {
if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)") {
unset beresp.http.Set-Cookie;
set beresp.ttl = 3600s;
}
}
#使用varnishadm加载缓存策略并使其生效
正则表达式的使用方法:
格式: VAR [!]~ “(?i)REG_EXPR”
VAR :变量
[!]~ :(不)匹配
(?i) :忽略大小写
REG-EXPR :正则表达式内容
5. 修改日志格式
- 修改后端服务器记录日志的访问者IP为客户端的真实IP
#在sub vcl_recv {} 配置段中添加:
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;
}
}
修改后端服务器记录的日志格式,
如果是httpd,将LofFormat中的 “%h” 修改为 “%{X-Forwarded-For}i”
如果是nginx,将log_format 中的 “$remote_addr” 修改为 “$X-Forwarded-For”
实现缓存修剪功能——方法1
通过设置缓存策略,通过自定义客户端请求方法(如:使用系统内置method “PURGE” 或 自定义method “BAN” ) 来实现修剪缓存功能
#在sub vcl_recv () 配置段中增加:
if (req.method == "PURGE") {
return(purge);
}
#使用varnishadm加载缓存策略并使其生效
#假设我们使用curl来修剪缓存,需要将上文中设置的curl浏览器限制给注释掉
/*
if (req.http.User-Agent ~ "(?i)curl") {
return(synth(403));
}
*/
使用curl测试修剪缓存:
#连续访问两次,检查缓存是否命中
curl -I http://172.16.50.1:6081/
#显示访问结果
X-cache: Hit via 172.16.50.1
#缓存命中后修剪该缓存
curl -X PURGE http://172.16.50.1:6081/
#显示结果
<!DOCTYPE html>
<html>
<head>
<title>200 Purged</title>
</head>
<body>
<h1>Error 200 Purged</h1>
<p>Purged</p>
<h3>Guru Meditation:</h3>
<p>XID: 36</p>
<hr>
<p>Varnish cache server</p>
</body>
</html>
#此时再次正常访问,发现缓存已经Miss
curl -I http://172.16.50.1:6081
X-cache: Miss from 172.16.50.1
注意:修剪缓存功能具有强大破坏性,要做权限控制
# 因为"vcl 4.0" 一行必须在文件最前端,可以在其下面来定义acl组(可以为多个),仅允许本机或者192.168.50.0网段内的用户可以修改缓存
acl purgers {
"127.0.0.1"/8;
"192.168.50.0"/24;
}
修改刚刚定义的PURGE功能配置段,增加访问权限控制功能:
#依然在sub vcl_recv{}配置段中:修改刚才的内容如下:
if (req.method == "PURGE") {
if (!client.ip ~ purgers) {
return(synth(405,"Purging not allow for " + client.ip));
}
return(purge);
}
#重新加载缓存策略并使其生效
实现缓存修剪功能——方法2
使用varnishadm命令中的ban子命令实现缓存修剪
- 将web服务器的资源根目录下的javascripts目录下的所有文件全部从缓存中清除(假设要被清除的文件已经在缓存中并且可以被命中)
cd /etc/varnish
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
ban req.url ~ ^/javascripts
#加载缓存策略文件并使其生效
- 将所有资源中以 “.js” 结尾的文件全部从缓存中清除
cd /etc/varnish
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
ban req.url ~ .js
#加载缓存策略文件并使其生效
- 使用ban命令的方法可以通过限制varnishadm命令的执行权限来控制缓存修剪功能
几个危险操作:
==不慎清空缓存后需要长时间的热身才能恢复热区缓存==
cd /etc/varnish
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
- 清空所有缓存
ban req.url == /
- 清空一个指定域名的所有缓存
ban req.url == / && req.http.host ~ "achudk.com"
varnish的反向代理和负载均衡等相关功能示例解析
定义多个后端主机并实现负载均衡功能实例
- 对多台后端主机实现负载均衡的步骤
第一,定义多个后端主机;
第二,将后端主机划分成组;
第三,调用定义的组。
- 注意:负载均衡功能需要调用 directors 模块
- 注意:每个不同的IP和不同的端口为一个单独的后端服务器,如下面后端主机示例
#vcl版本必须在文件最前面
vcl 4.0;
#导入directors模块
import directors;
#定义多个后端主机
backend websrv1 {
.host = "192.168.50.13";
.port = "80";
}
backend websrv2 {
.host = "192.168.50.13";
.port = "80";
}
#将后端主机分组,
sub vcl_init {
#分组取名为 websrvs ,调度方式为轮巡
new websrvs = directors.round_robin();
websrvs.add_backend(websrv1);
websrvs.add_backend(websrv2);
#在vcl_recv配置段里的最前面增加调用分组
sub vcl_recv {
set req.backend_hint = websrvs.backend();
set req.backend_hint = imgsrvs.backend();
...
...
}
#加载配置策略并使其生效
- 测试
for ((i=1;i<=10;i++)); do curl http://172.16.50.1:6081; curl -X PURGE http://172.16.50.1:6081 &> /dev/null;done
#测试结果
Web Server 2 --> 192.168.50.14:80
Web Server 2 --> 192.168.50.14:8080
Web Server 1 --> 192.168.50.13:80
Web Server 1 --> 192.168.50.13:8080
Web Server 2 --> 192.168.50.14:80
Web Server 2 --> 192.168.50.14:8080
Web Server 1 --> 192.168.50.13:80
Web Server 1 --> 192.168.50.13:8080
Web Server 2 --> 192.168.50.14:80
Web Server 2 --> 192.168.50.14:8080
根据请求资源类型的不同实现动静分离响应
#vcl版本必须在文件最前面
vcl 4.0;
#导入directors模块
import directors;
#定义多个后端主机
backend websrv1 {
.host = "192.168.50.13";
.port = "80";
}
backend websrv2 {
.host = "192.168.50.13";
.port = "80";
}
backend imgsrv1 {
.host = "192.168.50.14";
.port = "80";
}
backend imgsrv2 {
.host = "192.168.50.14";
.port = "8080";
}
backend appsrv1 {
.host = "192.168.50.15";
.port = "80";
}
backend appsrv2 {
.host = "192.168.50.15";
.port = "8080";
}
#将后端主机分组,
sub vcl_init {
#第一组取名为 websrvs ,调度方式为轮巡
new websrvs = directors.round_robin();
websrvs.add_backend(websrv1);
websrvs.add_backend(websrv2);
#第二组取名为 imgsrvs ,调度方式为随机,权重分别为2和1
new igmsrvs = directors.random();
imgsrvs.add_backend(imgsrv1,2);
imgsrvs.add_backend(imgsrv2,1);
}
#第三组取名为 appsrvs ,调度方式为随机,权重分别为2和1
new igmsrvs = directors.random();
imgsrvs.add_backend(appsrv1,2);
imgsrvs.add_backend(appsrv2,1);
}
#在vcl_recv配置段里的最前面增加调用分组
sub vcl_recv {
if (req.url ~ "(?i)\.(php|php5)$") {
set req.backend_hint = appsrvs.backend();
}
if (req.url ~ "(?i)\.(jpg|jpeg|png|gif)$") {
set req.backend_hint = imgsrvs.backend();
}
set req.backend_hint = websrvs.backend();
...
...
}
#加载配置策略并使其生效
基于cookie实现session sticky(会话粘性)
#假设有两个后端主机分别为srv1和srv2
sub vcl_init {
#分组取名为 cookie ,调度方式为基于cookie实现会话粘性,权重分别为2和1
new cookie = directors.hash();
websrvs.add_backend(srv1,2);
websrvs.add_backend(srv2,1);
#调用分组
sub vcl_recv {
set req.backend_hint = cookie.backent(req.http.cookie);
...
...
}
#加载策略并使其生效
对后端服务器进行健康状态监测
先定义一个健康状态监测的默认配置段,在每个backend server {} 中调用该默认配置
如果某个backend server中的检测方法与默认配置不同,在可以在本server配置段中定义来覆盖默认配置
方法1 :对某一backend server单独定义检测标准
backend websrv1 {
.host = "192.168.50.13";
.port = "80";
.probe = {
.url = "/index.html"; //请求的URL
.interval = 1s; //检测频率
.timeout = 1s; //检测超时时间
.window = 8; //X,基于X次检测,其中有Y次成功,判定为健康
.threshold = 5; //Y
}
}
方法2 :定义默认检测标准,可供调用
#先定义默认检测标准的配置段
probe default_probe {
.url = "/index.html";
.interval = 1s;
.timeout = 1s;
.window = 8;
.threshold = 5;
}
#其他backend server可以调用该默认检测指标
backend websrv2 {
.host = "192.168.50.13";
.port = "8080";
.probe = default_probe;
}
#加载策略并使其生效
- 测试后端服务器健康状态
cd /etc/varnish
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
backend.list
#显示结果
Backend name Refs Admin Probe
default(192.168.50.13,,80) 5 probe Healthy (no probe)
websrv1(192.168.50.13,,80) 5 probe Healthy 8/8
websrv2(192.168.50.13,,8080) 5 probe Healthy 8/8
websrv3(192.168.50.14,,80) 4 probe Healthy 8/8
websrv4(192.168.50.14,,8080) 4 probe Healthy 8/8
手动关闭一台后端服务器
cd /etc/varnish
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
backend.list
#显示结果
Backend name Refs Admin Probe
default(192.168.50.13,,80) 5 probe Healthy (no probe)
websrv1(192.168.50.13,,80) 5 probe Sick 0/8
websrv2(192.168.50.13,,8080) 5 probe Sick 0/8
websrv3(192.168.50.14,,80) 4 probe Healthy 8/8
websrv4(192.168.50.14,,8080) 4 probe Healthy 8/8
- 注意:”Probe” 一列信息中有个分数为8/8,当该数值小于5/8,即我们在配置中定义的X/Y,如果该值小于我们设定的值,则判定该服务器为Sick。
设置后端的主机属性:
backend BACKEND_NAME {
…
.connect_timeout = 0.5s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
.max_connections = 50;
}
最终配置实例
- 将多种功能组合起来,配置如下:
vim /etc/varnish/default.vcl
#vcl版本必须在文件最前面
vcl 4.0;
#导入directors模块
import directors;
#定义一个acl组,可供调用
acl purgers {
"127.0.0.1"/8;
"172.16.50.0"/16;
}
#健康状态监测的默认指标,可供各后端主机调用
probe default_probe {
.url = "/index.html";
.interval = 1s;
.timeout = 1s;
.window = 8;
.threshold = 5;
}
#定义多个后端主机
backend websrv1 {
.host = "192.168.50.13";
.port = "80";
.probe = default_probe;
.max_connections = 800;
.connect_timeout = 600s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
}
backend websrv2 {
.host = "192.168.50.13";
.port = "80";
.probe = default_probe;
.max_connections = 800;
.connect_timeout = 600s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
}
backend imgsrv1 {
.host = "192.168.50.14";
.port = "80";
.probe = default_probe;
.max_connections = 800;
.connect_timeout = 600s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
}
backend imgsrv2 {
.host = "192.168.50.14";
.port = "8080";
.probe = default_probe;
.max_connections = 800;
.connect_timeout = 600s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
}
backend appsrv1 {
.host = "192.168.50.15";
.port = "80";
.probe = default_probe;
.max_connections = 800;
.connect_timeout = 600s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
}
backend appsrv2 {
.host = "192.168.50.15";
.port = "8080";
.probe = default_probe;
.max_connections = 800;
.connect_timeout = 600s;
.first_byte_timeout = 20s;
.between_bytes_timeout = 5s;
}
#将后端主机分组,
sub vcl_init {
#第一组取名为 websrvs ,调度方式为轮巡
new websrvs = directors.round_robin();
websrvs.add_backend(websrv1);
websrvs.add_backend(websrv2);
#第二组取名为 imgsrvs ,调度方式为随机,权重分别为2和1
new igmsrvs = directors.random();
imgsrvs.add_backend(imgsrv1,2);
imgsrvs.add_backend(imgsrv2,1);
}
#第三组取名为 appsrvs ,调度方式为随机,权重分别为2和1
new igmsrvs = directors.random();
imgsrvs.add_backend(appsrv1,2);
imgsrvs.add_backend(appsrv2,1);
}
#vcl_recv配置段
sub vcl_recv {
#调用后端服务器的分组
if (req.url ~ "(?i)\.(php|php5)$") {
set req.backend_hint = appsrvs.backend();
}
if (req.url ~ "(?i)\.(jpg|jpeg|png|gif)$") {
set req.backend_hint = imgsrvs.backend();
}
set req.backend_hint = websrvs.backend();
#缓存修剪功能:定义请求方法 "PURGE" 及调用已经定义的acl purger
if (req.method == "PURGE") {
if (!client.ip ~ purgers) {
return(synth(405,"Purging not allow for " + client.ip));
}
return(purge);
}
#对可能为私密的信息不予缓存,直接pass至后端
if (req.url ~ "(?i)^/(admin|login)") {
return(pass);
}
#停止向使用curl浏览器的用户提供web服务,直接返回403
if (req.http.User-Agent ~ "(?i)curl") {
return(synth(403));
}
#修改后端服务器记录日志的访问者IP为客户端的真实IP
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;
}
}
#去掉某些非私密资源的Set-Cookie首部,根据资源的类型来定义是否将其加入公共缓存从而提高用户访问体验
if (beresp.http.Cache-Control !~ "(?i)s-maxage") {
if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)") {
unset beresp.http.Set-Cookie;
set beresp.ttl = 3600s;
}
}
}
# vcl_deliver配置段
sub vcl_deliver {
#在响应报文中添加X-Cache首部,显示该资源是否被缓存与缓存服务器地址
if (obj.hits>0) {
set resp.http.X-cache = "Hit via " + server.ip;
} else {
set resp.http.X-cache = "Miss from " + server.ip;
}
}
- 加载配置策略并使其生效
cd /etc/varnish
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
#进入交互模式
vcl.list
vcl.load test1 default.vcl
vcl.use test1