varnish
目录
I.varnish
原理
i.Varnish
简介
varnish
缓存是web应用加速器,同时也作为http
反向缓存代理。你可以安装varnish
在任何http
的前端,同时配置它缓存内容。与传统的 squid
相比,varnish
具有性能更高、速度更快、管理更加方便等诸多优点。有一部分企业已经在生产环境中使用其作为旧版本的squid
的替代方案,以在相同的服务器成本下提供更好的缓存效果,Varnish
更是作为CDN
缓存服务器的可选服务之一。
根据官网的介绍,Varnish
的主要特性如下:https://www.varnish-cache.org/
- 1.缓存位置:可以使用内存也可以使用磁盘。如果要使用磁盘的话推荐
SSD
做RAID1
- 2.日志存储:日志也存储在内存中。存储策略:固定大小,循环使用
- 3.支持虚拟内存的使用。
- 4.有精确的时间管理机制,即缓存的时间属性控制。
- 5.状态引擎架构:在不同的引擎上完成对不同的缓存和代理数据进行处理。可以通过特定的配置语言设计不同的控制语句,以决定数据在不同位置以不同方式缓存,在特定的地方对经过的报文进行特定规则的处理。
- 6.缓存管理:以二叉堆格式管理缓存数据,做到数据的及时清理。
ii.Varnish
与Squid
的对比
相同点
- 都是一个反向代理服务器;
- 都是开源软件;
Varnish
的优势
- 1、
Varnish
的稳定性很高,两者在完成相同负荷的工作时,Squid
服务器发生故障的几率要高于Varnish
,因为使用Squid
要经常重启; - 2、Varnish访问速度更快,因为采用了“Visual Page Cache”技术,所有缓存数据都直接从内存读取,而squid是从硬盘读取,因而Varnish在访问速度方面会更快;
- 3、Varnish可以支持更多的并发连接,因为Varnish的
TCP
连接释放要比Squid快,因而在高并发连接情况下可以支持更多TCP
连接; - 4、Varnish可以通过管理端口,使用正则表达式批量的清除部分缓存,而Squid是做不到的; squid属于是单进程使用单核CPU,但Varnish是通过fork形式打开多进程来做处理,所以可以合理的使用所有核来处理相应的请求;
iii.Varnish
的劣势
1、varnish进程一旦Crash或者重启,缓存数据都会从内存中完全释放,此时所有请求都会发送到后端服务器,在高并发情况下,会给后端服务器造成很大压力;
2、在varnish使用中如果单个url
的请求通过HA/F5
等负载均衡,则每次请求落在不同的varnish服务器中,造成请求都会被穿透到后端;而且同样的请求在多台服务器上缓存,也会造成varnish的缓存的资源浪费,造成性能下降;
iv.Varnish
劣势的解决方案
针对劣势一:在访问量很大的情况下推荐使用varnish的内存缓存方式启动,而且后面需要跟多台squid/nginx服务器。主要为了防止前面的varnish服 务、服务器被重启的情况下,大量请求穿透varnish,这样squid/nginx可以就担当第二层CACHE,而且也弥补了varnish缓存在内存中重启都会释放的问题;
针对劣势二:可以在负载均衡上做url哈希,让单个url请求固定请求到一台varnish服务器上;
II.缓存原理
i.使用varnish
作为web
代理缓存的原理
varnish是一个http反向代理的缓存。它从客户端接收请求然后尝试从缓存中获取数据来响应客户端的请求,如果varnish不能从缓存中获得数据来响应客户端,它将转发请求到后端(backend servers
),获取响应同时存储,最后交付给客户端。
如果varnish已经缓存了某个响应,它比你传统的后端服务器的响应要快很多,所以你需要尽可能是更多的请求直接从varnish的缓存中获取响应。
varnish决定是缓存内容或者是从后端服务器获取响应。后端服务器能通过http响应头中的Cache-Control来同步varnish缓存内容。在某些条件下varnish将不缓存内容,最常见的是使用cookie。当一个被标记有cookie的客户端web请求,varnish默认是不缓存。这些众多的varnish功能特点都是可以通过写vcl来改变的。
ii.简单架构
Varnish 分为management
进程和child
进程;
Management
进程:对子进程进行管理,同时对VCL
配置进行编译,并应用到不同的状态引擎。Child
进程:生成线程池,负责对用户请求进行处理,并通过hash
查找返回用户结果。
iii.varnish主要配置部分
varnish
配置主要分为:
- 后端配置
ACL
配置probes
配置directors
配置,核心子程序配置几大块。其中后端配置是必要的,在多台服务器中还会用到directors
配置,核心子程序配置。- 后端配置:即给varnish添加反代服务器节点,最少配置一个。
ACL
配置:即给varnish添加访问控制列表,可以指定这些列表访问或禁止访问。probes
配置:即给varnish添加探测后端服务器是否正常的规则,方便切换或禁止对应后端服务器。directors
配置:即给varnish添加负载均衡模式管理多个后端服务器。 核心子程序配置:即给varnish添加后端服务器切换,请求缓存,访问控制,错误处理等规则。
iv.VCL
中内置预设变量:变量(也叫object
)
req
:The request object,请求到达时可用的变量(客户端发送的请求对象)bereq
:The backend request object,向后端主机请求时可用的变量beresp
:The backend response object,从后端主机获取内容时可用的变量(后端响应请求对象)resp
:The HTTP response object,对客户端响应时可用的变量(返回给客户端的响应对象)obj
:存储在内存中时对象属性相关的可用的变量(高速缓存对象,缓存后端响应请求内容)
预设变量是系统固定的,请求进入对应的vcl
子程序后便生成,这些变量可以方便子程序提取,当然也可以自定义一些全局变量。
当前时间
now
:作用:返回当前时间戳。
客户端:(客户端基本信息)
client.ip
:返回客户端IP地址。
注:原client.port
已经弃用,如果要取客户端请求端口号使用 std.port(client.ip)
,需要import std
;才可以使用std client.identity
:用于装载客户端标识码。
服务器:(服务器基本信息)
注:原server.port
已经弃用,如果要取服务器端口号使用std.port(server.ip)
,需要import std;
才可以使用std
server.hostname
:服务器主机名。server.identity
:服务器身份标识。server.ip
:返回服务器端IP地址。
req
req
:(客户端发送的请求对象)req
:整个HTTP请求数据结构
req 选项 |
作用 |
---|---|
req.backend_hint |
指定请求后端节点,设置后 bereq.backend 才能获取后端节点配置数据 |
req.can_gzip |
客户端是否接受GZIP 传输编码。 |
req.hash_always_miss |
是否强制不命中高速缓存,如果设置为true ,则高速缓存不会命中,一直会从后端获取新数据。 |
req.hash_ignore_busy |
忽略缓存中忙碌的对象,多台缓存时可以避免死锁。 |
req.http |
对应请求HTTP的header。 |
req.method |
请求类型(如 GET , POST )。 |
req.proto |
客户端使用的HTTP协议版本。 |
req.restarts |
重新启动次数。默认最大值是4 |
req.ttl |
缓存有剩余时间。 |
req.url |
请求的URL 。 |
req.xid |
唯一ID 。 |
bereq
bereq
:(发送到后端的请求对象,基于req
对象)bereq
:整个后端请求后数据结构。
bereq 选项 |
解释 |
---|---|
bereq.backend |
所请求后端节点配置。 |
bereq.between_bytes_timeout |
从后端每接收一个字节之间的等待时间(秒)。 |
bereq.connect_timeout |
连接后端等待时间(秒),最大等待时间。 |
bereq.first_byte_timeout |
等待后端第一个字节时间(秒),最大等待时间。 |
bereq.http |
对应发送到后端HTTP 的header 信息。 |
bereq.method |
发送到后端的请求类型(如:GET , POST )。 |
bereq.proto |
发送到后端的请求的HTTP 版本。 |
bereq.retries |
相同请求重试计数。 |
bereq.uncacheable |
无缓存这个请求。 |
bereq.url |
发送到后端请求的URL 。 |
bereq.xid |
请求唯一ID 。 |
beresp
beresp
:(后端响应请求对象)beresp
:整个后端响应HTTP数据结构。
bereqsp 选项 |
解释 |
---|---|
beresp.backend.ip |
后端响应的IP 。 |
beresp.backend.name |
响应后端配置节点的name 。 |
beresp.do_gunzip |
默认为 false 。缓存前解压该对象 |
beresp.do_gzip |
默认为 false 。缓存前压缩该对象 |
beresp.grace |
设置当前对象缓存过期后可额外宽限时间,用于特殊请求加大缓存时间,当并发量巨大时,不易设置过大否则会堵塞缓存,一般可设置1m 左右,当beresp.ttl=0s 时该值无效。 |
beresp.http |
对应的HTTP 请求header |
beresp.keep |
对象缓存后带保持时间 |
beresp.proto |
响应的HTTP 版本 |
beresp.reason |
由服务器返回的HTTP 状态信息 |
beresp.status |
由服务器返回的状态码 |
beresp.storage_hint |
指定保存的特定存储器 |
beresp.ttl |
该对象缓存的剩余时间,指定统一缓存剩余时间。 |
beresp.uncacheable |
继承bereq.uncacheable ,是否不缓存 |
OBJ
OBJ
:(高速缓存对象,缓存后端响应请求内容)
obj 选项 |
解释 |
---|---|
obj.grace |
该对象额外宽限时间 |
obj.hits |
缓存命中次数,计数器从1开始,当对象缓存该值为1 ,一般可以用于判断是否有缓存,当前该值大于0 时则为有缓存。 |
obj.http |
对应HTTP 的header |
obj.proto |
HTTP 版本 |
obj.reason |
服务器返回的HTTP 状态信息 |
obj.status |
服务器返回的状态码 |
obj.ttl |
该对象缓存剩余时间(秒 ) |
obj.uncacheable |
不缓存对象 |
resp
resp
:(返回给客户端的响应对象)resp
:整个响应HTTP数据结构。
resp 选项 |
解释 |
---|---|
resp.http |
对应HTTP 的header 。 |
resp.proto |
编辑响应的HTTP 协议版本。 |
resp.reason |
将要返回的HTTP 状态信息。 |
resq.status |
将要返回的HTTP 状态码。 |
存储
storage.<name>.free_space
:存储可用空间(字节数)。storage.<name>.used_space
:存储已经使用空间(字节数)。storage.<name>.happy
:存储健康状态。
v.特定功能性语句
ban(expression)
:清除指定对象缓存call(subroutine)
:调用子程序,如:call(name)
;hash_data(input)
:生成hash
键,用于制定hash
键值生成结构,只能在vcl_hash
子程序中使用。调用hash_data(input)
后,即这个hash
为当前页面的缓存hash
键值,无需其它获取或操作,如:
sub vcl_hash{
hash_data(client.ip);
return(lookup);
}
注意:return(lookup);
是默认返回值,所以可以不写。
new()
:创建一个vcl
对象,只能在vcl_init
子程序中使用。return()
:结束当前子程序,并指定继续下一步动作,如:return (ok);
每个子程序可指定的动作均有不同。rollback()
:恢复HTTP头到原来状态,已经弃用,使用std.rollback()
代替。synthetic(STRING)
:合成器,用于自定义一个响应内容,比如当请求出错时,可以返回自定义404
内容,而不只是默认头信息,只能在vcl_synth
与vcl_backend_error
子程序中使用,如:
sub vcl_synth {
//自定义内容
synthetic ({"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>error</title>
</head>
<body>
<h1>Error</h1>
<h3>这只是一个测试自定义响应异常内容</h3>
</body>
</html>
"});
//只交付自定义内容
return(deliver);
regsub(str, regex, sub)
:使用正则替换第一次出现的字符串,第一个参数为待处理字符串,第二个参数为正则表达式,第三个为替换为字符串。regsuball(str, regex, sub)
:使用正则替换所有匹配字符串。参数与regsuball相同。 具体变量详见: https://www.varnish-cache.org/docs/4.0/reference/vcl.html#reference-vcl
vii.return
语句
return
语句是终止子程序并返回动作,所有动作都根据不同的vcl子程序限定来选用。
https://www.varnish-cache.org/docs/4.0/users-guide/vcl-built-in-subs.html
语法:
return(action);
常用的动作:
abandon
放弃处理,并生成一个错误。deliver
交付处理fetch
从后端取出响应对象hash
哈希缓存处理lookup
查找缓存对象ok
继续执行pass
进入pass非缓存模式pipe
进入pipe非缓存模式purge
清除缓存对象,构建响应restart
重新开始retry
重试后端处理synth(status code,reason)
合成返回客户端状态信息
viii.varnish
中内置子程序
注:varnish内置子程序均有自己限定的返回动作 return (动作);
不同的动作将调用对应下一个子程序。
vcl_recv
子程序
开始处理请求,通过 return (动作);
选择varnish处理模式,默认进入hash
缓存模式(即return(hash);
),缓存时间为配置项default_ttl
(默认为 120秒
)过期保持时间default_grace
(默认为10秒
)。该子程序一般用于模式选择,请求对象缓存及信息修改,后端节点修改,终止请求等操作。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,req
,storage
- 写:
client
,req
- 返回值:
synth(status code,reason);
定义响应内容。pass
进入pass
模式,并进入vcl_pass
子程序。pipe
进入pipe
模式,并进入vcl_pipe
子程序。hash
进入hash
缓存模式,并进入vcl_hash
子程序,默认返回值。purge
清除缓存等数据,子程序先从vcl_hash
再到vcl_purge
。
vcl_pipe
子程序
pipe
模式处理,该模式主要用于直接取后端响应内容返回客户端,可定义响应内容返回客户端。该子程序一般用于需要及时且不作处理的后端信息,取出后端响应内容后直接交付到客户端不进入vcl_deliver
子程序处理。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,bereq
,req
,storage
- 写:
client
,bereq
,req
- 返回值:
synth(status code,reason);
定义响应内容。pipe
继续pipe
模式,进入后端vcl_backend_fetch
子程序,默认返回值。
vcl_pass
子程序
pass
模式处理,该模式类似hash
缓存模式,仅不做缓存处理。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,req
,storage
- 写:
client
,req
- 返回值:
synth(status code,reason);
定义响应内容。fetch
继续pass
模式,进入后端vcl_backend_fetch
子程序,默认返回值。
vcl_hit
子程序
hash
缓存模式时,存在hash
缓存时调用,用于缓存处理,可放弃或修改缓存。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,obj
,req
,storage
- 写:
client
,req
- 返回值:
restart
重启请求。deliver
交付缓存内容,进入vcl_deliver
子程序处理,默认返回值。synth(status code,reason);
定义响应内容。
vcl_miss
子程序
hash
缓存模式时,不存在hash
缓存时调用,用于判断性的选择进入后端取响应内容,可以修改为pass
模式。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,req
,storage
- 写:
client
,req
- 返回值:
restart
重启请求。synth(status code,reason);
定义响应内容。pass
切换到pass
模式,进入vcl_pass
子程序。fetch
正常取后端内容再缓存,进入vcl_backend_fetch
子程序,默认返回值。
vcl_hash
子程序
hash
缓存模式,生成hash
值作为缓存查找键名提取缓存内容,主要用于缓存hash
键值处理,可使用hash_data(string)
指定键值组成结构,可在同一个页面通过IP
或cookie
生成不同的缓存键值。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,req
,storage
- 写:
client
,req
- 返回值:
lookup
查找缓存对象,存在缓存进入vcl_hit
子程序,不存在缓存进入vcl_miss
子程序,当使用了purge
清理模式时会进入vcl_purge
子程序,默认返回值。
vcl_purge
子程序
清理模式,当查找到对应的缓存时清除并调用,用于请求方法清除缓存,并报告。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,req
,storage
- 写:
client
,req
- 返回值:
synth(status code,reason);
定义响应内容。restart
重启请求。
vcl_deliver
子程序
客户端交付子程序,在vcl_backend_response
子程序后调用(非pipe
模式),或vcl_hit
子程序后调用,可用于追加响应头信息,cookie
等内容。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,req
,resp
,obj
,storage
- 写:
client
,req
,resp
- 返回值:
deliver
正常交付后端或缓存响应内容,默认返回值。 -restart
重启请求。
vcl_backend_fetch
子程序
发送后端请求之前调用,可用于改变请求地址或其它信息,或放弃请求。
- 可操作对象:(部分或全部值)
- 读:
server
,bereq
,storage
- 写:
bereq
- 返回值:
fetch
正常发送请求到到后端取出响应内容,进入vcl_backend_response
子程序,默认返回值。abandon
放弃后端请求,并生成一个错误,进入vcl_backend_error
子程序。
vcl_backend_response
子程序
后端响应后调用,可用于修改缓存时间及缓存相关信息。
- 可操作对象:(部分或全部值)
- 读:
server
,bereq
,beresp
,storage
- 写:
bereq
,beresp
- 返回值:
deliver
正常交付后端响应内容,进入vcl_deliver
子程序,默认返回值。abandon
放弃后端请求,并生成一个错误,进入vcl_backend_error
子程序。retry
重试后端请求,重试计数器加1,当超过配置中max_retries
值时会报错并进入vcl_backend_error
子程序。
vcl_backend_error
子程序
后端处理失败调用,异常页面展示效果处理,可自定义错误响应内容,或修改beresp.status
与beresp.http.Location
重定向等。
- 可操作对象:(部分或全部值)
- 读:
server
,bereq
,beresp
,storage
- 写:
bereq
,beresp
- 返回值:
deliver
只交付sysnthetic(string)
自定义内容,默认返回后端异常标准错误内容。retry
重试后端请求,重试计数器加1
,当超过配置中max_retries
值时会报错并进入vcl_backend_error
子程序。
vcl_synth
子程序
自定义响应内容。可以通过synthetic()
和返回值synth
调用,这里可以自定义异常显示内容,也可以修改resp.status
与resp.http.Location
重定向。
- 可操作对象:(部分或全部值)
- 读:
client
,server
,req
,resp
,storage
- 写:
req
,resp
- 返回值:
deliver
只交付sysnthetic(string)
自定义内容,默认返回sysnth
异常指定状态码与错误内容。restart
重启请求。
vcl_init
子程序
加载vcl
时最先调用,用于初始化VMODs
,该子程序不参与请求处理,仅在vcl
加载时调用一次。
- 可操作对象:(部分或全部值)
- 读:
server
- 写:无
- 返回值:
ok
正常返回,进入vcl_recv
子程序,默认返回值。
vcl_fini
子程序
卸载当前vcl
配置时调用,用于清理VMODs
,该子程序不参与请求处理,仅在vcl
正常丢弃后调用。
- 可操作对象:(部分或全部值)
- 读:
server
- 写:无
- 返回值:
-
ok
正常返回,本次vcl
将释放,默认返回值。
varnish子程序调用流程图,通过大部分子程序的return返回值进入下一步行动:
IX.优雅模式(Grace mode
)
Varnish中的请求合并当几个客户端请求同一个页面的时候,varnish只发送一个请求到后端服务器,然后让其他几个请求挂起并等待返回结果;获得结果后,其它请求再复制后端的结果发送给客户端;
但如果同时有数以千计的请求,那么这个等待队列将变得庞大,这将导致2类潜在问题:
惊群问题(thundering herd problem
),即突然释放大量的线程去复制后端返回的结果,将导致负载急速上升;没有用户喜欢等待;
故为了解决这类问题,可以配置varnish在缓存对象因超时失效后再保留一段时间,以给那些等待的请求返回过去的文件内容(stale content
),配置案例如下:
sub vcl_recv {
if (! req.backend.healthy) {
set req.grace = 5m;
} else {
set req.grace = 15s;
}
}
sub vcl_fetch {
set beresp.grace = 30m;
}
以上配置表示varnish将会将失效的缓存对象再多保留30分钟
,此值等于最大的req.grace
值即可;
而根据后端主机的健康状况,varnish可向前端请求分别提供5分钟
内或15秒
内的过期内容
III.安装varnish
i.安装依赖关系的软件包(注:使用centos
在线yum
源)
[root@varnish ~]# yum -y install autoconf automake jemalloc-devel libedit-devel libtool ncurses-devel pcre-devel pkgconfig python-docutils python-sphinx
ii.安装varnish
首先需要建立varnish
用户以及用户组来运行Varnish,并且创建Varnish缓存目录和日志目录。
[root@varnish ~]# useradd -s /sbin/nologin varnish
[root@varnish ~]# mkdir -p /data/varnish/{cache,log}
[root@varnish ~]# chown -R varnish:varnish /data/varnish
Varnish的
官方网址,可以在这里下载最新版本的软件。
下载地址
注意:Varnish网站有时会被墙。
Git
下载:git clone https://github.com/varnish/Varnish-Cache /var/tmp/
解压,进入解压目录编译安装
[root@varnish ~]# tar zxf varnish-4.0.3.tar.gz
[root@varnish ~]# cd varnish-4.0.3/
[root@varnish varnish-4.0.3]# export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
注:
./autogen.sh
如果从Git库下载的安装包时才需要运行,用于生成configure
编译文件。
配置:
[root@varnish varnish-4.0.3]# ./configure
注:不指定安装路径,默认是安装在/usr/local目录下
编译、安装
[root@varnish varnish-4.0.3]# make && make install
复制vcl
文件(在编译安装目录下),如果安装目录里没有 default.vcl
文件。
复制到安装目录的/usr/local/var/varnish/
目录下(当然并无必需要求在哪个目录,因为正式启动时还得指定这个文件的目录)
[root@varnish varnish-4.0.3]# cp etc/example.vcl /usr/local/var/varnish/default.vcl
IV.varnish
实例解析
varnish
配置基本上是编辑 VCL
(Varnish Configuration Language
) 文件,varnish
有一套自定义VCL
语法,启动时,会将配置文件编译为C语言
,再执行。
varnish 4.0
开始,每个VCL文件必须在开始行声明它的版本vcl 4.0;
块(子程序)由大括号分隔,语句用分号结束。所有的关键字及预设子程序名都是全小写。
注释:支持 //
或 #
多行时还可以使用/* .. */
i.后端服务器地址池配置及后端服务器健康检查
varnish
有"后端"或者"源"服务器的概念。backend server
提供给varnish
加速的内容。实际上就是给varnish
添加可供访问的web
服务器,如果有多台web
服务器时,可添加多个backend
块。
1.后端服务器定义
命令:
backend
这个定义为最基本的反向入口定义,用于varnish
连接对应的服务器,如果没有定义或定义错误则用户无法访问正常页面。
语法格式:
backend name{
.attribute = "value";
}
说明:backend
是定义后端关键字,name
是当前后端节点的别名
,多个后端节点时,name
名不能重复,否则覆盖。花括号里面定义当前节点相关的属性(键=值
)。除默认节点外其它节点定义后必需有调用,否则varnish
无法启动。后端是否正常可以通过std.healthy(backend)
判断。
支持运算符:
=
(赋值运算)
==
(相等比较)
~
(匹配,可以使用正则表达式,或访问控制列表)
!~
(不匹配,可以使用正则表达式,或访问控制列表)
!
(非)
&&
(逻辑与)
||
(逻辑或)
属性列表:
.host="xxx.xxx.xxx.xxx";
//要转向主机(即后端主机)的IP或域名,必填键/值对。.port="8080";
//主机连接端口号或协议名(HTTP等),默认80.host_header='';
//请示主机头追加内容.connect_timeout=1s;
//连接后端的超时时间.first_byte_timeout=5s;
//等待从后端返回的第一个字节时间.between_bytes_timeout=2s;
//每接收一个字节之间等待时间.probe=probe_name;
//监控后端主机的状态,指定外部监控name或者内部直接添加.max_connections=200;
//设置最大并发连接数,超过这个数后连接就会失败
例:(下面两个例子结果是一样的,但第二个例子中更适用于集群,可以方便批量修改)
backend web{
.host="192.168.31.83";
.port="80";
.probe={ //直接追加监控块.probe是一个的参数
.url="/";
.timeout=2s;
}
}
或
如果有多个web需要.probe
属性,这个时候可以这样做。
probe web_probe{ //监控必需定义在前面,否则后端调用找不到监控块。
.url="/";
.timeout=2s;
}
backend web{
.host="192.168.31.83";
.port="80";
.probe=web_probe; //调用外部共用监控块
}
2.监视器定义
命令:
probe
监控可以循环访问指定的地址,通过响应时间判定服务器是否空闲或正常。这类命令非常适用于集群中某些节点服务器崩溃或负载过重,而禁止访问这台节点服务器。
语法格式:
probe name{
.attribute = "value";
}
说明:probe
是定义监控关键字,name
是当前监控点的别名,多个监控节点时,name
名不能重复,否则覆盖。花括号里面定义当前节点相关的属性(键=值
)。
没有必填属性,因为默认值就可以正常执行操作。
属性列表:
.url="/";
//指定监控入口URL地址,默认为/
.request="";
//指定监控请求入口地址,比.url
优先级高。.expected_response="200";
//请求响应代码,默认是200
.timeout=2s;
//请求超时时间。.interval=5s;
//每次轮询请求间隔时间,默认为5s
。.initial=-1;
//初始启动时以.window
轮询次数中几次良好后续才能使用这个后端服务器节点,默认为-1
,则轮询完.window
所有次数良好判定为正常。.window=8;
//指定多少轮询次数,用于判定服务器正常,默认是8
。.threshold=3;
//必须多少次轮询正常才算该后端节点服务器正常,默认是3
。
例:创建健康监测,定义健康检查名称为
backend_healthcheck
probe backend_healthcheck {
.url = "/";
.timeout = 1s;
.interval = 5s;
.window = 5;
.threshold = 3;
}
在上面的例子中varnish
将每5s
检测后端,超时设为1s
。每个检测将会发送get /
的请求。如果5
个检测中大于3
个是成功,varnish
就认为后端是健康的,反之,后端就有问题了。
3.集群负载均衡directors
varnish
可以定义多个后端,也可以将几个后端放在一个后端集群里面已达到负载均衡的目的。
你也可以将几个后端组成一组后端。这个组被叫做Directors
。可以提高性能和弹性。
directors
是varnish
负载均衡模块,使用前必需引入directors
模块,directors
模块主要包含:round_robin
,random
,hash
,fallback
负载均衡模式。
round_robin
: 循环依次逐个选择后端服务器。random
: 随机选择后端服务器,可设置每个后端权重增加随机率。hash
: 通过散列随机选择对应的后端服务器且保持选择对应关系,下次则直接找对应的后端服务器。Fallback:
后备
注意:random
,hash
有权重值设置,用于提高随机率。每个后端最好都配置监控器(后端服务器正常监测)以便directors
自动屏蔽不正常后端而不进入均衡列队中。
这些操作需要你载入VMOD
(varnish module
),然后在vcl_init
中调用这个VMOD
。
import directors; # load the directors
backend web1 {
.host = "192.168.0.10";
.port = "80";
.probe = backend_healthcheck;
}
backend web2 {
.host = "192.168.0.11";
.port = "80";
.probe = backend_healthcheck;
}
//初始化处理
sub vcl_init { //调用vcl_init初始化子程序创建后端主机组,即directors
new web_cluster = directors.round_robin(); //使用new关键字创建drector对象,使用round_robin算法
web_cluster.add_backend(web1); //添加后端服务器节点
web_cluster.add_backend(web2);
}
//开始处理请求
sub vcl_recv { //调用vcl_recv子程序,用于接收和处理请求
set req.backend_hint = web_cluster.backend(); //选取后端
}
说明:
set
命令是设置变量unset
命令是删除变量web_cluster.add_backend
(backend
,real
); 添加后端服务器节点,backend
为后端配置别名,real
为权重值,随机率计算公式:100 * (当前权重 / 总权重)
。req.backend_hint
是varnish
的预定义变量,作用是指定请求后端节点vcl
对象需要使用new
关键字创建,所有可创建对象都是内定的,使用前必需import
,所有new
操作只能在vcl_init
子程序中。
扩展:varnish
将不同的url
发送到不同的后端server
import directors; # load the directors
backend web1 {
.host = "192.168.0.10";
.port = "80";
.probe = backend_healthcheck;
}
backend web2 {
.host = "192.168.0.11";
.port = "80";
.probe = backend_healthcheck;
}
backend img1 {
.host = "img1.lnmmp.com";
.port = "80";
.probe = backend_healthcheck;
}
backend img2 {
.host = "img2.lnmmp.com";
.port = "80";
.probe = backend_healthcheck;
}
//初始化处理
sub vcl_init { //调用vcl_init初始化子程序创建后端主机组,即directors
new web_cluster = directors.round_robin(); //使用new关键字创建drector对象,使用round_robin算法
web_cluster.add_backend(web1); //添加后端服务器节点
web_cluster.add_backend(web2);
new img_cluster = directors.random();
img_cluster.add_backend(img1,2); //添加后端服务器节点,并且设置权重值
img_cluster.add_backend(img2,5);
}
//根据不同的访问域名,分发至不同的后端主机组
sub vcl_recv {
if (req.http.host ~ "(?i)^(www.)?benet.com$") {
set req.backend_hint = web_cluster.backend(); //选取后端
} elsif (req.http.host ~ "(?i)^images.benet.com$") {
set req.backend_hint = img_cluster.backend();
}
}
说明:中的i就是忽略大小写的意思。(?i)
表示开启忽略大小写,而(?-i)
表示关闭忽略大小写
4.访问控制列表(ACL
)
创建一个地址列表,用于后面的判断,可以是域名
或IP
集合。这个可以用于指定某些地址请求入口,防止恶意请求等。
语法格式:
acl purgers {
"127.0.0.1";
"localhost";
“192.168.134.0/24”
!"192.168.134.1";
}
说明:acl
是访问列表关键字(必需小写),name
是该列表的别名用于调用,花括号内部是地址集。
注意:如果列表中包含了无法解析的主机地址,它会匹配任何地址。
如果不想让它匹配可以在前添加一个 !
符号,如上面 !"192.168.134.1";
使用
ACL
只需要用 匹配运算符~
或!~
如:
sub vcl_recv {
if (req.method == "PURGE") { //PURGE请求的处理
if (client.ip ~ purgers) {
return(purge);
} else {
return(synth(403, "Access denied."));
}
}
}
5.缓存规则配置
此处是完整的配置文件,为了视觉效果将整个配置文件拆开了。保留了配置文件中的注释//
.
sub vcl_recv {
// PURGE请求的处理
if (req.method == "PURGE") {
if (!client.ip ~ purgers) {
return (synth(405, "Not Allowed."));
}
return (purge);
}
set req.backend_hint = web.backend();
//将php
、asp
等动态内容访问请求直接发给后端服务器,不缓存。
if (req.url ~ "\.(php|asp|aspx|jsp|do|ashx|shtml)($|\?)") {
return (pass);
}
//将非GET
和HEAD
访问请求直接发给后端服务器,不缓存。例如POST
请求。
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
//如果varnish
看到header
中有Authorization
头,它将pass
请求。
if (req.http.Authorization) {
return (pass);
}
//带cookie
首部的GET
请求也缓存
if (req.url ~ "\.(css|js|html|htm|bmp|png|gif|jpg|jpeg|ico|gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)($|\?)") {
unset req.http.cookie;
return (hash);
}
说明:默认情况,varnish
不缓存从后端响应的http
头中带有Set-Cookie
的对象。如果客户端发送的请求带有Cookie header
,varnish
将忽略缓存,直接将请求传递到后端。
//为发往后端主机的请求添加X-Forward-For
首部,首次访问增加X-Forwarded-For
头信息,方便后端程序获取客户端ip
,而不是varnish
地址
if (req.restarts == 0) {
if (req.http.x-forwarded-for) {//如果设置过此header则要再次附加上用逗号隔开
set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
} else {//如果只有一层代理的话,就无需设置了
set req.http.X-Forwarded-For = client.ip;
}
}
req.restarts == 0
代表客户端首次访问
说明:X-Forwarded-For
是用来识别通过HTTP
代理或负载均衡方式连接到Web
服务器的客户端最原始的IP地址
的HTTP
请求头字段
子程序:
子程序是一种类似C
的函数,但是程序没有调用参数,子程序以 sub
关键字定义。在VCL
里子程序是用于管理程序。
注意:所有VCL
内置的程序都是以vcl_
开头,并已经预置好,在VCL
文件中只要声明对应的内置子程序,都会在对应的流程中调用。
V.varnish
完整配置实例
i.拓扑环境
主机 | 地址 |
---|---|
Varnish |
:192.168.31.250 |
Web01 |
:192.168.31.83 |
Web02 |
:192.168.31.141 |
配置web01
、web02
做为后端服务器(过程略)
确保varnish
服务器能正常访问web01
、web02
Varnish
缓存代理服务器配置:
ii.vcl
文件配置内容
[root@varnish ~]# cat /usr/local/var/varnish/default.vcl
#使用varnish版本4的格式.
vcl 4.0;
#加载后端负载均衡模块
import directors;
#加载std模块
import std;
#创建名为backend_healthcheck的健康检查策略
probe backend_healthcheck {
.url="/";
.interval = 5s;
.timeout = 1s;
.window = 5;
.threshold = 3;
}
#定义后端服务器
backend web_app_01 {
.host = "192.168.31.83";
.port = "80";
.first_byte_timeout = 9s;
.connect_timeout = 3s;
.between_bytes_timeout = 1s;
.probe = backend_healthcheck;
}
backend web_app_02 {
.host = "192.168.31.141";
.port = "80";
.first_byte_timeout = 9s;
.connect_timeout = 3s;
.between_bytes_timeout = 1s;
.probe = backend_healthcheck;
}
#定义允许清理缓存的IP
acl purgers {
"127.0.0.1";
"localhost";
"192.168.31.0/24";
}
#vcl_init初始化子程序创建后端主机组
sub vcl_init {
new web = directors.round_robin();
web.add_backend(web_app_01);
web.add_backend(web_app_02);
}
#请求入口,用于接收和处理请求。这里一般用作路由处理,判断是否读取缓存和指定该请求使用哪个后端
sub vcl_recv {
#将请求指定使用web后端集群 .在集群名后加上 .backend()
set req.backend_hint = web.backend();
# 匹配清理缓存的请求
if (req.method == "PURGE") {
if (!client.ip ~ purgers) {
return (synth(405, "Not Allowed."));
}
# 是的话就执行清理
return (purge);
}
# 如果不是正常请求 就直接穿透没商量
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "PATCH" &&
req.method != "DELETE") {
return (pipe);
}
# 如果不是GET和HEAD就跳到pass
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
#如果匹配动态内容访问请求就跳到pass
if (req.url ~ "\.(php|asp|aspx|jsp|do|ashx|shtml)($|\?)") {
return (pass);
}
#具有身份验证的请求跳到pass
if (req.http.Authorization) {
return (pass);
}
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(bmp|png|gif|jpg|jpeg|ico|gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)$") {
unset req.http.Accept-Encoding;
} elseif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elseif (req.http.Accept-Encoding ~ "deflate") {
set req.http.Accept-Encoding = "deflate";
} else {
unset req.http.Accept-Encoding;
}
}
if (req.url ~ "\.(css|js|html|htm|bmp|png|gif|jpg|jpeg|ico|gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)($|\?)") {
unset req.http.cookie;
return (hash);
}
# 把真实客户端IP传递给后端服务器 后端服务器日志使用X-Forwarded-For来接收
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;
}
}
return (hash);
}
# hash事件(缓存事件)
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
return (lookup);
}
# 缓存命中事件
sub vcl_hit {
if (req.method == "PURGE") {
return (synth(200, "Purged."));
}
return (deliver);
}
# 缓存不命中事件
sub vcl_miss {
if (req.method == "PURGE") {
return (synth(404, "Purged."));
}
return (fetch);
}
# 返回给用户的前一个事件 通常用于添加或删除header头
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
#取消显示php框架版本的header头
unset resp.http.X-Powered-By;
#取消显示web软件版本、Via(来自varnish)等header头 为了安全
unset resp.http.Server;
unset resp.http.X-Drupal-Cache;
unset resp.http.Via;
unset resp.http.Link;
unset resp.http.X-Varnish;
#显示请求经历restarts事件的次数
set resp.http.xx_restarts_count = req.restarts;
#显示该资源缓存的时间单位秒
set resp.http.xx_Age = resp.http.Age;
#显示该资源命中的次数
set resp.http.hit_count = obj.hits;
#取消显示Age 为了不和CDN冲突
unset resp.http.Age;
#返回给用户
return (deliver);
}
# pass事件
sub vcl_pass {
return (fetch);
}
#处理对后端返回结果的事件(设置缓存、移除cookie信息、设置header头等) 在fetch事件后自动调用
sub vcl_backend_response {
#开启grace模式 表示当后端全挂掉后 即使缓存资源已过期(超过缓存时间) 也会把该资源返回给用户 资源最大有效时间为5分钟
set beresp.grace = 5m;
#后端返回如下错误状态码 则不缓存
if (beresp.status == 499 || beresp.status == 404 || beresp.status == 502) {
set beresp.uncacheable = true;
}
#如请求php或jsp 则不缓存
if (bereq.url ~ "\.(php|jsp)(\?|$)") {
set beresp.uncacheable = true;
} else { //自定义缓存文件的缓存时长,即TTL值
if (bereq.url ~ "\.(css|js|html|htm|bmp|png|gif|jpg|jpeg|ico)($|\?)") {
set beresp.ttl = 15m;
unset beresp.http.Set-Cookie;
} elseif (bereq.url ~ "\.(gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)($|\?)") {
set beresp.ttl = 30m;
unset beresp.http.Set-Cookie;
} else {
set beresp.ttl = 10m;
unset beresp.http.Set-Cookie;
}
}
#返回给用户
return (deliver);
}
sub vcl_purge {
return (synth(200,"success"));
}
sub vcl_backend_error {
if (beresp.status == 500 ||
beresp.status == 501 ||
beresp.status == 502 ||
beresp.status == 503 ||
beresp.status == 504) {
return (retry);
}
}
sub vcl_fini {
return (ok);
}
iii.启动varnish
当启动varnish时有两个重要的参数你必须设置: 一个是处理http
请求的tcp
监听端口,另一个是处理真实请求的后端server
注:如果你使用操作系统自带的包管理工具安装的varnish,你将在下面的文件找到启动参数:
Red Hat
, Centos
: /etc/sysconfig/varnish
1.- a
- a
参数定义了varnish监听在哪个地址,并用该地址处理http
请求,你可能想设置这个参数在众所周知的http 80端口
.
例子:
-a :80
-a localhost:80
-a 192.168.1.100:8080
-a '[fe80::1]:80'
-a '0.0.0.0:8080,[::]:8081'
如果你的webserver
和varnish
运行在同一台机器,你必须换一个监听地址.
2.- f
VCL-file
or -b
backend
-f
添加vcl文件
,- b
定义后端server
varnish需要知道从哪里找到这个需要缓存的http server
.你可以用-b
参数指定,或者帮把它放在vcl文件
中,然后使用-f
参数指定.
在启动的时候使用-b
是一个快捷的方式.
-b 192.168.1.2:80
注意:如果你指定的是name
,这个name
必须能解析成一个IPv4
或者IPv6
的地址
如果你使用-f
参数,你启动的时候可以在-f
指定vcl文件
。
默认的varnish使用100M
的内存来缓存对象,如果你想缓存更多,可以使用-s
参数.
注:Varnish拥有大量的有用的命令行参数,建议查看其帮助
[root@varnish ~]# /usr/local/sbin/varnishd -h
启动
varnish
[root@varnish ~]# /usr/local/sbin/varnishd -f /usr/local/var/varnish/default.vcl -s malloc,200M -a 0.0.0.0:80
[root@varnish ~]# netstat -anpt | grep 80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 6173/varnishd
3.访问测试varnish
现在,varnish已经启动和运行,你可以通过varnish访问您的Web应用程序。
打开火狐浏览器
第一次访问
第二次访问
4.varnish4
配置手动清除缓存
varnish4
通过vcl
配置清楚缓存
通过vcl
配置可以让客户端手动请求清楚缓存,以保证局部数据及时更新,而不用重启varnish服务器。
配置方法
#允许清除缓存IP集
acl purgers {
"127.0.0.1";
"localhost";
"192.168.31.0/24";
}
sub vcl_recv {
……
if (req.method == "PURGE") {
if (!client.ip ~ purgers) {
return (synth(405, "Not Allowed."));
}
return (purge); //清除缓存
}
……
}
sub vcl_purge {
return (synth(200,"success"));
}
打开火狐浏览器,随便进入一个缓存页面,如下图所示。
点击编辑和重发, 修改请求类型为 PURGE
再点击 发送
查看返回状态,如果成功则成功清除缓存,可以按 F5
刷新页面,查看新内容。