Varnish 简介
Varnish是一款高性能且开源的反向代理服务器和 HTTP 加速器(其实就是带缓存的反向代理服务),它可以把整个HTTP响应内容缓存到内存或文件中,从而提高Web服务器的响应速度。其采用全新的软件体系机构,和现在的硬件体系紧密配合,与传统的 squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点,很多大型的网站都开始尝试使用 varnish 来替换 squid,这些都促进 varnish 迅速发展起来。
Varnish内置强大的VCL(Varnish Configuration Language)配置语言,允许通过各种条件判断来灵活调整缓存策略。在程序启动时,varnish就把VCL转换成二进制代码,因此性能非常高。
挪威的最大的在线报纸 Verdens Gang(vg.no) 使用 3 台 Varnish 代替了原来的 12 台 Squid,性能比以前更好,这是 Varnish 最成功的应用案例。
Varnish特点:
(1)是基于内存缓存,重启后数据将消失。
(2)利用虚拟内存方式,io性能好。
(3)支持设置0~60秒内的精确缓存时间。
(4)VCL配置管理比较灵活。
(5)32位机器上缓存文件大小为最大2G。
(6)具有强大的管理功能,例如top,stat,admin,list等。
(7)状态机设计巧妙,结构清晰。
(8)利用二叉堆管理缓存文件,达到积极删除目的。

Varnish与Squid的对比
说到Varnish,不能不提Squid,Squid是一个高性能的代理缓存服务器,它和varnish之间有诸多的异同点,这里分析如下:
两者相同点:
(1)都是一个反向代理服务器,
(2)都是开源软件,
Varnish的优点:
(1)Varnish的稳定性很高,两者在完成相同负荷的工作时,Squid服务器发生故障的几率要高于Varnish,因为使用Squid要经常重启。
(2)Varnish访问速度更快,Varnish采用了“Visual Page Cache”技术,所有缓存数据都直接从内存读取,而squid是从硬盘读取,因而Varnish在访问速度方面会更快。
(3)Varnish可以支持更多的并发连接,因为Varnish的TCP连接释放要比Squid快。因而在高并发连接情况下可以支持更多TCP连接。
(4)Varnish可以通过管理端口,使用正则表达式批量的清除部分缓存,而Squid是做不到的。
与传统的Squid相比,Varnish的缺点:
(1)varnish在高并发状态下CPU、IO、内存等资源开销都高于Squid。
(2)varnish进程一旦Hang、Crash或者重启,缓存数据都会从内存中完全释放,此时所有请求都会发送到后端服务器,在高并发情况下,会给后端服务器造成很大压力。

Varnish 的后端存储
Varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。后端存储的类型包括:
file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);
malloc:使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象;
persistent(experimental):与file的功能相同,但可以持久存储数据(即重启varnish数据时不会被清除);仍处于测试期;


varnish系统架构

缓存服务器-varnish_代理服务器

arnish主要运行两个进程:Management进程和Child进程(也叫Cache进程)。如上图,

Management进程
Management进程主要实现应用新的配置、编译VCL、监控varnish、初始化varnish以及提供一个命令行接口等。Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。
Management 管理接口:
CLI interface 命令行接口
Telnet interface telnet接口
Web interface Web管理接口

Child/Cache 进程
Child/Cache 进程包含多种类型的线程,常见的如:
Accept 线程:接收新的连接请求并响应;
Worker 线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;
Object Expiry 线程:从缓存中清理过期内容;
Commad line 线程 : 管理接口
Storage/hashing 线程 :缓存存储
Log/stats 线程:日志管理线程
Backend Communication 线程:管理后端主机线程
Varnish依赖“工作区(workspace)”以降低线程在申请或修改内存时出现竞争的可能性。在varnish内部有多种不同的工作区,其中最关键的当属用于管理会话数据的session工作区。

