varnish 缓存是 web 应用加速器,同时也作为 http 反向缓存代理。你可以安装 varnish 在任何http 的前端,同时配置它缓存内容。与传统的 squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点。有一部分企业已经在生产环境中使用其作为旧版本的 squid的替代方案,以在相同的服务器成本下提供更好的缓存效果,Varnish 更是作为 CDN 缓存服务器的可选服务之一
根据官网的介绍,Varnish 的主要特性如下:https://www.varnish-cache.org/
缓存位置:可以使用内存也可以使用磁盘。如果要使用磁盘的话推荐 SSD 做 RAID1
日志存储:日志也存储在内存中。存储策略:固定大小,循环使用支持虚拟内存的使用
有精确的时间管理机制,即缓存的时间属性控制
状态引擎架构:在不同的引擎上完成对不同的缓存和代理数据进行处理。可以通过特定的配置语言设计不同的控制语句,以决定数据在不同位置以不同方式缓存,在特定的地方对经过的报文进行特定规则的处理
缓存管理:以二叉堆格式管理缓存数据,做到数据的及时清理
相同点:
都是一个反向代理服务器
都是开源软件
针对劣势一:
在访问量很大的情况下推荐使用 varnish 的内存缓存方式启动,而且后面需要跟多台 squid/nginx 服务器。主要为了防止前面的 varnish 服 务、服务器被重启的情况下,大量请求穿透 varnish,这样 squid/nginx 可以就担当第二层 CACHE,而且也弥补了 varnish 缓存在内存中重启都会释放的问题
针对劣势二:
可以在负载均衡上做 url 哈希,让单个 url 请求固定请求到一台 varnish 服务器上
varnish 是一个 http 反向代理的缓存。它从客户端接收请求然后尝试从缓存中获取数据来响应客户端的请求,如果 varnish 不能从缓存中获得数据来响应客户端,它将转发请求到后端(backend servers),获取响应同时存储,最后交付给客户端
如果 varnish 已经缓存了某个响应,它比你传统的后端服务器的响应要快很多,所以你需要尽可能是更多的请求直接从 varnish 的缓存中获取响应
varnish 决定是缓存内容或者是从后端服务器获取响应。后端服务器能通过 http 响应头中的Cache-Control 来同步 varnish 缓存内容。在某些条件下 varnish 将不缓存内容,最常见的是使用 cookie。当一个被标记有 cookie 的客户端 web 请求,varnish 默认是不缓存。这些众多的varnish 功能特点都是可以通过写 vcl 来改变的
Varnish 分为 management 进程和 child 进程
对子进程进行管理,同时对 VCL 配置进行编译,并应用到不同的状态引擎
生成线程池,负责对用户请求进行处理,并通过 hash 查找返回用户结果
varnish 配置主要分为:
后端配置ACL 配置probes 配置directors 配置核心子程序配置其中后端配置是必要的,在多台服务器中还会用到 directors 配置,核心子程序配置
即给 varnish 添加反代服务器节点,最少配置一个
即给 varnish 添加访问控制列表,可以指定这些列表访问或禁止访问
即给 varnish 添加探测后端服务器是否正常的规则,方便切换或禁止对应后端服务器
即给 varnish 添加负载均衡模式管理多个后端服务器
即给 varnish 添加后端服务器切换,请求缓存,访问控制,错误处理等规则
req:
The request object,请求到达时可用的变量(客户端发送的请求对象)
bereq:
The backend request object,向后端主机请求时可用的变量
beresp:
The backend response object,从后端主机获取内容时可用的变量(后端响应请求对象)
resp:
The HTTP response object,对客户端响应时可用的变量(返回给客户端的响应对象)
obj:
存储在内存中时对象属性相关的可用的变量(高速缓存对象,缓存后端响应请求内容)
预设变量是系统固定的,请求进入对应的 vcl 子程序后便生成,这些变量可以方便子程序提取,当然也可以自定义一些全局变量
当前时间:
注:原 client.port 已经弃用,如果要取客户端请求端口号使用std.port(client.ip), importstd;才可以使用 std
client.ip:返回客户端 IP 地址
client.identity:用于装载客户端标识码
注:原 server.port 已经弃用,如果要取服务器端口号使用std.port(server.ip),需要 import std;才可以使用 std
server.hostname:服务器主机名
server.identity:服务器身份标识
server.ip:返回服务器端 IP 地址
return 语句是终止子程序并返回动作,所有动作都根据不同的 vcl 子程序限定来选用
https://www.varnish-cache.org/docs/4.0/users-guide/vcl-built-in-subs.html 语法:return(action);
常用的动作:
abandon 放弃处理,并生成一个错误 deliver 交付处理 fetch 从后端取出响应对象 hash 哈希缓存处理 lookup 查找缓存对象 ok 继续执行 pass 进入 pass 非缓存模式 pipe 进入 pipe 非缓存模式 purge 清除缓存对象,构建响应 restart 重新开始 retry 重试后端处理 synth(status code,reason) 合成返回客户端状态信息注:varnish 内置子程序均有自己限定的返回动作 return (动作); 不同的动作将调用对应下一个子程序
可操作对象:(部分或全部值)
读:client,server,req,storage
写:client,req
返回值:
synth(status code,reason); 定义响应内容
pass 进入 pass 模式,并进入 vcl_pass 子程序
pipe 进入 pipe 模式,并进入 vcl_pipe 子程序
hash 进入 hash 缓存模式,并进入 vcl_hash 子程序,默认返回值
purge 清除缓存等数据,子程序先从 vcl_hash 再到 vcl_purge
可操作对象:(部分或全部值)
读:client,server,bereq,req,storage
写:client,bereq,req
返回值:
synth(status code,reason); 定义响应内容
pipe 继续 pipe 模式,进入后端 vcl_backend_fetch 子程序,默认返回值
可操作对象:(部分或全部值) 读:client,server,req,storage
写:client,req
返回值:
synth(status code,reason); 定义响应内容
fetch 继续 pass 模式,进入后端 vcl_backend_fetch 子程序,默认返回值
可操作对象:(部分或全部值)
读:client,server,obj,req,storage
写:client,req
返回值:
restart 重启请求
deliver 交付缓存内容,进入 vcl_deliver 子程序处理,默认返回值
synth(status code,reason); 定义响应内容
可操作对象:(部分或全部值)
读:client,server,req,storage
写:client,req
返回值:
restart 重启请求
synth(status code,reason); 定义响应内容
pass 切换到 pass 模式,进入 vcl_pass 子程序
fetch 正常取后端内容再缓存,进入 vcl_backend_fetch 子程序,默认返回值
可操作对象:(部分或全部值)
读:client,server,req,storage
写:client,req
返回值:
lookup 查找缓存对象,存在缓存进入 vcl_hit 子程序,不存在缓存进入 vcl_miss 子程序,当使用了 purge 清理模式时会进入 vcl_purge 子程序,默认返回值
可操作对象:(部分或全部值)
读:client,server,req,storage
写:client,req
返回值:
synth(status code,reason); 定义响应内容
restart 重启请求
可操作对象:(部分或全部值)
读:client,server,req,resp,obj,storage
写:client,req,resp
返回值:
deliver 正常交付后端或缓存响应内容,默认返回值
restart 重启请求
可操作对象:(部分或全部值)
读:server,bereq,storage
写:bereq
返回值:
fetch 正常发送请求到到后端取出响应内容,进入 vcl_backend_response 子程序,默认返回值
abandon 放弃后端请求,并生成一个错误,进入 vcl_backend_error 子程序
可操作对象:(部分或全部值)
读:server,bereq,beresp,storage
写:bereq,beresp
返回值:
deliver 正常交付后端响应内容,进入 vcl_deliver 子程序,默认返回值
abandon 放弃后端请求,并生成一个错误,进入 vcl_backend_error 子程序
retry 重试后端请求,重试计数器加 1,当超过配置中 max_retries 值时会报错并进入vcl_backend_error 子程序
可操作对象:(部分或全部值)
读:server,bereq,beresp,storage
写:bereq,beresp
返回值:
deliver 只交付 sysnthetic(string) 自定义内容,默认返回后端异常标准错误内容
retry 重试后端请求,重试计数器加 1,当超过配置中 max_retries 值时会报错并进入vcl_backend_error 子程序
可操作对象:(部分或全部值)
读:client,server,req,resp,storage
写:req,resp
返回值:
deliver 只交付 sysnthetic(string) 自定义内容,默认返回 sysnth 异常指定状态码与错误内容
restart 重启请求
可操作对象:(部分或全部值)
读:server
写:无
返回值:
ok 正常返回,进入 vcl_recv 子程序,默认返回值
可操作对象:(部分或全部值)
读:server
写:无
返回值:
ok 正常返回,本次 vcl 将释放,默认返回值
varnish 子程序调用流程图,通过大部分子程序的 return 返回值进入下一步行动:
Varnish 中的请求合并
当几个客户端请求同一个页面的时候,varnish 只发送一个请求到后端服务器,然后让其他几个请求挂起并等待返回结果;获得结果后,其它请求再复制后端的结果发送给客户端;但如果同时有数以千计的请求,那么这个等待队列将变得庞大,这将导致 2 类潜在问题:
惊群问题(thundering herd problem),即突然释放大量的线程去复制后端返回的结果,将导致负载急速上升;没有用户喜欢等待;
故为了解决这类问题,可以配置 varnish 在缓存对象因超时失效后再保留一段时间,以给那些等待的请求返回过去的文件内容(stale content),配置案例如下:
sub vcl_recv { if (! req.backend.healthy) { set req.grace = 5m; } else { set req.grace = 15s; } } sub vcl_fetch { set beresp.grace = 30m; }以上配置表示 varnish 将会将失效的缓存对象再多保留 30 分钟,此值等于最大的 req.grace值即可
而根据后端主机的健康状况,varnish 可向前端请求分别提供 5 分钟内或 15 秒内的过期内容
安装包
提取码:h71d
varnish 的官方网址为 http://varnish-cache.org,可以在这里下载最新版本的软件
注意:Varnish 网站有时会被墙
解压,进入解压目录编译安装:
[root@varinsh ~]# tar zxf varnish-4.0.3.tar.gz [root@varinsh ~]# cd varnish-4.0.3/ [root@varinsh varnish-4.0.3]# export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig [root@varinsh varnish-4.0.3]# ./configure && make && make install # 不指定安装路径,默认是安装在/usr/local 目录下注:
./autogen.sh 如果从 Git 库下载的安装包时才需要运行,用于生成 configure 编译文件
复制 vcl 文件(在编译安装目录下),如果安装目录里没有 default.vcl 文件
复制到安装目录的/usr/local/var/varnish/目录下(当然并无必需要求在哪个目录,因为正式启动时还得指定这个文件的目录)
[root@varinsh varnish-4.0.3]# cp etc/example.vcl /usr/local/var/varnish/default.vclvarnish 有"后端"或者"源"服务器的概念。backend server 提供给 varnish 加速的内容。实际上就是给 varnish 添加可供访问的 web 服务器,如果有多台 web 服务器时,可添加多个 backend块
命令:backend。这个定义为最基本的反向入口定义,用于 varnish 连接对应的服务器,如果没有定义或定义错误则用户无法访问正常页面
语法格式:
backend name{ .attribute = "value"; }说明:
backend 是定义后端关键字,name 是当前后端节点的别名,多个后端节点时,name 名不能重复,否则覆盖。花括号里面定义当前节点相关的属性(键=值)。除默认节点外其它节点定义后必需有调用,否则 varnish 无法启动。后端是否正常可以通过 std.healthy(backend)判断
支持运算符:
= (赋值运算)
== (相等比较) (匹配,可以使用正则表达式,或访问控制列表)!~ (不匹配,可以使用正则表达式,或访问控制列表)
! (非)
&& (逻辑与)
|| (逻辑或)
属性列表:
.host=“xxx.xxx.xxx.xxx”;
要转向主机(即后端主机)的 IP 或域名,必填键/值对
.port=“8080”;
主机连接端口号或协议名(HTTP 等),默认 80
.host_header=’’;
请示主机头追加内容
.connect_timeout=1s;
连接后端的超时时间
.first_byte_timeout=5s;
等待从后端返回的第一个字节时间
.between_bytes_timeout=2s;
每接收一个字节之间等待时间
.probe=probe_name;
监控后端主机的状态,指定外部监控 name 或者内部直接添加
.max_connections=200;
设置最大并发连接数,超过这个数后连接就会失败
例:(下面两个例子结果是一样的,但第二个例子中更适用于集群,可以方便批量修改)
backend web{ .host="192.168.31.83"; .port="80"; .probe={ # 直接追加监控块.probe 是一个的参数 .url="/"; .timeout=2s; } } 或 probe web_probe{ # 监控必需定义在前面,否则后端调用找不到监控块。 .url="/"; .timeout=2s; } backend web{ .host="192.168.31.83"; .port="80"; .probe=web_probe; //调用外部共用监控块 }命令:probe 。监控可以循环访问指定的地址,通过响应时间判定服务器是否空闲或正常。这类命令非常适用于集群中某些节点服务器崩溃或负载过重,而禁止访问这台节点服务器。
语法格式:
probe name{ .attribute = "value"; }说明:
probe 是定义监控关键字,name 是当前监控点的别名,多个监控节点时,name 名不能重复,否则覆盖。花括号里面定义当前节点相关的属性(键=值)。没有必填属性,因为默认值就可以正常执行操作
属性列表:
.url="/";
指定监控入口 URL 地址,默认为"/"
.request="";
指定监控请求入口地址,比 .url 优先级高
.expected_response=“200”;
请求响应代码,默认是 200
.timeout=2s;
请求超时时间
.interval=5s;
每次轮询请求间隔时间,默认为 5s
.initial=-1;
初始启动时以.window 轮询次数中几次良好后续才能使用这个后端服务器节点,默认为 -1 ,则轮询完 .window 所有次数良好判定为正常
.window=8;
指定多少轮询次数,用于判定服务器正常,默认是 8
.threshold=3;
必须多少次轮询正常才算该后端节点服务器正常,默认是 3
例:创建健康监测,定义健康检查名称为 backend_healthcheck
probe backend_healthcheck { .url = "/"; .timeout = 1s; .interval = 5s; .window = 5; .threshold = 3; }在上面的例子中 varnish 将每 5s 检测后端,超时设为 1s。每个检测将会发送 get /的请求。如果 5 个检测中大于 3 个是成功,varnish 就认为后端是健康的,反之,后端就有问题了
directors 是 varnish 负载均衡模块,使用前必需引入 directors 模块,directors 模块主要包含:
round_robin,random,hash,fallback 负载均衡模式
round_robin : 循环依次逐个选择后端服务器
random : 随机选择后端服务器,可设置每个后端权重增加随机率
hash : 通过散列随机选择对应的后端服务器且保持选择对应关系,下次则直接找对应的后端服务器
Fallback:后备
注意:
random,hash 有权重值设置,用于提高随机率。每个后端最好都配置监控器(后端服务器正常监测)以便 directors 自动屏蔽不正常后端而不进入均衡列中。
这些操作需要你载入 VMOD(varnish module),然后在 vcl_init 中调用这个 VMOD
import directors; # load the directors backend web1 { .host = "192.168.0.10"; .port = "80"; .probe = backend_healthcheck; } backend web2 { .host = "192.168.0.11"; .port = "80"; .probe = backend_healthcheck; } # 初始化处理 sub vcl_init { # 调用 vcl_init 初始化子程序创建后端主机组,即 directors new web_cluster = directors.round_robin(); # 使用 new 关键字创建 drector 对象,使用 round_robin 算法 web_cluster.add_backend(web1); # 添加后端服务器节点 web_cluster.add_backend(web2); } # 开始处理请求 sub vcl_recv { # 调用 vcl_recv 子程序,用于接收和处理请求 set req.backend_hint = web_cluster.backend(); # 选取后端 }说明:
set 命令是设置变量
unset 命令是删除变量
web_cluster.add_backend( backend , real ); 添加后端服务器节点,backend 为后端配置别名,real 为权重值,随机率计算公式:100 * (当前权重 / 总权重)
req.backend_hint 是 varnish 的预定义变量,作用是指定请求后端节点
vcl 对象需要使用 new 关键字创建,所有可创建对象都是内定的,使用前必需 import,所有new 操作只能在 vcl_init 子程序中
扩展:varnish 将不同的 url 发送到不同的后端 server
import directors; # load the directors backend web1 { .host = "192.168.0.10"; .port = "80"; .probe = backend_healthcheck; } backend web2 { .host = "192.168.0.11"; .port = "80"; .probe = backend_healthcheck; } backend img1 { .host = "img1.lnmmp.com"; .port = "80"; .probe = backend_healthcheck; } backend img2 { .host = "img2.lnmmp.com"; .port = "80"; .probe = backend_healthcheck; } # 初始化处理 sub vcl_init { # 调用 vcl_init 初始化子程序创建后端主机组,即 directors new web_cluster = directors.round_robin(); # 使用 new 关键字创建 drector 对象,使用 round_robin 算法 web_cluster.add_backend(web1); # 添加后端服务器节点 web_cluster.add_backend(web2); new img_cluster = directors.random(); img_cluster.add_backend(img1,2); # 添加后端服务器节点,并且设置权重值 img_cluster.add_backend(img2,5); } # 根据不同的访问域名,分发至不同的后端主机组 sub vcl_recv { if (req.http.host ~ "(?i)^(www.)?benet.com$") { set req.http.host = "www.benet.com"; set req.backend_hint = web_cluster.backend(); # 选取后端 } elsif (req.http.host ~ "(?i)^images.benet.com$") { set req.backend_hint = img_cluster.backend(); } }说明:中的 i 就是忽略大小写的意思。(?i)表示开启忽略大小写,而(?-i)表示关闭忽略大小写
创建一个地址列表,用于后面的判断,可以是域名或 IP 集合。这个可以用于指定某些地址请求入口,防止恶意请求等
语法格式:
acl purgers { "127.0.0.1"; "localhost"; “192.168.134.0/24” !"192.168.134.1"; }说明:acl 是访问列表关键字(必需小写),name 是该列表的别名用于调用,花括号内部是地址集
注意:如果列表中包含了无法解析的主机地址,它会匹配任何地址
如果不想让它匹配可以在前添加一个 ! 符号,如上面 !“192.168.134.1”;
使用 ACL 只需要用 匹配运算符 ~ 或 !~ 如:
sub vcl_recv { if (req.method == "PURGE") { # PURGE 请求的处理 if (client.ip ~ purgers) { return(purge); } else { return(synth(403, "Access denied.")); } } }说明:默认情况,varnish 不缓存从后端响应的 http 头中带有 Set-Cookie 的对象。如果客户 端发送的请求带有 Cookie header,varnish 将忽略缓存,直接将请求传递到后端
为发往后端主机的请求添加 X-Forward-For 首部,首次访问增加 X-Forwarded-For 头信息,方 便后端程序获取客户端 ip,而不是 varnish 地址
if (req.restarts == 0) { if (req.http.x-forwarded-for) { # 如果设置过此 header 则要再次附加上用逗号隔开 set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; } else { # 如果只有一层代理的话,就无需设置了 set req.http.X-Forwarded-For = client.ip; } }说明: X-Forwarded-For 是用来识别通过 HTTP 代理或负载均衡方式连接到 Web 服务器的客户 端最原始的 IP 地址的 HTTP 请求头字段
子程序:
子程序是一种类似 C 的函数,但是程序没有调用参数,子程序以 sub 关键字定义。在 VCL里子程序是用于管理程序
注意:所有 VCL 内置的程序都是以 vcl_ 开头,并已经预置好,在 VCL 文件中只要声明对应的内置子程序,都会在对应的流程中调用
配置 web01、web02 做为后端服务器(过程略)
确保 varnish 服务器能正常访问 web01、web02
Varnish 缓存代理服务器配置:
当启动 varnish 时有两个重要的参数你必须设置: 一个是处理 http 请求的 tcp 监听端口,另一个是处理真实请求的后端 server
注:如果你使用操作系统自带的包管理工具安装的 varnish,你将在下面的文件找到启动参数:Red Hat, Centos: /etc/sysconfig/varnish
‘-a’ 参数定义了 varnish 监听在哪个地址,并用该地址处理 http 请求,你可能想设置这个参数在众所周知的 http 80 端口
例子:
-a :80 -a localhost:80 -a 192.168.1.100:8080 -a ‘[fe80::1]:80’ -a ‘0.0.0.0:8080,[::]:8081’
如果你的 webserver 和 varnish 运行在同一台机器,你必须换一个监听地址
-f 添加 vcl 文件,-b 定义后端 serve
varnish 需要知道从哪里找到这个需要缓存的 http server.你可以用-b 参数指定,或者帮把它放在 vcl 文件中,然后使用-f 参数指定
在启动的时候使用-b 是一个快捷的方式.
-b 192.168.1.2:80
注意:如果你指定的是 name,这个 name 必须能解析成一个 IPv4 或者 IPv6 的地址如果你使用-f 参数,你启动的时候可以在-f 指定 vcl 文件
默认的 varnish 使用 100M 的内存来缓存对象,如果你想缓存更多,可以使用-s 参数
注:Varnish 拥有大量的有用的命令行参数,建议查看其帮助
[root@varinsh ~]# /usr/local/sbin/varnishd -h启动 varnish
[root@varinsh ~]# varnishd -f /usr/local/var/varnish/default.vcl -s malloc,200M -a 0.0.0.0:80[root@varinsh ~]# netstat -anput | grep 80 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 13968/varnishd现在,varnish 已经启动和运行,你可以通过 varnish 访问您的 Web 应用程序
第一次访问
第二次访问