使用Varnish为网站加速

使用Varnish为网站加速

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

介绍

概述

Varnish 是一款高性能且开源的反向代理服务器和 HTTP 加速器,其采用全新的软件体系机构,和现在的硬件体系紧密配合,与传统的 Squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点。

Varnish 的作者 Poul-Henning Kamp 是 FreeBSD 的内核开发者之一,他认为现在的计算机比起 1975 年已经复杂许多。在 1975 年时,储存媒介只有两种:内存与硬盘。但现在计算机系统的内存除了主存外,还包括了 CPU 内的 L1、L2,甚至有 L3 快取。硬盘上也有自己的快取装置,因此 Squid Cache 自行处理物件替换的架构不可能得知这些情况而做到最佳化,但操作系统可以得知这些情况,所以这部份的工作应该交给操作系统处理,这就是 Varnish cache 设计架构。

很多门户网站已经部署了 Varnish,并且反应都很好,甚至反应比 Squid 还稳定,且效率更高,资源占用更少。相信在反向代理、Web 加速方面,Varnish 已经有足够能力代替 Squid。

挪威的最大的在线报纸 Verdens Gang(vg.no) 使用 3 台 Varnish 代替了原来的 12 台 Squid,性能比以前更好,这是 Varnish 最成功的应用案例。


可参考视频:


Varnish 官方文档:https://varnish-cache.org/docs/
Varnish 在线指南:https://book.varnish-software.com/4.0/index.html

进程结构

Varnish主要有两个进程:manage 进程及 child进程:

  • manage 进程:用于更新配置,vcl 文件的编译,varnish 的监控,初始化 varnish 及提供 varnish 管理接口;
  • child 进程:主要用于进行请求任务的处理,接受请示等;

child 进程中各线程又可分为如下几种:

  • accept 线程:监听端口,接受连接,接受连接后会生成一个 session,查看是否有空闲线程,若有则分配给其处理,若无,则检查等待队列 overflow 的大小,若过大则抛弃请求,否则加入 overflow 队列;
  • worker 线程:从 overflow 队列中获取任务,由 varnish 进行任务处理,处理完成后由 pipe 线程实现通信,传递给 epoll 线程,等待下一个事件的发送。child 子进程会给每一个用户的请求进行处理,处理一个则开启一个 worker 进程,所以它是单线程,单响应的,即每一个用户的请求是由一个独立的线程响应的,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;
  • epoll 线程:当事件发送时将对应的 session 放入 overflow 队列,以供 work 线程从中取出继续处理,在等待事件发送时同时会去检查 session 是否过期;
  • expire 线程:对以二叉树形式组织的缓存对象进行过期检查,对过期的对象进行处理会更新或者弃用;
  • storage/hashing 线程:完成 hash 并进行缓存存储;
  • log/stats 线程:查看记录日志并统计各种状态;
  • backend communication 线程:管理后端主机线程,当缓存中没有缓存需要由此进程交由后端服务器处理;

缓存流程

程序能够缓存是因为程序具有局部性,时间局部性和空间局部性。缓存一般存储为键值,key 为访问路径的 url 经过 hash 计算后的结果,value 为服务器内容,一般缓存存储的是热点数据,切缓存中存储的数据不会大于后端 Web 服务器的程序数据大小。缓存的对象是有生命周期的,当缓存空间耗尽的时候会根据 LRU 算法(最近最少使用)或其他算法对缓存空间进行清理。缓存中一般只能缓存非客户私有数据,客户私有数据和带 cookie 的数据可以进行缓存,但是存在风险,所以一般不对客户私有数据进行缓存。