Varnish 缓存的工作流程
Varnish 与一般服务器软件类似,分为 master 进程和 child 进程。Master 进程读入存储配置文件,调用合适的存储类型,然后创建或读入相应大小的缓存文件,接着 master 初始化管理该存储空间的结构体,然后 fork 并监控 child 进程。Child 进程在主线程的初始化的过程中,将前面打开的存储文件整个 mmap 到内存中,此时创建并初始化空闲结构体,挂到存储管理结构体,以待分配。Child 进程分配若干线程进行工作,主要包括一些管理线程和很多 worker 线程。
接着,开始真正的工作,varnish 的某个负责接收新 HTTP 连接线程开始等待用户,如果有新的 HTTP 连接过来,它总负责接收,然后唤醒某个等待中的线程,并把具体的处理过程交给它。Worker 线程读入 HTTP 请求的 URI,查找已有的 object,如果命中则直接返回并回复用户。如果没有命中,则需要将所请求的内容,从后端服务器中取过来,存到缓存中,然后再回复。
分配缓存的过程:它根据所读到 object 的大小,创建相应大小的缓存文件。为了读写方便,程序会把每个 object 的大小变为最接近其大小的内存页面倍数。然后从现有的空闲存储结构体中查找,找到最合适的大小的空闲存储块,分配给它。如果空闲块没有用完,就把多余的内存另外组成一个空闲存储块,挂到管理结构体上。如果缓存已满,就根据 LRU 机制,把最旧的 object 释放掉。
释放缓存的过程:有一个超时线程,检测缓存中所有 object 的生存期,如果超初设定的 TTL(Time To Live)没有被访问,就删除之,并且释放相应的结构体及存储内存。注意释放时会检查该存储内存块前面或后面的空闲内存块,如果前面或后面的空闲内存和该释放内存是连续的,就将它们合并成更大一块内存。
整个文件缓存的管理,没有考虑文件与内存的关系,实际上是将所有的 object 都考虑是在内存中,如果系统内存不足,系统会自动将其换到 swap 空间,而不需要 varnish 程序去控制。、

Varnish Configuration Language - VCL(varnish配置语言-VCL)
Varnish有一个很棒的配置系统,大部分其他的系统使用配置指令,让您打开或者关闭一些开关。Varnish使用区域配置语言,这种语言叫做“VCL”(varnish configuration language),在执行vcl时,varnish就把VCL转换成二进制代码。
VCL文件被分为多个子程序,不同的子程序在不同的时间里执行,比如一个子程序在接到请求时执行,另一个子程序在接收到后端服务器传送的文件时执行。   
varnish将在不同阶段执行它的子程序代码,因为它的代码是一行一行执行的,不存在优先级问题。随时可以调用这个子程序中的功能并且当他执行完成后就退出。
   如果到最后您也没有调用您的子进程中的功能,varnish将执行一些内建的VCL代码,这些代码就是default.vcl中被注释的代码。
   99%的几率您需要改变vcl_recv 和 vcl_fetch这两个子进程。
Varnish 状态引擎(state engine)
VCL用于让管理员定义缓存策略,而定义好的策略将由varnish的management进程分析、转换成C代码、编译成二进制程序并连接至child进程。varnish内部有几个所谓的状态(state),在这些状态上可以附加通过VCL定义的策略以完成相应的缓存处理机制,因此VCL也经常被称作“域专用”语言或状态引擎,“域专用”指的是有些数据仅出现于特定的状态中。
1.VCL状态引擎
在VCL状态引擎中,状态之间具有相关性,但彼此间互相隔离,每个引擎使用return(x)来退出当前状态并指示varnish进入下一个状态。
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函数中的可能出现的漏洞。

 

HTTP请求基本处理流程

缓存服务器-varnish_机构_02

Varnish处理HTTP请求的过程大致分为如下几个步骤:
Receive 状态(vcl_recv):也就是请求处理的入口状态,根据VCL规则判断该请求应该pass(vcl_pass)或是 pipe(vcl_pipe),还是进入 lookup(本地查询);
Lookup 状态:进入该状态后,会在 hash 表中查找数据,若找到,则进入 hit(vcl_hit)状态,否则进入 miss(vcl_miss)状态;
Pass(vcl_pass)状态:在此状态下,会直接进入后端请求,即进入 fetch(vcl_fetch)状态;
Fetch(vcl_fetch)状态:在 fetch 状态下,对请求进行后端获取,发送请求,获得数据,并根据设置进行本地存储;
Deliver(vcl_deliver)状态:将获取到的数据发给客户端,然后完成本次请求;

