varnish

varnish


varnish 介绍

  Varnish cache,或称Varnish,是一套高性能的反向网站缓存服务器(reverse proxy server),其采用全新的软件体系机构,和现在的硬件体系紧密配合,与传统的 squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点,很多大型的网站都开始尝试使用 varnish 来替换 squid,挪威最大的在线报纸 Verdens Gang 使用3台Varnish代替了原来的12台Squid,性能比以前更好,这是 Varnish 最成功的应用案例


varnish 程序架构

varnish 主要由两个进程:Management进程和Child进程(也叫Cache进程)

  • Management进程用来管理配置的变更(包括VCL和参数)、编译VCL、监控Varnish运行、初始化Varnish,以及提供命令行接口等。管理进程会每隔几秒钟检查一下子进程,如果发现子进程挂了,会kill掉然后重新启动一个。这些操作都会记录在日志里面,以利于你检查错误。
  • Child进程包含多种类型的线程:
    • Acceptor线程:接收新的连接请求并回应
    • Worker线程:一个会话一个线程,通常会使用数百个Worker线程
    • Expire线程:负责从缓存中清除旧的内容

Alt text


varnish 状态引擎

VCL有多个状态引擎,状态之间存在相关性,但状态引擎彼此间互相隔离。每个状态引擎可使用return(x)指明关联至哪个下一级引擎。每个状态引擎对应于vcl文件中的一个配置段,即为subroutine。

Alt text
varnish开始处理一个请求时,首先需要分析HTTP请求本身,比如从首部获取请求方法、验正其是否为一个合法的HTT请求等。当这些基本分析结束后就需要做出第一个决策,即varnish是否从缓存中查找请求的资源。这个决定的实现则需要由VCL来完成,简单来说,要由vcl_recv方法来完成。如果管理员没有自定义vcl_recv函数,varnish将会执行默认的vcl_recv函数。然而,即便管理员自定义了vcl_recv,但如果没有为自定义的vcl_recv函数指定其终止操作(terminating),其仍将执行默认的vcl_recv函数。事实上,varnish官方强烈建议让varnish执行默认的vcl_recv以便处理自定义vcl_recv函数中的可能出现的漏洞。

Built in subroutines(内建函数)

Client Side

  1. vcl_recv
    当用户请求到达时,第一个调用的就是vcl_recv,由vcl_recv判定来如何处理请求,vcl_recv通过调用return(ARGUMENTS)来决定后序的执行动作,有以下几种情况:
1. return(hash)
2. return(pass)
3. return(pipe)
4. return(synth(status_code, reason))
5. return(purge)
  1. vcl_pipe
    在进入pipe模式时该函数被调用,在这种模式下,请求直接被送到后端服务器,所有的数据流动均由客户端和服务器之间直接完成,直到连接被关闭。这种情况下,varnish充当传输层的TCP转发器。后序动作:
1. return(pipe)
2. return(synth(status_code,reason))
  1. vcl_pass
    在进入pass模式时该函数被调用,在这种模式下,请求直接被送到后端服务器,后端服务器的响应报文也直接发送给客户端,Varnish不对响应的数据进行缓存。后续动作:
1. return(fetch)
2. return(restart)
3. return(synth(status_code,reason))
  1. vcl_hit
    经过vcl_req()分析,将请求发送到lookup后,经vcl_hash()计算判断是否命中缓存,若命中则自动调用vcl_hit函数。由该模式的规则判断是否使用命中的数据回复客户端,有以下几种后续操作:
1. return(deliver)
2. return(fetch)
3. return(pass)
4. return(restart)
5. return(synth(status_code,reason))
  1. vcl_miss
    vcl_hash()计算判断若未命中缓存,则自动调用vcl_miss函数,由该模式下的规则判断是否从后端服务器中取回数据响应客户端。有以下几种后续操作:
1. return(fetch)
2. return(pass)
3. return(restart)
4. return(synth(status_code,reason))
  1. vcl_hash
    vcl_recv选择后,对于符合缓存要求的请求发送到该模式下进行hash运算判断是否名中缓存

  2. vcl_purge
    进入该模式后调用vcl_purge,所命中的数据将会从缓存中清除,后续操作如下:

1. return(restart)
2. return(synth(status_code,reason))
  1. vcl_deliver
    当请求的数据返回给客户端时调用此函数,后续操作如下:
1. return(deliver)
2. return(restart)
3. return(synth(status_code,reason))
  1. vcl_synth
    调用此函数的目的在于构建一个由VCL根据模板规则自动生成报文回复客户端,该报文不是从后端服务器处获取得到,是由VCL自动生成的。后续操作:
1. return(deliver)
2. return(restart)
3. return(synth(status_code,reason))

Backend Side

  1. vcl_bakend_fetch
    调用此函数到后端服务器去取数据,后续操作有
1. retrun(fetch)
2. return(abandon)
  1. vcl_backend_response
    当后端服务器成功的将响应报文发送过来时,调用此函数,后续操作有:
1. return(deliver)
2. return(abandon)
3. return(retry)
  1. vcl_backend_error
    未能从后端服务器成功取到数据,或所发重试请求数据次数达到时,调用该函数,后续动作:
1. return(deliver)
2. return(retry)

vcl.load / vcl.discard

  1. vcl_init
    VCL加载时第一个执行的初始化函数,任何请求都需经过此函数的执行,通常用于初始化 VMODs,使用return()返回以下两种状态
1. return(ok)
2. return(fail)
  1. vcl_fini
    所有的请求都已经结束,在vcl配置被丢弃时调用,主要用于清理VMODs


VCL 变量

VCL 中有内建变量,也可以自定义变量

VCL 常用内建变量

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.nameBE主机的主机名;
beresp.ttlBE主机响应的内容的余下的可缓存时长;
obj.*
obj.hits:此对象从缓存中命中的次数;
obj.ttl:对象的ttl
server.*
server.ipvarnish主机的IP
server.hostnamevarnish主机的Hostname
client.*
client.ip:发请求至varnish主机的客户端IP
---
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.*:存储在缓存空间中的缓存对象的属性;只读;


VCL 自定义变量

set VALUE_NAME VALUE #自定义变量
unset VALUE_NAME #取消变量


varnish 实例


实例1 : 在发往客户端的响应报文中添加首部字段,值为是否命中缓存,若命中,则增加命中的次数

Alt text

  1. 172.18.17.21 上安装 varnish172.18.17.21上安装httpd
# 172.18.17.21
yum -y install varnish
# 172.18.17.22
yum -y install httpd
  1. 修改varnish监听端口为80, 并启动服务
vim /etc/varnish/varnish.params
...
VARNISH_LISTEN_PORT=80
...
systemctl start varnish.service
  1. 回复客户端的响应报文中添加首部字段,告知是否命中缓存,及命中次数
# 172.18.17.21
[21@root varnish]# cat /etc/varnish/default.vcl
# vcl version
vcl 4.0;
# backend server
backend default {
.host = "192.168.17.22";
.port = "80";
}
# vcl_recv
sub vcl_recv {
}
# vcl_backend_response
sub vcl_backend_response {
}
# vcl_deliver
sub vcl_deliver {
if(obj.hits>0){
set resp.http.X-Cache = "Hit " + obj.hits + " times via " + server.ip;
}else{
set resp.http.X-Cache = "Miss from "+ server.ip;
}
}
  1. 测试
# 第一次请求,未能命中
[20@root ~]# curl http://172.18.17.21 -I
HTTP/1.1 200 OK
Date: Sat, 04 Nov 2017 11:45:46 GMT
Server: Apache/2.4.6 (CentOS)
Last-Modified: Sat, 04 Nov 2017 11:28:45 GMT
ETag: "24-55d2686380d08"
Content-Length: 36
Content-Type: text/html; charset=UTF-8
X-Varnish: 32770
Age: 0
Via: 1.1 varnish-v4
X-Cache: Miss from 172.18.17.21
Connection: keep-alive
# 后续请求命中缓存
[20@root ~]# curl http://172.18.17.21 -I
HTTP/1.1 200 OK
Date: Sat, 04 Nov 2017 11:45:46 GMT
Server: Apache/2.4.6 (CentOS)
Last-Modified: Sat, 04 Nov 2017 11:28:45 GMT
ETag: "24-55d2686380d08"
Content-Length: 36
Content-Type: text/html; charset=UTF-8
X-Varnish: 9 32771
Age: 27
Via: 1.1 varnish-v4
X-Cache: Hit 3 times via 172.18.17.21
Connection: keep-alive


实例2 : 以 /admin,/login开头的URL,要求不缓存

sub vcl_recv {
if(req.url ~ "(?i)^/(admin|login)"){
return(pass);
}
}


实例3 : varnish向后端服务器所发的请求报文X-Forwarded-For字段中,添加反代理服务器的IP

  1. 修改varnish配置文件 /etc/varnish/default.vcl
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;
}
}
}
  1. 在后端httpd服务器日志中读取出首部字段的值
vim /etc/httpd/conf/httpd.conf
LogFormat "%{X-Forwarded-For}i ..." combined