关于 Varnish 系列其他文章的传送门

一、Cache is King¶
为什么需要缓存¶
Cache is King 的理念在计算机的方方面面都能得到体现,从 CPU 的 L1、L2、L3 级缓存,再到硬盘的缓存模块,就连内存其实也是为了缓存各种数据而存在。
在互联网的应用中,很多内容其实是不需要经常变化的,因此可以将这些不需要经常变动的数据生成缓存,可以有以下几点好处:
- 可以节约大量重复计算和资源;
- 降低延迟,提升用户体验。

缓存是如何实现的¶
一个程序的运行具有局部性特征:
- 时间局部性:一个数据被访问过之后,可能很快会被再次访问到;
- 空间局部性:一个数据被访问时,其周边的数据也有可能被访问到。
所以通常会在后端服务器前增加 cache 服务器,如果客户端的请求命中了 cache,缓存服务器就会直接响应给客户端。所以对热区数据做缓存的话,能大大提高响应速度,也能减轻后端服务器的压力。
缓存有时效性,缓存空间也会耗尽,会使用 LRU,遵循最近最少使用原则清理过期缓存。
缓存命中率算法:hit/(hit+miss)
- 页面命中率:基于页面数量进行衡量;
- 字节命中率:基于页面的体积进行衡量。
缓存有两个层级:
- 私有缓存:用户代理附带的本地缓存机制,比如浏览器缓存;
- 公共缓存:反向代理服务器的缓存功能,比如对热区数据的缓存。
实现缓存功能的软件¶
缓存软件目前有两个开源的解决方案,他们的关系就像 Apache 和 Nginx 一样:
- squid:老牌缓存服务器,支持的功能多,但是性能差;
- Varnish:年轻的新生力量,虽然功能和 squid 比略徐一筹,但是运行是却是丝滑般流畅;
这里主要介绍 Varnish。
二、Varnish 介绍¶
什么是 Varnish¶
Varnish 是一款高性能的开源的 HTTP 加速器,挪威最大的在线报纸平台 Verdens Gang 使用 3 台 Varnish 代替了原来的 12 台 Squid,而且性能比以前更好。

Varnish 的作者 Poul-Henning Kamp 是 FreeBSD 的内核开发者之一,他认为现在的计算机比起 1975 年已经复杂许多。在 1975 年时,储存媒介只有两种:内存与硬盘。但现在计算机系统的内存除了主存外,还包括了 CPU 内的 L1、L2,甚至有 L3 缓存。硬盘上也有自己的缓存装置,因此 Squid Cache 自行处理物件替换的架构不可能得知这些情况而做到最佳化,但操作系统可以得知这些情况,所以这部份的工作应该交给操作系统处理,这就是 Varnish cache设计架构。
Varnish 项目在 2006 年发布第一个版本 0.9,经过 varnish 开发团队和网友们的辛苦耕耘,现在的 varnish 已经很健壮。很多门户网站已经部署了 varnish,并且反应都很好,甚至比 squid 还稳定,且效率更高,资源占用更少。相信在反向代理,web 加速方面,varnish 已经有足够能力代替squid。
Varnish 的特点¶
Varnish 是一个轻量级的 Cache 和反向代理软件。先进的设计理念和成熟的设计框架是 Varnish 的主要特点。现在的 Varnish 总共代码量不大,虽然功能在不断改进,但是还需要继续丰富和加强。
下面总结了 Varnish 的一些特点:
- 基于内存进行缓存,重启后数据将消失;
- 利用虚拟内存方式,I/O 性能好;
- 支持设置 0~60 秒内的精确缓存时间;
- VCL 配置管理比较灵活;
- 32 位机器上缓存文件大小为最大 2GB;
- 具有强大的管理功能,例如 top,stat,admin,list 等;
- 状态机设计巧妙,结构清晰;
- 利用二叉堆管理缓存文件,达到积极删除目的。
Varnish 与 Squid 对比¶
说到 Varnish,不能不提 Squid,Squid 是一个高性能的代理缓存服务器,它和 Varnish 之间有诸多的异同点,下面进行分析。
下面是 Varnish 与 Squid 之间的相同点。
- 都是一个反向代理服务器;
- 都是开源软件。
下面是它们的不同点,也是 Varnish 的优点:
- Varnish 的稳定性很高。两者在完成相同负荷的工作时,Squid 服务器发生故障的几率要高于 Varnish,因为 Squid 需要经常重启;
- Varnish 访问速度更快。Varnish 采用了 “Visual Page Cache” 技术,所有缓存数据都直接从内存读取,而 Squid 是从硬盘读取缓存数据,因此 Varnish 在访问速度方面会更快;
- Varnish 可以支持更多的并发连接。因为 Varnish 的 TCP 连接释放要比 Squid 快,所以在高并发连接情况下可以支持更多 TCP 连接;
- Varnish 可以通过管理端口,使用正则表达式批量清除部分缓存,而 Squid 做不到。