varnish内置函数

vcl_recv:用于接收和处理请求;当请求到达并成功接收后被调用,通过判断请求的数据来决定如何处理请求;
vcl_pipe:此函数在进入pipe模式时被调用,用于将请求直接传递至后端主机,并将后端响应原样返回客户端;
vcl_pass:此函数在进入pass模式时被调用,用于将请求直接传递至后端主机,但后端主机的响应并不缓存直接返回客户端;
vcl_hit:在执行 lookup 指令后,在缓存中找到请求的内容后将自动调用该函数;
vcl_miss:在执行 lookup 指令后,在缓存中没有找到请求的内容时自动调用该方法,此函数可用于判断是否需要从后端服务器获取内容;
vcl_hash:在vcl_recv调用后为请求创建一个hash值时,调用此函数;此hash值将作为varnish中搜索缓存对象的key;
vcl_purge:pruge操作执行后调用此函数,可用于构建一个响应;
vcl_deliver:将在缓存中找到请求的内容发送给客户端前调用此方法;
vcl_backend_fetch:向后端主机发送请求前,调用此函数,可修改发往后端的请求;
vcl_backend_response:获得后端主机的响应后,可调用此函数;
vcl_backend_error:当从后端主机获取源文件失败时,调用此函数;
vcl_init:VCL加载时调用此函数,经常用于初始化varnish模块(VMODs)
vcl_fini:当所有请求都离开当前VCL,且当前VCL被弃用时,调用此函数,经常用于清理varnish模块;

Varnish内置公用变量:
VCL内置的公用变量可以用在不同的VCL函数中,根据这些公用变量使用的不同阶段。
 缓存服务器-varnish_软件_03

内置变量的分类(如上图):

req:请求到达时可用的变量
bereq:向后端主机请求时可用的变量
beresp:从后端主机获取内容时可用的变量
resp:对客户端响应时可用的变量
obj:存储在内存中时对象属性相关的可用的变量


Varnish安装与配置及相关实例

一、环境准备:
系统环境
Centos6.5(2.6.32-431.el6.x86_64) 关闭防火墙和Selinux
IP规划:
node:eth0:172.16.19.20/16
node1:eth0: 172.16.19.21/16
node2:eth0: 172.16.19.22/16
主机hosts文件

# cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 172.16.19.21 node1.luo.com node1 172.16.19.22 node2.luo.com node2 172.16.19.23 node3.luo.com node3

时间同步
# ntpdate 172.16.0.1
每隔10分钟同步时间crontab -e 编辑crontab工作内容如下:
*/10 * * * * /usr/sbin/ntpdate 172.16.0.1 &>/dev/null&&/sbin/hwclock-w
# service crond restart


二、后端(WEB)服务器配置
分别为node1与node2安装httpd并写好测试页面
# yum install httpd
node1测试页面

# echo "<h1>node1.luo.com</h1>" > /var/www/html/index.html # echo "<h1>test.com</h1>" > /var/www/html/test.html # echo "<h1>node1 is alived.....................</h1>" > /var/www/html/.health.html # service httpd restart # curl http://172.16.19.21

node2测试页面
#  echo "<h1>node2.luo.com</h1>" > /var/www/html/index.html
# echo "<h1>node2 is alived.....................</h1>"  > /var/www/html/.health.html
# service httpd restart
# curl http://172.16.19.22

三、varnish服务器配置

软件下载与安装
varnish下载地址:
https://repo.varnish-cache.org/redhat/
https://www.varnish-cache.org/releases

本文使用varnish-3.0.5版本进行演示,最新版本为varnish-4.0.1,在此使用rpm方式安装
yum安装已下载的rpm包可解决依赖关系:
# yum install -y varnish-3.0.5-1.el6.x86_64.rpm \
varnish-libs-3.0.5-1.el6.x86_64.rpm \
varnish-docs-3.0.5-1.el6.x86_64.rpm

主要配置文件