Varnish 的工作流程大致如下:

  1. 初始化过程
    Varnish 的 master 进程负责启动工作,master 进程读取配置文件,根据指定的空间大小来创建存储空间,创建并管理 child 进程。child 进程来处理后续任务,它会分配一些线程来执行不同的工作。例如:接受 http 的请求、为缓存对象分配存储空间、清除过期缓存对象、释放空间、碎片整理等;
  2. http 请求处理过程
    由专门负责接收 http 请求的线程一直监控 http 请求端口,当有请求时会唤起一个工作线程。这个工作线程会分析 http 的请求,并清楚了解需求的内容开始在缓存中查找是否有这个请求对象内容。如果请求内容存在,会把缓存对象直接返回给用户。如果请求不存在,他会把请求转给后端服务器处理,并等待结果,工作线程从后端得到请求的答案后会先把请求答案缓存进一个内存空间用以备份,下次访问时时会快速响应从而节约时间,然后把请求答案返回给用户;
  3. 分配缓存过程
    有一个对象需要缓存时,根据这个对象的大小,到空闲缓存区中查找大小最适合的空闲块,找到后就把这个对象放进去。如果这个对象没有填满这个空闲块,就把剩余的空间做为一个新的空闲块,如果空闲缓存区中没地方了,就要先删除一部分缓存来腾出地方,删除是根据最近最少使用原则;
  4. 释放缓存过程
    有一个线程来负责缓存的释放工作,他定期检查缓存中所有对象的生存周期,如果某个对象在指定的时间段内没有被访问,就把这个对象删除,释放其占用的缓存空间。释放空间后,检查一下临近的内存空间是否是空闲的,如果是,就整合为一个更大的空闲块,实现空间碎片的整理;

日志

为了与系统的其它部分进行交互,child 进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个 worker 线程都使用了日志数据缓存。共享内存日志大小一般为 90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。Varnish 提供了多个不同的工具如 varnishlog、varnishncsa 或 varnishstat 等来分析共享内存日志中的信息并能够以指定的方式进行显示。

VCL 简介

Varnish Configuration Language (VCL)是 Varnish 配置缓存策略的工具,它是一种基于“域”(domain specific)的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用 set 自定义变量、支持 if 判断语句,也有内置的函数和变量等。使用 VCL 编写的缓存策略通常保存至 .vcl 文件中,其需要编译成二进制的格式后才能由 Varnish 调用。事实上,整个缓存策略就是由几个特定的子例程如 vcl_recv、vcl_fetch 等组成,它们分别在不同的位置(或时间)执行,如果没有事先为某个位置自定义子例程,Varnish 将会执行默认的定义。

VCL 策略在启用前,会由 management 进程将其转换为 C 代码,而后再由 gcc 编译器将 C 代码编译成二进制程序。编译完成后,management 负责将其连接至 varnish 实例,即 child 进程。正是由于编译工作在 child 进程之外完成,它避免了装载错误格式 VCL 的风险。因此,varnish 修改配置的开销非常小,其可以同时保有几份尚在引用的旧版本配置,也能够让新的配置即刻生效。编译后的旧版本配置通常在varnish重启时才会被丢弃,如果需要手动清理,则可以使用 varnishadm 的 vcl.discard 命令完成。

后端存储

Varnish 支持多种不同类型的后端存储,这可以在 varnishd 启动时使用 -s 选项指定。后端存储的类型包括:

  • file:使用特定的文件存储全部的缓存数据,并通过操作系统的 mmap() 系统调用将整个缓存文件映射至内存区域(如果条件允许);
  • malloc:使用 malloc()库调用在 varnish 启动时向操作系统申请指定大小的内存空间以存储缓存对象;
  • persistent(experimental):与 file 的功能相同,但可以持久存储数据(即重启 varnish 数据时不会被清除);

使用

准备如下主机:

主机名IP描述
A10.0.1.200Varnish 主机
B10.0.1.201Web 主机
C10.0.1.202Web 主机

通过 A 主机访问 B、C 主机的 Web 服务效果如下:

$ curl 10.0.1.201
from B-201
$ curl 10.0.1.202
from C-202

安装配置

Varnish 已被 epel 源所收录,添加 epel 源后可直接通过 yum 安装,我这里直接在 A 主机上执行如下命令:

$ yum install varnish -y

在开始配置前我们先看一下 Varnish 的配置参数,Varnish 的配置参数主要由如下几种类型:

  • varnishd 应用程序的命令行参数:在 Varnish 服务的 unit 文件中引用了 /etc/varnish/varnish.params 文件,当 varnishd 二进制程序启动时会使用该文件中定义的参数;
  • -p 选项指明的参数:也是运行时参数,可在程序运行中,通过其 CLI 进行配置;
  • vcl:配置缓存系统的缓存机制,通过 vcl 配置文件进行配置,先编译,后应用,依赖于 C 编译器;