当然,与传统的 Squid 相比,Varnish 也有缺点:
- Varnish 在高并发状态下 CPU、I/O 和内存等资源开销都高于 Squid;
- Varnish 进程一旦挂起、崩溃或者重启,缓存数据都会从内存中完全释放,此时所有请求都会被发送到后端服务器,在高并发情况下,这会给后端服务器造成很大压力。
Varnish 官方站点:http://www.varnish-cache.org/
分为商业版和社区版:
- Community
- Enterprise
三、Varnish 的安装¶
安装 varnish¶
Varnish 已经被收录在 EPEL 源中,安装 EPEL 源并使用 YUM 即可安装。
1 | yum install epel-release |
程序架构¶

Varnish 程序和 Nginx 类似分为主进程和子进程。
- Manager 进程;
- Child/cache 进程。
Management 进程负责:
- 提供命令行管理工具;
- 完成初始化;
- 管理子进程;
- 编译缓存的步骤;
- VCL 编译器读取
VCL(Varnish Configuration Language),转换成 C 语言代码; - 由 C 语言编译器再转换成二进制的
Shared opject,再由 Child 进程加载成共享模块使用。
- VCL 编译器读取
Child/cache 进程负责:
- 提供命令行接口;
- 把缓存构建为 HASH 格式的数据,存放到存储;
- 记录缓存使用的日志和运行状态;
- 接收用户请求;
- 与后端被代理的服务器通信;
- 处理用户请求;
- 缓存过期管理。
Varnish 的程序环境¶
/usr/sbin/varnishd:主程序;/usr/bin/varnishadm:命令行工具;/usr/bin/varnishtest:测试工具程序:/usr/sbin/varnish_reload_vcl:VCL 配置文件重载程序;/usr/lib/systemd/system/varnish.service:Systemd Unit File;/usr/lib/systemd/system/varnishlog.service:日志持久的服务;/usr/lib/systemd/system/varnishncsa.service:日志持久的服务;
配置文件:
/etc/varnish/varnish.params:配置 Varnish 服务进程的工作特性,例如监听的地址和端口,缓存机制等,通过 Systemd Unit File 加载;/etc/varnish/default.vcl:配置各 Child/Cache 线程的缓存策略;
Shared Memory Log 交互工具:
/usr/bin/varnishhist:保存日志为原始格式,日志和统计计数器等是放置在内存中的,如果想永久保存到磁盘中的工具,可使用此工具的服务;/usr/bin/varnishlog:每一个请求独立显示。/usr/bin/varnishncsa:保存日志为http_combined格式,日志和统计计数器等是放置在内存中的,如果想永久保存到磁盘中的工具,可使用此工具的服务;/usr/bin/varnishstat:交互式的显示统计信息;/usr/bin/varnishtop:实时显示速率日志;
Varnish 程序的选项¶
程序选项:从 /etc/varnish/varnish.params 文件加载。
-a address[:port][,address[:port][...]:设置面向 web 服务的监听网络,默认为6081端口;-T address[:port]:设置面向客户端工具的监听网络,默认为6082端口;-s [name=]type[,options]:定义缓存存储机制;-u:user;-g:group;-f:config(VCL配置文件);-F:运行于前台;
设定运行时参数:通过 Systemd Unit 文件中的 DEAMON_OPTS 变量引用。
示例:
1 | DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300" |
-p param=value:设定运行参数及其值; 可重复使用多次;-r param[,param...]:设定指定的参数为只读状态;
定义后端缓存存储机制¶
[name=]type,[,options]:指定 varnish 的缓存存储机制( Storage Types)。
例如:
malloc<,size>:使用 malloc 类型,内存类存储,<size>用于定义空间大小,重启后所有缓存项会失效。由于 malloc 是串行机制,大并发量时性能差,可以使用 jemalloc,这是一个并行的支持扩展的 malloc 的实现,默认作为依赖已安装,但没有启用。file<,dir_or_file><,size><,granularity>:使用文件或目录类型,默认路径是/tmp下,数据在黑盒中,无法直接查看,重启后所有缓存项失效。
建议:
- 内存容易出现碎片,建议使用高性能硬盘存储,比如 SSD
- 缓存区大小可调整,不建议过大,最好和热区数据接近
四、Varnish 配置文件¶
Varnish 的配置文件有两个:
/etc/varnish/varnish.params:配置 Varnish 服务进程的工作特性,例如监听的地址和端口,缓存机制等;重启服务才能生效;/etc/varnish/default.vcl:配置各 Child/Cache 线程的缓存策略;使用专用工具重载即可生效。
1. varnish.params 的相关参数¶
配置 Varnish 服务进程的工作特性。
1 | // 将此值设置为 1 时,使用 systemd 进行重新加载会尝试在不重启的情况下切换 VCL |
2. default.vcl 的相关参数¶
主要用于配置缓存策略,VCL 配置文件遵循 C 语言语法,必须以 ; 结尾。
Varnish 的所有配置都是通过 VCL(varnish configure language) 来配置的。它是一种基于“域” (domain specific) 的简单编程语言,仅生效在特定的状态引擎(state engine)。它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用 set 自定义变量、支持 if 判断语句,也有内置的函数和变量等。使用 VCL 编写的缓存策略通常保存至
.vcl文件中,其需要编译成二进制的格式后才能由 varnish 调用。VCL 有多个状态引擎,状态之间存在相关性,但状态引擎彼此间互相隔离;每个状态引擎可使用
return(action)指明关联至哪个下一级引擎;每个状态引擎对应于 vcl 文件中的一个配置段,即为 subroutine。
状态引擎列表¶
- 前端状态引擎:
vcl_recv:用于接受和处理请求。当请求到达并成功接收后被调用,通过判断请求的数据来决定如何处理请求。例如如何响应、怎么响应、使用哪个后端服务器等;vcl_hash:将数据来源生成 HASH 值;vcl_hit:命中缓存时要执行的操作;vcl_pass:不是 GET 和 HEAD 的其他正常请求,将请求直接发往后端主机;vcl_miss:没有命中缓存,直接将请求发给后端主机;vcl_purge:执行缓存清理;vcl_synth:用于在 Varnish 中生成内容;vcl_pipe:遇到无法理解的用户请求,直接结束;vcl_deliver:将用户请求的内容响应给客户端时用到的方法;
- 后端状态引擎:
vcl_backend_fetch:后端引擎读取首部报文;vcl_backend_response:收到响应报文并判断是否缓存;vcl_backend_error:接收响应报文错误;
- 两个特殊的引擎:
vcl_init:在接收任何处理请求之前要执行的 vcl 代码,主要用于初始化 VMODs;vcl_fini:所有的请求都已经结束,Varnish 即将关闭,在 vcl 配置被丢弃时调用,主要用于清理 VMODs;
各个引擎的工作流程图¶
可用的变量¶
subroutine 中可用的变量,R/W 代表对数据是否有读/写权限。
| subroutine | req. | bereq. | beresp. | obj. | resp. |
|---|---|---|---|---|---|
| vcl_backend_fetch | R/W | ||||
| vcl_backend_response | R/W | R/W | |||
| vcl_backend_error | R/W | R/W | |||
| vcl_recv | R/W | ||||
| vcl_pipe | R | R/W | |||
| vcl_pass | R/W | ||||
| vcl_hash | R/W | ||||
| vcl_purge | R/W | ||||
| vcl_miss | R/W | ||||
| vcl_hit | R/W | R | |||
| vcl_deliver | R/W | R | R/W | ||
| vcl_synth | R/W | R/W |
常用到的变量¶
请求报文类:bereq.*, req.*
bereq.http.HEADERS:可以使用各种首部信息;bereq.request, req.request:请求方法;bereq.url, req.url:请求的 url;bereq.proto,req.proto:请求的协议版本;bereq.backend:指明要调用的后端主机;req.http.Cookie:客户端的请求报文中 Cookie 首部的值;req.http.User-Agent:客户端浏览器信息;
响应报文类:beresp.*, resp.*
beresp.http.HEADERS:可以使用各种首部信息;beresp.status, resp.status:响应的状态码;reresp.proto, resp.proto:协议版本;beresp.backend.name:后端主机的主机名;beresp.ttl:后端主机响应的内容的余下的可缓存时长;
缓存类:obj.*
obj.hits:此对象从缓存中命中的次数;obj.ttl:对象的 ttl 值
Varnish 自身类:server.*
server.ip:Varnish 主机的 IP;server.hostname:Varnish 主机的 Hostname;
客户端类:client.*
client.ip:发请求至 Varnish 主机的客户端 IP;
用户自定义:
set:定义一个变量unset:引用一个变量
如果想引用 http 报文头部变量就可以写成这样:
req.http:客户端发给代理的请求报文;bereq.http:代理发给后端的请求报文;beresp.http:后端发给代理的响应报文;resp.http:代理发给客户端的响应报文;
3. 缓存策略自定义¶
默认的缓存策略如下所示:
vim /etc/varnish/default.vcl
1 | // 告诉编译器这是 4.0 版本 |
示例 1:显示缓存与否¶
在 vcl_deliver 中定义一个动作,增加一个响应报文首部,显示当前页面是否为缓存。
obj.hits:是内建变量,用于保存某缓存项的从缓存中命中的次数。resp.http:代理发给客户端的响应报文;
1 | if (obj.hits>0) { // 如果缓存命中次数如果大于 0 |
示例 2:不使用缓存¶
强制对某类资源的请求不检查缓存,(?i) 表示忽略大小写:
1 | vcl_recv { |
示例 3:缓存私转公¶
对于特定类型的资源,例如公开的图片等,取消其私有标识,并强行设定其可以由 varnish 缓存的时长;
在 vcl_backend_response 中定义:
"s-maxage":公共缓存最大的可缓存时长
1 | if (beresp.http.cache-control !~ "s-maxage") { |
示例 4:客户端 IP 传递¶
传递客户端的真实 IP。
在 vcl_recv 中定义:
1 | if (req.restarts == 0) { // 如果不是被重写的请求 |
VCL 配置重载¶
配置好后可使用 varnish_reload_vcl 命令重载 VCL 文件,使其生效。
1 | -> # varnish_reload_vcl |
五、管理工具 varnishadm¶
管理工具 varnishadm,是C/S架构。
默认连接地址 127.0.0.1:6082
可使用交互式模式(长连接),也可直接运行命令加参数(短连接)。
语法:varnishadm [-t timeout] [-S secretfile] -T [address]:port command [...]
控制相关命令¶
ping:存活性探测;auth:认证;baaner:显示欢迎页;start:启动;stop:停止;stats:查看状态;
配置文件相关的命令¶
vcl.list:列出 VCL;vcl.load:装载,加载并编译;vcl.use:激活指定的 VCL;vcl.discard:删除指定的 VCL;vcl.show [-v] <configname>:查看指定的规则的详细信息;
运行时参数¶
注意:临时有效,写在配置文件中可永久生效
param.show -l:显示所有可调参数列表;param.show <PARAM>:param.set <PARAM> <VALUE>:
后端管理命令¶
storage.list:列出后端使用的存储;backend.list:列出后端服务器,(no probe)代表未定义健康状态检测方式。ban.list:
六、在 VCL 中定义一些功能的配置示例¶
1. 清理过期缓存¶
缓存对象的修剪可以使用 purge 或 ban:
purge:对指定的 url 清理缓存。ban:支持使用正则表达模式,对 url 匹配,并清理缓存。
使用 purge 清理方式¶
当请求方法是 PURGE 时,就清理缓存。
- (1) 自定义何时执行 purge 操作。
1 | sub vcl_recv { |
- (2) 定义执行 purge 的操作。
1 | sub vcl_purge { |
限制指定的客户端才能执行 PURGE 操作:
- (1) 定义 acl 列表,acl 属于单独的配置段。
1 | acl purgers { |
- (2) 何时执行 purge 操作。
1 | sub vcl_recv { |
- (3) 定义执行 purge 操作
1 | sub vcl_purge { |
生效后,通过 curl -X PRUGE ip:port/path 就可以 清理缓存。
使用 ban 清理方式¶
- (1) 使用客户端工具
varnishadm
语法:ban <field> <operator> <arg>
示例:
1 | ban req.url ~ (?i)^/javascripts |
- (2) 在 vcl 配置文件中定义,使用
ban()函数定义
示例:
如果执行 curl -X BAN http://www.ilinux.io/test1.html,就 ban req.http.host==www.ilinux.io && req.url==/test1.html
1 | sub vcl_recv { |
2. 如何设定多个后端主机,并动静分离¶
- Nginx 中使用:
proxy_pass - Haproxy 中使用:
use_backend
而在 Varnish 中,先定义多个后端:
1 | backend default { |
再定义匹配规则,可以实现 php 分离:
1 | sub vcl_recv { |
3. 设置后端的主机属性¶
1 | backend BE_NAME { |
4. 对后端进行负载均衡+动静分离调度¶
Varnish 中的负载均衡通过 Director 模块实现。
import directors:使用前需要导入。
示例:
1 | import directors; // 加载 director 模块 |
测试查看效果
1 | # while true; do curl 192.168.50.11/index.html; done |
使用 varnishadm 查看定义的后端
1 | # varnishadm backend.list |
5. 基于 Cookie 的 session 绑定¶
在上一个的配置基础上,定义基于 Cookie 的 session 绑定。
示例:
1 | sub vcl_init { // 在初始化配置段中定义组 |
6. 健康状态检测¶
健康状态检测的定义方式,使用 .probe 模块,可用参数:
1 | .url = // 检测时要请求的 URL,默认为 / |
健康状态检测的两种配置方法:
- (1) 单独定义 probe 模块,之后引用即可。
1 | probe web_health { // 先定义 |
- (2) 在每个需要的
backend中都定义一次。
1 | backend static1 { |
手动设定健康状态的命令¶
backend.set_health <ARG>sick:管理强制 down;healthy:管理强制 up;auto:自动探测;
七、设置运行时参数¶
使用 varnishadm 设置运行时参数,立刻生效,但是重启后失效。
param.show -l:显示所有可调参数列表;param.show <PARAM>:显示某个参数当前运行的值;param.set <PARAM> <VALUE>:设定一个参数;
想永久生效的话需要保存在 /etc/varnish/varnish.params 配置文件中;
-p param=value:设定运行参数及其值;每个值前都需要加-p;-r param[,param...]:设定指定的参数为只读状态;
示例:
1 | DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300" |
设置参数在 Varnish 运行时不能修改,只能重启修改,缓存会全部失效。
-r param[,param...]
示例:
1 | DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300 -r thread_pools,thread_pool_max" |
thread_pools 相关参数¶
线程相关的参数,thread_pools 可以设定要运行的线程数,和 Nginx 一样,Varnish 也使用 epoll 模型,即一个线程处理多个请求,所以线程数量设置最好小于或等于 CPU 核心数量。
-
thread_pools Number:设置线程池的个数,最好小于或等于 CPU 核心数量; -
thread_pool_max:每线程池的最大并发连接数,所以最大并发连接数 =thread_pools * thread_pool_max; -
thread_pool_min:额外意义为“最大空闲线程数”; -
thread_pool_timeout:当空闲的线程数大于thread_pool_min设置的值后,超过此值设置的时间后,空闲的线程将被 KILL 掉; -
thread_pool_add_delay:至少要等待这么长时间后,再创建新线程; -
thread_pool_destroy_delay:至少要等待这么长时间后,再销毁线程;
Timer 相关的参数¶
send_timeout:向客户端发送响应报文时的连接超时时长;timeout_idle:客户机连接的空闲超时时长;timeout_req:等待客户端传输完成请求报文的超时时长;cli_timeout:等待客户端接收响应报文的超时时长;
八、日志¶
Varnish 的日志保存在一段内存空间中,是循环覆盖的方式,太老旧的会被覆盖掉。
1. 实时显示统计信息¶
varnishstat:交互式的显示统计信息;
-1 -f FILED_NAME -f FILED_NAME:只显示指定的统计项,-1只显示一批次的信息就退出;-l:查看所有可用的统计项;
主要有:MAIN.cache_hit, MAIN.cache_miss
2. 实时显示速率日志¶
varnishtop:实时显示速率日志;
-1:只显示一批次的信息就退出;-i taglist:白名单,可以同时使用多个-i选项,也可以一个选项跟上多个标签;-I <[taglist:]regex>:对指定的标签的值,基于 regex 进行过滤;-x taglist:黑名单-X <[taglist:]regex>:对指定的标签的值,基于 regex 进行过滤,符合条件的予以排除;
3. 实时抽取出详细独立日志¶
varnishlog:每一个请求独立显示。/usr/lib/systemd/system/varnishlog.service:运行为服务态,将日志持久保存而不被覆盖;
4. 原始格式日志¶
varnishncsa:显示为类似于 httpd 日志样式的原始日志。/usr/lib/systemd/system/varnishncsa.service:运行为服务态,将日志持久保存而不被覆盖;