/etc/rc.d/init.d/varnish # 启动服务脚本 /etc/sysconfig/varnish # 启动脚本的配置文件 /etc/varnish #全局配置文件 /etc/varnish/default.vcl #默认VCL /var/log/varnish #日志文件

初次启动varnish

a、修改启动脚本的配置文件
# vim /etc/sysconfig/varnish

VARNISH_LISTEN_PORT=80 #这里设置的监听端口设置为6081 VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 #远程管理IP VARNISH_ADMIN_LISTEN_PORT=6082 #管理端口 VARNISH_STORAGE="malloc,64M" #我们这里设置的是64M,因为虚拟机内存小,在生产环境中可以根据实际需求设置 #VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}"

b、启动服务,查看监听端口80与6082
# service varnish start
# ss -tnlp |grep varnish
LISTEN     0      128                      :::80                    :::*      users:(("varnishd",9563,8))
LISTEN     0      128                       *:80                     *:*      users:(("varnishd",9563,7))
LISTEN     0      10                127.0.0.1:6082                     *:*      users:(("varnishd",9562,6))

配置文件详解

# cat /etc/sysconfig/varnish # Configuration file for varnish # # /etc/init.d/varnish expects the variable $DAEMON_OPTS to be set from this # shell script fragment. # # Maximum number of open files (for ulimit -n) NFILES=131072 #打开最大文件数 # Locked shared memory (for ulimit -l) # Default log size is 82MB + header MEMLOCK=82000 #默认日志大小 # Maximum number of threads (for ulimit -u) NPROCS="unlimited" #最大线程数 # Maximum size of corefile (for ulimit -c). Default in Fedora is 0 # DAEMON_COREFILE_LIMIT="unlimited" #最大内核打开的文件数 # Set this to 1 to make init script reload try to switch vcl without restart. # To make this work, you need to set the following variables # explicit: VARNISH_VCL_CONF, VARNISH_ADMIN_LISTEN_ADDRESS, # VARNISH_ADMIN_LISTEN_PORT, VARNISH_SECRET_FILE, or in short, # use Alternative 3, Advanced configuration, below RELOAD_VCL=1 #是否自动加载VCL # This file contains 4 alternatives, please use only one. ## Alternative 1, Minimal configuration, no VCL #方案1,最小配置,不方便 # # Listen on port 6081, administration on localhost:6082, and forward to # content server on localhost:8080. Use a fixed-size cache file. # #DAEMON_OPTS="-a :6081 \ # -T localhost:6082 \ # -b localhost:8080 \ # -u varnish -g varnish \ # -s file,/var/lib/varnish/varnish_storage.bin,1G" ## Alternative 2, Configuration with VCL #方案2,配置组件 # # Listen on port 6081, administration on localhost:6082, and forward to # one content server selected by the vcl file, based on the request. Use a # fixed-size cache file. # #DAEMON_OPTS="-a :6081 \ # -T localhost:6082 \ # -f /etc/varnish/default.vcl \ # -u varnish -g varnish \ # -S /etc/varnish/secret \ # -s file,/var/lib/varnish/varnish_storage.bin,1G" ## Alternative 3, Advanced configuration #方案3,高级配置 # # See varnishd(1) for more information. # # # Main configuration file. You probably want to change it :) VARNISH_VCL_CONF=/etc/varnish/default.vcl #默认的VCL存放位置 # # # Default address and port to bind to # # Blank address means all IPv4 and IPv6 interfaces, otherwise specify # # a host name, an IPv4 dotted quad, or an IPv6 address in brackets. # VARNISH_LISTEN_ADDRESS= VARNISH_LISTEN_PORT=6081 #服务监听端口 # # # Telnet admin interface listen address and port VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 #管理IP VARNISH_ADMIN_LISTEN_PORT=6082 #管理端口 # # # Shared secret file for admin interface VARNISH_SECRET_FILE=/etc/varnish/secret #默认的加密文件 # # # The minimum number of worker threads to start VARNISH_MIN_THREADS=50 #最小线程数 # # # The Maximum number of worker threads to start VARNISH_MAX_THREADS=1000 #最大线程数 # # # Idle timeout for worker threads VARNISH_THREAD_TIMEOUT=120 #线程超时时间 # # # Cache file location VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin #缓存文件的位置 # # # Cache file size: in bytes, optionally using k / M / G / T suffix, # # or in percentage of available disk space using the % suffix. VARNISH_STORAGE_SIZE=1G #设置存储的大小 # # # Backend storage specification #后端存储规范,这里是我们主要配置的地方 VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}" #默认存储在文件里,我们这里修改为malloc # # # Default TTL used when the backend does not specify one #默认TTL时使用的后端不指定一个 VARNISH_TTL=120 # # # DAEMON_OPTS is used by the init script. If you add or remove options, make # # sure you update this section, too. #所有的启动选项 DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \ -f ${VARNISH_VCL_CONF} \ -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \ -t ${VARNISH_TTL} \ -w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} \ -u varnish -g varnish \ -S ${VARNISH_SECRET_FILE} \ -s ${VARNISH_STORAGE}" # ## Alternative 4, Do It Yourself. See varnishd(1) for more information. #方案4,设置你自己的配置。看到varnishd(1)更多的信息。 # # DAEMON_OPTS=""

 