varnishd 的常用选项如下:

-p param=value              # 配置参数(child 的进程数,worker 的线程数)
-r param[,param...]         # 设定只读参数列表
-f file                     # 读取 VCL 配置文件
-a address:port             # 指定地址端口
-d                          # debug,运行于调试模式    
-s [name=]kind[,options]    # 指定存储类型
    -s malloc[,<size>] 
    -s file,<dir_or_file>,<size>,<granularity>
    -s persist{experimental}
-T address:port             # 指定管理接口地址

程序组成

安装完 Varnish 程序后可以通过 rpm -ql varnish 查看生成的文件,其中常用的文件如下:

  • /etc/varnish/varnish.params:配置 varnish 服务进程的工作特性,例如监听的地址和端口,缓存机制;
  • /etc/varnish/default.vcl:配置各 child/cache 线程的缓存策略;
  • /usr/sbin/varnishd:主程序;
  • /usr/bin/varnishadm:CLI interface,命令行管理程序(默认 6082 为 varnish 管理端口);
  • /usr/bin/varnishtest:测试工具程序:
  • /usr/sbin/varnish_reload_vcl:VCL 配置文件重载程序;;
  • /usr/lib/systemd/system/varnishlog.service:日志服务 unit 文件;
  • /usr/lib/systemd/system/varnishncsa.service:日志持久服务的 unit 文件;
  • /usr/bin/varnishlog:查看 varnish 共享内存访问日志;
  • /usr/bin/varnishhist:同样是查看访问日志,只不过它的显示格式类似于 Nginx 的访问日志;
  • /usr/bin/varnishhist:varnishhist 工具读取 varnishd 的共享内存段日志,生成一个连续更新的柱状图,显示最后 N 个请求的处理情况。这个 N 的值是终端的纵坐标的高度,横坐标代表的是对数,如果缓存命中就标记“|”,如果缓存没有命中就标记上“#”符号;
  • /usr/bin/varnishtop:varnishtop 工具读取共享内存的日志,然后连续不断的显示和更新大部分普通日志。适当的过滤使用 –I-i-X-x 选项,它可以按照您的要求显示请求的内容,客户端,浏览器等其他日志里的信息;
  • /usr/bin/varnishstat:varnish 有很多计数器,可以为我们计数丢失率,命中率,存储信息,创建线程,删除对象等,几乎所有的操作。varnishstat 将存储这些数值,在优化 varnish 的时候可以使用这个命令;

有一个程序可以定期轮询 varnishstat 的数据并生成好看的图表。这个项目叫做 Munin。 Munin 可以在 http://munin-monitoring.org/ 找到。在 varnish 的源码中有 munin 插件。

简单缓存示例

下面以代理缓存 B 主机 Web 服务为例,修改 /etc/varnish/default.vcl 中的如下内容:

backend default {
    .host = "172.16.1.201";
    .port = "80";
}

启动 A 主机的 Varnish 服务,检查端口:

$ ss -tanl | egrep '608[12]'
LISTEN     0      128          *:6081                     *:*                  
LISTEN     0      10     127.0.0.1:6082                     *:*                  
LISTEN     0      128         :::6081                    :::*  

其中 6081 是 Varnish 真正的代理端口,6082 则是管理服务监听的端口。

测试访问 A 主机的 Varnish 服务:

$ curl 10.0.1.200:6081
from B-201

可以看到此时就通过 Varnish 访问到了 B 主机,并且 Varnish 会将此次响应内容缓存,后续相同的访问则会直接从 Varnish 主机响应,你可以试着监听一下 Web 服务的访问日志看看~~

管理程序的使用

由于现在管理程序仅监听了 127.0.0.1,所以我们目前仅能在 Varnish 主机上通过 varnishadm 程序来管理 Varnish 服务,在 A 主机执行下面命令将会进入交互式命令行下:

# -S /etc/varnish/secret 用来指定秘钥文件。
$ varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
200        
-----------------------------
Varnish Cache CLI 1.0
-----------------------------
Linux,3.10.0-862.el7.x86_64,x86_64,-smalloc,-smalloc,-hcritbit
varnish-4.0.5 revision 07eff4c29

Type 'help' for command list.
Type 'quit' to close CLI session.

在该命令行下可以通过 help 获取可使用的子命令,各子命令的功用如下:

help [<command>]                                	    # 帮助
ping [<timestamp>]                              	    # 测试主机是否能通
auth <response>             
quit                                            	    # 退出
banner                      
status                                                  # varnish 服务状态
start                                                   # 启动工作线程
stop                                                    # 关闭线程 
vcl.load <configname> <filename>                        # 编译、装载 vcl 脚本
vcl.inline <configname> <quoted_VCLstring>  
vcl.use <configname>                                    # 使用 vcl 脚本
vcl.discard <configname>                                # 删除 vcl 脚本
vcl.list                                                # 列出所有的 vcl 脚本
vcl.show [-v] <configname>				                # 查看 vcl 脚本的详细信息
param.show [-l] [<param>]                               # 查看运行时参数
param.set <param> <value>                               # 设置运行时参数
panic.show                                              # 显示 varnish 崩溃时的信息 
panic.clear             				                # 清理 varnish 崩溃信息
storage.list                                            # 列出 storage 列表
backend.list [<backend_expression>]                     # 列出后端服务器列表
backend.set_health <backend_expression> <state>         # 对后端服务器设置
ban <field> <operator> <arg> [&& <field> <oper> <arg>]  # 设置要清理的缓存项
ban.list                                                # 列出后端要清理的缓存项

VCL 的状态引擎

先看下图,其中列出了最核心的几个状态引擎:

image.png

下面对上图流程做下描述:

  • 首先 vcl_recv 接受请求;
  • 如果直接能确定请求是错误的,那么将请求发往 vcl_error,由 vcl_error 生成错误页响应发往 vcl_deliver
  • 如果请求是不能缓存的请求(比如请求方法为 POST 或 PUT、请求中包含了敏感信息),则发往 vcl_pass,继而发往后端服务器;
  • 如果请求是无法理解的请求,则发往 vcl_pipe,继而发往后端服务器;
  • 如果上述条件都不满足,则检查请求是否可以缓存;
  • 如果不可缓存则将该请求直接发往给 vcl_fetch 去后端取数据;
  • 如果可以缓存则将请求发往给 vcl_hash
  • vcl_hash 对请求内容进行 hash;
  • 对请求的 hash 码进行比较,命中则发往 vcl_hit,未命中发往 vcl_miss
  • vcl_hit 调用缓存资源发往下一个引擎 vcl_deliver
  • vcl_miss 把未命中的请求发往 vcl_fetch
  • vcl_fetch 向后端服务器请求资源,把请求到的资源发往 vcl_deliver
  • vcl_deliver 生成响应报文,发往客户端;

综上所述,状态引擎的工作流程根据流向路线划分主要有如下几类:

  • vcl_recv --> vcl_hash --> vcl_hit --> vcl_deliver
  • vcl_recv --> vcl_hash --> vcl_miss --> vcl_fetch --> vcl_deliver
  • vcl_recv --> vcl_pass --> vcl_fetch --> vcl_deliver
  • vcl_recv --> vcl_pipe

各引擎之间存在一定程度上的相关性,前一个 engine 如果可以有多种下游 engine,则上游 engine 需要用 return 指明要转移到的下游 engine。

注意,上述列出的仅是 v3 版本 Varnish 中常用的状态引擎,目的是更易于理解 Varnish 的工作机制,在 v4 版本以后 Varnish 的状态引擎还发生了很多改变,这里我就仅列出官网给出的工作流程图,如下:

image.png

由于流程分支太多,语言实在描述,具体可参考「官方文档」。

VCL 编程

Varnish 是通过 VCL 编程语言结合状态引擎来进行访问控制的,其编程语法如下:

(1) //, #, /* */ 用于注释;会被编译器忽略;
(2) sub $name: 用于定义子例程或函数;
	sub vcl_recv {

	}
(3) 不支持循环;
(4) 有众多内置的变量,变量的可调用位置与state engine有密切相关性;
(5) 支持终止语句,return (action);没有返回值;
(6) "域"专用;
(7) 操作符:=, ==, ~, !, &&, ||
(8) 条件判断语句:
	if (CONDTION) {

	} else {

	}
(9) 变量赋值:set name=value
	unset name

不仅如此,Varnish 还有很多内置的函数,不过我们也很少用到,想了解的话可参考 https://docs.fastly.com/vcl/functions/

下面就基于上面的「简单缓存示例」做一下修改,通过 VCL 编程配置给响应报文添加一个响应头,这个响应头标识着此次请求是否命中了缓存。

通过上面的 VCL 状态引擎的执行流程可知,这个响应头我们可以定义在 vcl_deliver 中,这里就向响应给客户端的报文添加一个自定义首部 X-Cache,需改 A 主机的 Varnish 配置文件的如下部分:

$ vim /etc/varnish/default.vcl
...
sub vcl_deliver {
        if (obj.hits>0) {
                set resp.http.X-Cache = "HIT";
        } else {
                set resp.http.X-Cahce = "MISS";
        }
}

要让修改后的配置文件生效,需要使用 varnishadm 工具编译配置文件并使其生效,如下:

$ varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
...
vcl.load test1 default.vcl
200        
VCL compiled.
vcl.use test1
200        
VCL 'test1' now active

访问 A 主机,查看响应头:

$ curl -I 10.0.1.200:6081
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Thu, 16 Apr 2020 12:00:13 GMT
Content-Type: text/html
Content-Length: 10
X-Varnish: 7
Age: 0
Via: 1.1 varnish-v4
X-Cahce: MISS
Connection: keep-alive

$ curl -I 10.0.1.200:6081
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Thu, 16 Apr 2020 12:00:13 GMT
Content-Type: text/html
Content-Length: 10
X-Varnish: 10 8
Age: 3
Via: 1.1 varnish-v4
X-Cache: HIT
Connection: keep-alive

可以看到,第一次访问是没有命中缓存的,第二次访问则是命中缓存的。

VCL 内置变量

在上面「VCL 编程」示例中使用了一个内置变量 obj.hits,该变量记录了用户请求资源的命中缓存的次数,除此之外,还有很多其它内置变量,下面仅列出常用的内置变量:

变量名功用
now当前时间
client.ip客户端 IP 地址
server.hostname当前 Varnish 主机的主机名
server.ip当前 Varnish 主机的主机名 IP
server.port当前 Varnish 主机与客户端建立连接使用的端口
req.request客户端请求类型
req.url客户端请求 URL
req.proto客户端请求使用的协议
req.backend处理此次请求的后端主机
req.http.<header>客户端相应的请求头
req.hash_always_miss对此请求强制不使用缓存,如果设置为 true,Varnish 将忽略任何现有缓存对象,并始终从后端(重新)请求数据
bereq.requestVarnish 主机向后端主机发出的请求的类型
bereq.urlVarnish 主机向后端主机发出的请求的 URL
bereq.protoVarnish 主机与后端主机通信使用的协议
bereq.http.<header>Varnish 主机向后端主机发出的请求中相应的请求头
bereq.connect_timeoutVarnish 主机请求后端主机的连接超时时间
beresp.proto后端主机响应 Varnish 主机使用的 HTTP 协议版本
beresp.status后端主机响应 Varnish 主机的 HTTP 响应状态码
beresp.response后端主机响应 Varnish 主机的响应状态信息
beresp.cacheable 如果后端主机响应 Varnish 主机的内容是可缓存的,此值则为 true。如果 HTTP 状态码为 200、203、300、301、302、404 或 410,并且在 vcl_recv 中未调用 pass,则认为响应可以缓存。但是,如果 TTL 和响应的宽限时间均为 0,则 beresp.cacheable 将为 0。
bereq.backend指明要调用的后端主机
beresp.backend.ip后端主机的 IP
beresp.backend.name后端主机的主机名
beresp.ttl后端主机响应 Varnish 主机的响应对象的剩余生存时间(以秒为单位),beresp.ttl 是可写的
obj.proto缓存对象使用的 HTTP 协议版本
obj.status缓存对象的 HTTP 状态码
obj.response缓存对象的响应状态信息
obj.cacheable缓存对象是否可以缓存,如果后端主机响应 Varnish 主机时 beresp.cacheable 值为 true,那么此值就为 true
obj.ttl缓存对象的剩余生存时间(以秒为单位),obj.ttl 是可写的
obj.lastuse从上次请求命中该对象以来经过的时间,以秒为单位
obj.hits该缓存对象被命中的次数
req.hash引用缓存对象的哈希键
resp.protoVarnish 主机响应客户端时使用的 HTTP 协议版本
resp.statusVarnish 主机响应客户端时的 HTTP 响应状态码
resp.responseVarnish 主机响应客户端时的 HTTP 响应状态信息
resp.http.<header>Varnish 主机响应客户端时相应的响应头

