一、Varnish的基本介绍
Varnish与一般服务器软件类似,就是一个web缓存代理服务器,分为master(management)进程和child(worker,主要做cache的工作)进程(也叫Cache进程)。master进程读入命令,进行一些初始化,然后fork并监控child进程。child进程分配若干线程进行工作,主要包括一些管理线程和很多woker线程。
Management进程主要实现应用新的配置、编译VCL、监控varnish、初始化varnish以及提供一个命令行接口等。 Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。
Child进程包含多种类型的线程,常见的如下:
Acceptor线程:接收新的连接请求并响应;
Worker线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;
Expiry线程:从缓存中清理过期内容;
Varnish依赖“工作区(workspace)”以降低线程在申请或修改内存时出现竞争的可能性。在varnish内部有多种不同的工作区,其中最关键的当属用于管理会话数据的session工作区。
二、Varnish的特性
1、Varnish的稳定性很高,两者在完成相同负荷的工作时,Squid服务器发生故障的几率要高于Varnish,因为使用Squid要经常重启;
2、Varnish访问速度更快,因为采用了“Page Cache”技术,所有缓存数据都直接从内存读取(映射),而squid是从硬盘读取,因而Varnish在访问速度方面会更快;
3、Varnish可以支持更多的并发连接,因为Varnish的TCP连接释放要比Squid快,因而在高并发连接情况下可以支持更多TCP连接;
4、Varnish可以通过管理端口,使用正则表达式批量的清除部分缓存,而Squid是做不到的;
5、squid属于是单进程使用单核CPU,但Varnish是通过fork形式打开多进程来做处理,所以可以合理的使用所有核来处理相应的请求。
补充知识:Varnish和Squid的不同
Squid是从硬盘读取缓存的数据,而Varnish是把数据存放在内存中,直接从读取内存,避免了频繁在内存、磁盘中交换文件,所以Varnish要相对更高效,但也有缺点,内存中的缓存在服务器重启后会丢失。
varnish和squid在中小规模的应用上,varnish足够轻量级,足够好用,但是在巨大的并发请求来说,单个varnish所能够承载的并发访问量大概在5000个连接请求左右,超出5000个可能就就得不稳定了;而在这里squid就能表现出良好的性能了,因此在大规模的企业级应用中仍然是 以squid居多,而在中小规模的自己公司的反向代理缓存中varnish居多。
三、VCL的介绍
VCL,Varnish Configuration Language 是varnish配置缓存策略的工具,它是一种基于“域”(可想象与iptables的几个链,也就是类似钩子函数)的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用set自定义变量、支持if判断语句,也有内置的函数和变量等。
使用VCL编写的缓存策略通常保存至.vcl文件中,其需要编译成二进制的格式后才能由varnish调用。事实上,整个缓存策略就是由几个特定的子例程如vcl_recv、vcl_hash等组成,它们分别在不同的位置(或时间)执行,如果没有事先为某个位置自定义子例程,varnish将会执行默认的定义。
VCL的工作方式是基于状态引擎(state engine)来实现的。
vcl内部有很多state engine,类似于iptables上的钩子函数,分别作用于不同的逻辑位置,常用的有:
vcl_recv: 当请求报文进来时,会使用这里的规则去判断,比如该请求是不是能缓存的类型,是否允许它访问本地的varnish服务等;
vcl_hash:在此处对请求报文中的特定属性做hash计算,得出用于比对key的特征码;
vcl_hit : 当缓存命中之后,我们要做什么操作;
vcl_miss : 缓存miss之后,我们要做什么操作;
vcl_backend_fetch: 与后端主机通信时,我们要做什么操作;
vcl_deliver :响应数据给客户前,我们要做什么操作,相当于iptables的POSTROUTING链;
vcl_pass : 如果我们希望某些报文不要查询缓存的话,可以直接送到vcl_pass中进行处理;vcl_pass中的报文会直接送往后端主机;
vcl_pipe : 比较特殊的engine,会直接把上游来的请求报文送往后端服务器;
vcl_error : 当拒绝用户访问时,可以直接通过这个engine手动合成一个信息页面,告诉用户拒绝访问原因之类的信息。使用这个engine的好处是,当我们预先判定用户的某些请求一定会被拒绝时,我们可以直接在varnish层就把用户的请求打回去;
vcl_purge :当管理员要手动修剪缓存条目时,我们要执行什么规则;
这些engine之间之前彼此是有关联性的,也有先后的逻辑顺序,和iptables的 PREROUTING -- > INPUT 这种逻辑很类似。比如vcl_recv基本上是所有engine的上游,它需要根据制定好的规则,来判断每个报文的下游engine是哪一个。
state engine的常见数据流向:
vcl_recv --> lookup --> vcl_hash --> cached --> vcl_hit--> vcl_deliver
数据报文进来之后先检查,如果可缓存,则送进缓存中匹配,如果命中了,则直接响应给客户。
vcl_recv --> lookup --> vcl_hash --> cached --> vcl_miss --> vcl_backend_fetch --> vcl_deliver
数据报文进来之后先检查,如果可缓存,则送进缓存中匹配,如果没命中缓存,varnish会替用户去后端应用服务器请求对应的页面,先缓存下来,然后响应给客户。
vcl_recv --> vcl_pipe --> backendserver
送往vcl_pipe的报文不会做任何处理直接送到后端服务器。
vcl_recv --> vcl_pass --> vcl_backend_fetch --> vcl_deliver
通常用于不能缓存的内容,不检查缓存直接送往vcl_pass,经由pass送到后端服务器去响应
四、Varnish的工作原理及工作流程
上图说明:
vcl_recv的结果如果可以查询缓存并可以识别,那就要到vcl_hash这步了,如果无法识别那就通过pipe(管道)送给vcl_pipe,如果能识别,但不是一个可缓存的对象,那就通过pass送到vcl_pass去,vcl_hash之后就可查看缓存中有没有了,有这个请求的对象就表示命中 (vcl_hit),如果没有那就表示未命中(vcl_miss),如果命中的就可以直接通过deliver直接送给vcl_deliver响应了,如果 未命中就通过fetch交给vcl_fatch去后端服务器上去取数据,取回数据之后如果数据可以缓存就缓存(cache),本地缓存完之后再构建响应, 如果不可以缓存就不做缓存交给vcl_deliver响应了;而如果命中了交给vcl_pass,交给pass之后就要到Fetch objet from backend后端服务器上去取数据了,这是因为这个命中的对象可能是过期或者是要做单独立额外的处理的;这就是vcl的状态引擎过程。
五、实例
实验:实现基于Keepalived+Haproxy+Varnish+LNMP企业级架构
一、环境准备:
两台haproxy(一台master,一台backup,VIP:172.17.253.115)(对varnish实现负载均衡)
一台varnish(IP分别为:192.168.159.139)
两台后端服务器(已实现lnmp)(IP分别为:192.168.159.120 192.168.159.121)
二、安装步骤:
1、在用作haproxy负载均衡的机器上安装keepalived和haproxy
yum install keepalived
yum install haproxy
2、在用作varnish的服务器上安装varnish
yum install varnish
三、修改配置文件及启动服务
1、对主haproxy操作:主要代码如下
①vim /etc/keepalived/keepalived.conf 实现高可用
! Configuration File for keepalived
global_defs {
notification_email {
[email protected] #收件人
}
notification_email_from [email protected]
smtp_server 127.0.0.1 #发件的服务器
smtp_connect_timeout 30
router_id LVS_DEVEL2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 14
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 111111
}
virtual_ipaddress {
172.17.253.115
}
启动服务:systemctl start keepalived
②vim /etc/haproxy/haproxy.cfg 实现负载均衡
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
stats socket /var/lib/haproxy/stats
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
listen stats
mode http #基于http协议
bind 0.0.0.0:1080 #监听1080端口
stats enable #开启统计报告服务
stats hide-version #隐藏统计报告版本信息
stats uri /haproxyadmin #统计报告访问url
stats realm Haproxy\ Statistics #页面登陆信息
stats auth admin:admin #验证账号信息
stats admin if TRUE #验证模式
frontend http-in
bind *:80
default_backend cache
backend cache
balance roundrobin #负载均衡算法
server cache1 192.168.159.139:6081(varnish对应端口) check
启动服务:systemctl start haproxy (打开了80和1080端口)
2、对从haproxy操作:
①vim /etc/keepalived/keepalived.conf
配置基本同上面主的,只需要修改下面两行,然后启动服务
state BACKUP
priority 90
②vim /etc/haproxy/haproxy.cfg
配置完全同上面主的,配置完成后启动服务
3、两台varnish上进行同样的操作:
①vim /etc/varnish/default.vcl
vcl 4.0; #版本
import directors; #导入模块
probe backend_healthcheck { #定义健康状态检测
.url = "/index.html"; #检测的页面
.window = 5; #窗口
.threshold = 1; #门槛,1表示至少有一个正常工作
.interval = 3s; #检测频度
.timeout = 1s; #超时时长
}
backend web1 { #定义后端服务器
.host = "192.168.159.120";
.port = "80";
.probe = backend_healthcheck; #健康状态检测
}
backend web2 { #定义后端服务器
.host = "192.168.159.121";
.port = "80";
.probe = backend_healthcheck; #健康状态检测
}
sub vcl_init { #初始化
new web_cluster = directors.round_robin(); #定义后端服务器组,使用轮询算法
web_cluster.add_backend(web1); #引用上面定义的服务器
web_cluster.add_backend(web2);
}
sub vcl_recv { #定义入口函数
if(req.url ~ "index.php"){ #不缓存index.php页面,直接跳到pass
return (pass);
}
if(req.method == "GET"){ #请求方法为GET的就缓存
return (hash);
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "PURGE" &&
req.method != "DELETE"){
return (pipe); #如果不属于以上列出的方法,那么就通过管道直接传递到后端服务器
}
return (hash);
}
sub vcl_hash{ #定义hash
hash_data(req.url); #对请求的url进行hash处理
}
sub vcl_backend_response { #自定义缓存文件时长,即TTL值
if(bereq.url ~ "\.(jpg|jpeg|gif|png)$"){
set beresp.ttl = 30d; #缓存图片30天
}
if(bereq.url ~ "\.(html|css|js)$"){
set beresp.ttl = 7d; #缓存静态页面7天
}
return (deliver);
}
sub vcl_deliver { #为响应添加首部,显示缓存是否命中
if(obj.hits > 0){
set resp.http.X-Cache = "HIT from " + server.ip; #命中
}
else{
set resp.http.X-Cache = "MISS"; #没命中
}
unset resp.http.Via; #取消显示varnish版本号
}
②vim /etc/varnish/varnish.params
VARNISH_STORAGE="file,/var/lib/varnish/bin,1G" #以文件形式缓存,大小为1G,存在/var/lib/varnish/bin文件中
VARNISH_LISTEN_PORT=6081 #监听端口为6081
启动服务:systemctl start varnish (打开了6081端口)
查看页面是否生效
六、Varnish命令行工具
Varnish常见工具使用
1>、varnishstat – Varnish Cache statistics 各种计数器
-1 批次显示,只显示1次
-1 -f FILED_NAME
-l:可用于-f选项指定的字段名称列表;
# varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss
# varnishstat -l -f MAIN -f MEMPOOL
2>、varnishtop – Varnish log entry ranking 将日志文件中相关数据逆序排序
-1 Instead of a continously updated display, print the statistics once and exit. -i taglist,可以同时使用多个-i选项,也可以一个选项跟上多个标签;筛选
-I <[taglist:]regex>
-x taglist:排除列表
-X <[taglist:]regex>
varnishtop -i RespStatus 查看响应码
3>、varnishlog – Display Varnish logs 查看实时日志
4>、 varnishncsa – Display Varnish logs in Apache / NCSA combined log format 标准日志格式
七、Varnish的后端存储
varnish的缓存对象在每次服务重启时都会被清空并重新建立,所以这些服务器都是不应该随便去重启的,varnish为了把数据更持久化的存储,引入了更多的存储机制,所以varnish支持多种不同的后端存储;
varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。后端存储的类型包括:
(1)file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);
(2)malloc:使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象;
(3)persistent(experimental):与file的功能相同,但可以持久存储数据(即重启varnish数据时不会被清除);仍处于测试期;
varnish无法追踪某缓存对象是否存入了缓存文件,从而也就无从得知磁盘上的缓存文件是否可用,因此,file存储方法在varnish停止或重启 时会清除数据。而persistent方法的出现对此有了一个弥补,但persistent仍处于测试阶段,例如目前尚无法有效处理要缓存对象总体大小超 出缓存空间的情况,所以,其仅适用于有着巨大缓存空间的场景。
八、Varnish检测后端主机的健康状态
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.magedu.com"
"Connection: close";
(3) .window:设定在判定后端主机健康状态时基于最近多少次的探测进行,默认是8;
(4) .threshold:在.window中指定的次数中,至少有多少次是成功的才判定后端主机正健康运行;默认是3;
(5) .initial:Varnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold;
(6) .expected_response:期望后端主机响应的状态码,默认为200;
(7) .interval:探测请求的发送周期,默认为5秒;
(8) .timeout:每次探测请求的过期时长,默认为2秒;
例如:
backend server1 {
.host = “172.16.42.3”;
.port = “80”;
.probe = {
.url= “/.healthcheck.html” #得先创建这个测试页面;
.timeout= 1s;
.interval= 2s;
.window=5;
.threshold=5;
}