# cat /etc/varnish/test.vcl

#配置后端服务器 backend web1 { .host = "172.16.19.21"; .port = "80"; } sub vcl_recv { set req.backend = web1; } #设置条件让请求test.html文件时不缓存 sub vcl_recv { if (req.url ~ "^/test.html$") { return(pass); } set req.backend = web1; } #修改vcl_deliver增一个响应头部 sub vcl_deliver { if (obj.hits > 0) { set resp.http.X-Cache = "Hit from"+" "+server.ip; } else { set resp.http.X-Cache = "Miss via"+" "+server.ip; } }

# varnishadm
varnish> vcl.load a1 ./test1.vcl 200 VCL compiled. varnish> vcl.use a1 200 varnish> vcl.list 200 available 0 boot active 1 a1 varnish> vcl.show a1 200 backend web1 { .host = "172.16.19.21"; .port = "80"; } .............

 

主页面可以缓存命中。访问网页第一的时候是X-Cache:Miss from 172.16.19.20,第二次刷新就可以命中缓存,如下图

缓存服务器-varnish_加速器_04
配置test.html文件不可以缓存,测试结果

 缓存服务器-varnish_加速器_05
无论你访问多少次(ctrl+F5强制刷新),缓存都不会命中的。X-Cache: Miss via 172.16.19.20

 

实例:

# 创建后端主机 backend web1 { .host = "172.16.19.21"; .port = "80"; .probe = { .url = "/.health.html"; .window = 5; .threshold = 2; .interval = 3s; .timeout = 2s; } } backend web2 { .host = "172.16.19.22"; .port = "80"; .probe = { .url = "/.health.html"; .window = 5; .threshold = 2; .interval = 3s; .timeout = 2s; } } # 创建后端负载均衡主机组,即directors,random算法; director webgroup random { { .backend = web1; .weight = 1; } { .backend = web2; .weight = 1; } } acl purgers { # 定义刷新缓存的来源IP; "127.0.0.1"; "192.168.1.0"/24; "172.16.0.0"/16; } sub vcl_recv { if (req.request == "PURGE") { if (!client.ip ~ purgers) { error 405 "Method not allowed"; } return (lookup); } set req.backend = webgroup; } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged"; } } sub vcl_miss { if (req.request == "PURGE") { purge; error 404 "Not in cache"; } } sub vcl_pass { if (req.request == "PURGE") { error 502 "PURGE on a passed object"; } } sub vcl_deliver { if (obj.hits > 0) { # 为响应添加X-Cache首部,显示缓存是否命中; set resp.http.X-Cache = "HIT " + server.ip; } else { set resp.http.X-Cache = "MISS " + server.ip; } }

通过 varnishadm 手动清除缓存
varnish> ban.url  .*$ #清除所有
varnish> ban.url  /index.html #清除index.html页面缓存
varnish> ban.url /admin/$ #清除admin目录缓存

负载均衡验证:
 缓存服务器-varnish_机构_06

 

缓存服务器-varnish_代理服务器_07