更多内置变量可参考:https://docs.fastly.com/vcl/variables/

要注意的是,这些内置变量可使用的位置与状态引擎密切相关,即有些内置变量仅能在特定的某些状态引擎中才能使用,也很容易理解,比如在 vcl_recv 中是不可能使用 beresp.status 变量的,因为此时请求还没到达后端主机哪来的后端主机的响应状态码~~~这些变量具体能在哪些状态引擎下使用还请参考上面的状态引擎执行流程图和官方文档。

对指定类型资源不缓存

下例配置的是对请求 URI 以 /login/admin 的资源不缓存。

修改 Varnish 配置文件中的 vcl_recv 函数如下:

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

编译并加载此配置文件,测试请求 URI 以 /login/admin 开头的资源,会发现 Varnish 不会缓存它们了~~

强制缓存指定类型资源

下例配置是对特定类型的资源取消其私有的 cookie 标识,并强行设定其可以 Varnish 缓存的时长。

之所以取消 cookie 标识,是因为 Varnish 默认不会缓存带有 cookie 的请求。

在 Varnish 配置文件中添加如下 vcl_backend_response 函数:

sub vcl_backend_response {
	# 如果后端主机响应给 Varnish 主机的响应头中没有设置 s-maxage,即没有设置此资源的缓存时长 
	if (beresp.http.cache-control !~ "s-maxage") {
		# 判断是否是图片资源
		if (bereq.url ~ "(?i)\.jpg$") {
			# 设定其缓存时长为 1 小时
			set beresp.ttl = 3600s;
			# 取消其 Set-Cookie 响应首部
			unset beresp.http.Set-Cookie;
		}
		# 判断是否是 css 资源
		if (bereq.url ~ "(?i)\.css$") {
			# 设定其缓存时长为 10 分钟
			set beresp.ttl = 600s;
			# 取消其 Set-Cookie 响应首部
			unset beresp.http.Set-Cookie;
		} 
	}
}

编译并加载此配置文件,测试请求 URI 以 .jpg.css 结尾的资源,会发现它们一定会被 Varnish 缓存~~

允许指定主机清理缓存

修改 Varnish 配置文件如下:

acl local {
    "localhost";    /* 本机 */  
    "10.0.1.0"/24; /* 所有此网络中的主机 */  
    "172.16.1.200"; /* 除了这个主机 */  
}

sub vcl_recv {
  if (req.method == "PURGE") {
    if (client.ip ~ local) {
        ban("obj.http.url ~ " + req.url);
    } else {
        return(synth(403, "Access denied."));
    }   
  }
}

该示例的效果是仅允许 local ACL 中定义的主机发送 PURGE 请求清理对应缓存。

负载均衡与健康检测

要负载均衡后端主机需要创建调度器对象,要检测一个后端主机是否是健康状态需要通过 probe 关键字来定义对应的检查规则,看如下示例:

vcl 4.0;

import directors; 

# 定义健康监测规则 - 方式一
probe proweb {
    # 健康监测请求的 URL
    .url = "/health";
    # 期待的响应状态码
    .expected_response = 200;
}

backend server1 {
    .host = "10.0.1.201";
    .port = "80";
    # 定义健康监测规则 - 方式二
    .probe = { 
        # 健康监测请求的 URL
        .url = "/healthtest";
        # 超时时间
        .timeout = 1s; 
        # 每 4 秒发起一次健康监测请求
        .interval = 4s; 
        # 下面两项的含义是,以最近 5 次的检测结果为标准,如果有 3 次是检测成功的,那么就认为对应后端主机是健康的
        .window = 5;
        .threshold = 3;
    }
}

backend server2 {
    .host = "10.0.1.202";
    .port = "80";
    .probe = proweb;
}

sub vcl_init {
    # 创建临时调度器对象
    new round_robin_director = directors.round_robin();
    round_robin_director.add_backend(server1);
    round_robin_director.add_backend(server2);
    # 创建加权随机调度器对象
    new random_director = directors.random();
    random_director.add_backend(server1, 10);  # 2/3 的请求调度给 server1
    random_director.add_backend(server2, 5);   # 1/3 的请求调度给 server2
}

sub vcl_recv {
    # 使用轮询调度器对象
    set req.backend_hint = round_robin_director.backend();
}

加载配置文件后,此时可以通过 varnishadm 工具查看到后端主机的健康状态:

$ varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
...
backend.list 
200        
Backend name                   Refs   Admin      Probe
default(172.16.1.201,,80)      2      probe      Healthy (no probe)
server1(10.0.1.201,,80)        1      probe      Healthy 5/5
server2(10.0.1.202,,80)        1      probe      Healthy 8/8

测试负载均衡效果时可以以 PURGE 方法请求 Vanish 主机,此时会发现 Vanish 主机是轮询请求两个后端主机返回响应内容。

此部分内容可参考 http://book.varnish-software.com/4.0/chapters/Saving_a_Request.html#health-checks

在上面负载均衡示例中其实已经介绍了两种调度算法,分别是 round_robinrandom,即轮询与随机。除此之外它还有 fallbackhash 算法:

  • fallback 算法可以看做是在多个后端主机中挑选一个最“健康”的主机为当次请求提供服务,这个“健康”的判断条件可能是响应速度最快、经过的路由最短等;
  • hash 算法其实与 Nginx 中的 hash 算法类似,我们可以为 hash 方法提供一个哈希种子,当这个种子是客户端源地址,则实现的就是源地址哈希,当这个种子是请求的 URL,那么实现的就是 URL 哈希的效果;

有了上述概念,基于 Cookie 做会话绑定就很简单啦~~只需要将哈希种子设为客户端请求的 cookie 即可,看如下示例:

vcl 4.0;

import directors;

backend server1 {
    .host = "10.0.1.201";
    .port = "80";
}

backend server2 {
    .host = "10.0.1.202";
    .port = "80";
}

sub vcl_init {
    new h = directors.hash();
    h.add_backend(server1, 1); 
    h.add_backend(server2, 1); 
}

sub vcl_recv {
    set req.backend_hint = h.backend(req.http.cookie);
}

到这里我应该不用解释你也能看懂了~~~

动静分离

直接看下面示例,灰常清晰:

vcl 4.0;

backend server1 {
    .host = "10.0.1.201";
    .port = "80";
}

backend server2 {
    .host = "10.0.1.202";
    .port = "80";
}

sub vcl_recv {
    # 如果请求的是图片资源,则将其调度至 server1
    # 否则将其调度至 server2
    if (req.url ~ "(?i)\.(jpg|png|gif)$") {
        # 通过 req.backend_hint 能指定当次请求调度的后端主机。
        set req.backend_hint = server1;
    } else {
        set req.backend_hint = server2;
    }
}

此时如果访问图片资源,请求将会被调度到 10.0.1.201 主机,如果访问其它类型资源,请求将会被调度到 10.0.1.202 主机。

VCL 大量示例可参考 http://varnish-cache.org/trac/wiki/VCLExamples

参考:

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/varnish.html

Buy me a cup of coffee ☕.