一、varnish 原理:
1)Varnish 简介:
varnish 缓存是 web 应用加速器,同时也作为 http 反向缓存代理。你可以安装 varnish 在任何 http 的前端,同时配置它缓存内容。与传统的 squid 相比,varnish 具有性能更高、速度更 快、管理更加方便等诸多优点。有一部分企业已经在生产环境中使用其作为旧版本的 squid 的替代方案,以在相同的服务器成本下提供更好的缓存效果,Varnish 更是作为 CDN 缓存服 务器的可选服务之一。
根据官网的介绍,Varnish 的主要特性如下:https://www.varnish-cache.org/
1.缓存位置:可以使用内存也可以使用磁盘。如果要使用磁盘的话推荐 SSD 做 RAID1
2.日志存储:日志也存储在内存中。存储策略:固定大小,循环使用
3.支持虚拟内存的使用。
4.有精确的时间管理机制,即缓存的时间属性控制。
5.状态引擎架构:在不同的引擎上完成对不同的缓存和代理数据进行处理。可以通过特定的 配置语言设计不同的控制语句,以决定数据在不同位置以不同方式缓存,在特定的地方对经 过的报文进行特定规则的处理。
6.缓存管理:以二叉堆格式管理缓存数据,做到数据的及时清理。
2)Varnish 与 Squid 的对比
相同点:
都是一个反向代理服务器;
都是开源软件;
Varnish 的优势:
1、Varnish 的稳定性很高,两者在完成相同负荷的工作时,Squid 服务器发生故障的几率要 高于 Varnish,因为使用 Squid 要经常重启;
2、Varnish 访问速度更快,因为采用了“Visual Page Cache”技术,所有缓存数据都直接从内存 读取,而 squid 是从硬盘读取,因而 Varnish 在访问速度方面会更快;
3、Varnish 可以支持更多的并发连接,因为 Varnish 的 TCP 连接释放要比 Squid 快,因而在 高并发连接情况下可以支持更多 TCP 连接;
4、Varnish 可以通过管理端口,使用正则表达式批量的清除部分缓存,而 Squid 是做不到的; squid 属于是单进程使用单核 CPU,但 Varnish 是通过 fork 形式打开多进程来做处理,所以可 以合理的使用所有核来处理相应的请求;
Varnish 的劣势:
1、varnish 进程一旦 Crash 或者重启,缓存数据都会从内存中完全释放,此时所有请求都会 发送到后端服务器,在高并发情况下,会给后端服务器造成很大压力; 2、在 varnish 使用中如果单个 url 的请求通过 HA/F5 等负载均衡,则每次请求落在不同的 varnish 服务器中,造成请求都会被穿透到后端;而且同样的请求在多台服务器上缓存,也 会造成 varnish 的缓存的资源浪费,造成性能下降;
Varnish 劣势的解决方案:
针对劣势一:在访问量很大的情况下推荐使用 varnish 的内存缓存方式启动,而且后面需要 跟多台 squid/nginx 服务器。主要为了防止前面的 varnish 服 务、服务器被重启的情况下, 大量请求穿透 varnish,这样 squid/nginx 可以就担当第二层 CACHE,而且也弥补了 varnish 缓 存在内存中重启都会释放的问题;
针对劣势二:可以在负载均衡上做 url 哈希,让单个 url 请求固定请求到一台 varnish 服务器 上;
3)使用 varnish 作为 web 代理缓存的原理 :
varnish 是一个 http 反向代理的缓存。它从客户端接收请求然后尝试从缓存中获取数据来响 应客户端的请求,如果 varnish 不能从缓存中获得数据来响应客户端,它将转发请求到后端 (backend servers),获取响应同时存储,最后交付给客户端。 如果 varnish 已经缓存了某个响应,它比你传统的后端服务器的响应要快很多,所以你需要 尽可能是更多的请求直接从 varnish 的缓存中获取响应。 varnish 决定是缓存内容或者是从后端服务器获取响应。后端服务器能通过 http 响应头中的 Cache-Control 来同步 varnish 缓存内容。在某些条件下 varnish 将不缓存内容,最常见的是使 用 cookie。当一个被标记有 cookie 的客户端 web 请求,varnish 默认是不缓存。这些众多的 varnish 功能特点都是可以通过写 vcl 来改变的。
5)简单架构:
Varnish 分为 management 进程和 child 进程;
Management 进程:对子进程进行管理,同时对 VCL 配置进行编译,并应用到不同的状态引 擎。
Child 进程:生成线程池,负责对用户请求进行处理,并通过 hash 查找返回用户结果。
6)varnish 主要配置部分:
varnish 配置主要分为:后端配置,ACL 配置,probes 配置,directors 配置,核心子程序配置 几大块。
其中后端配置是必要的,在多台服务器中还会用到 directors 配置,核心子程序配 置。
后端配置:即给 varnish 添加反代服务器节点,最少配置一个。
ACL 配置:即给 varnish 添加访问控制列表,可以指定这些列表访问或禁止访问。
probes 配置:即给 varnish 添加探测后端服务器是否正常的规则,方便切换或禁止对应后端 服务器。
directors 配置:即给 varnish 添加负载均衡模式管理多个后端服务器。
核心子程序配置:即给 varnish 添加后端服务器切换,请求缓存,访问控制,错误处理等规 则。
7)VCL 中内置预设变量:变量(也叫 object):
req:The request object,请求到达时可用的变量(客户端发送的请求对象)
bereq:The backend request object,向后端主机请求时可用的变量
beresp:The backend response object,从后端主机获取内容时可用的变量(后端响应请求对象)
resp:The HTTP response object,对客户端响应时可用的变量(返回给客户端的响应对象) obj:存储在内存中时对象属性相关的可用的变量(高速缓存对象,缓存后端响应请求内容)
预设变量是系统固定的,请求进入对应的 vcl 子程序后便生成,这些变量可以方便子程序提 取,当然也可以自定义一些全局变量。
当前时间:
now :作用:返回当前时间戳。
服务器:(服务器基本信息)
注:原 server.port 已经弃用,如果要取服务器端口号使用 std.port(server.ip),需要 import std; 才可以使用 std
server.hostname:服务器主机名。
server.identity:服务器身份标识。
server.ip:返回服务器端 IP 地址。
varnish 子程序调用流程图,通过大部分子程序的 return 返回值进入下一步行动:
11)优雅模式(Garce mode)
Varnish 中的请求合并 当几个客户端请求同一个页面的时候,varnish 只发送一个请求到后端服务器,然后让其他 几个请求挂起并等待返回结果;获得结果后,其它请求再复制后端的结果发送给客户端; 但如果同时有数以千计的请求,那么这个等待队列将变得庞大,这将导致 2 类潜在问题: 惊群问题(thundering herd problem),即突然释放大量的线程去复制后端返回的结果,将导致 负载急速上升;没有用户喜欢等待; 故为了解决这类问题,可以配置 varnish 在缓存对象因超时失效后再保留一段时间,以给那 些等待的请求返回过去的文件内容(stale content),配置案例如下:
二、安装 varnish
1、安装依赖关系的软件包(注:使用 centos 在线 yum 源)
[root@varnish ~]# yum -y install autoconf automake libedit-devel libtool ncurses-devel pcre-devel pkgconfig python-docutils python-sphinx
2、安装 varnish Varnish 的官方网址为 http://varnish-cache.org,可以在这里下载最新版本的软件。
下载地址:https://www.varnish-cache.org/content/varnish-cache-403
解压,进入解压目录编译安装:
[root@varnish ~]# tar zxf varnish-4.0.3.tar.gz
[root@varnish ~]# cd varnish-4.0.3/
[root@varnish varnish-4.0.3]# ./configure
注:不指定安装路径,默认是安装在/usr/local 目录下
编译、安装
[root@varnish varnish-4.0.3]# make && make install
复制 vcl 文件(在编译安装目录下),如果安装目录里没有 default.vcl 文件。
复制到安装目录的/usr/local/var/varnish/目录下(当然并无必需要求在哪个目录,因为正式 启动时还得指定这个文件的目录)
[root@varnish varnish-4.0.3]# cp etc/example.vcl /usr/local/var/varnish/default.vcl
(编写配置文件 把原来的配置cp下来)
#使用 varnish 版本 4 的格式.
vcl 4.0;
#加载后端负载均衡模块
import directors;
#加载 std 模块
import std;
#创建名为 backend_healthcheck 的健康检查策略
probe backend_healthcheck {
.url="/";
.interval = 5s;
.timeout = 1s;
.window = 5;
.threshold = 3;
}
#定义后端服务器
backend web_app_01 {
.host = "192.168.239.134"; (定义第一台webip)
.port = "80";
.first_byte_timeout = 9s;
.connect_timeout = 3s;
.between_bytes_timeout = 1s;
.probe = backend_healthcheck;
}
backend web_app_02 {
.host = "192.168.239.141"; (定义第二台webip)
.port = "80";
.first_byte_timeout = 9s;
.connect_timeout = 3s;
.between_bytes_timeout = 1s;
.probe = backend_healthcheck;
}
#定义允许清理缓存的 IP
acl purgers {
"127.0.0.1";
"localhost";
"192.168.239.0/24"; (本网段)
}
#vcl_init 初始化子程序创建后端主机组
sub vcl_init {
new web = directors.round_robin();
web.add_backend(web_app_01);
web.add_backend(web_app_02);
}
#请求入口,用于接收和处理请求。这里一般用作路由处理,判断是否读取缓存和指定该请
求使用哪个后端
sub vcl_recv {
#将请求指定使用 web 后端集群 .在集群名后加上 .backend()
set req.backend_hint = web.backend();
# 匹配清理缓存的请求
if (req.method == "PURGE") {
if (!client.ip ~ purgers) {
return (synth(405, "Not Allowed."));
}
# 是的话就执行清理
return (purge);
}
# 如果不是正常请求 就直接穿透没商量
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "PATCH" &&
req.method != "DELETE") {
return (pipe);
}
# 如果不是 GET 和 HEAD 就跳到 pass
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
#如果匹配动态内容访问请求就跳到 pass
if (req.url ~ "\.(php|asp|aspx|jsp|do|ashx|shtml)($|\?)") {
return (pass);
}
#具有身份验证的请求跳到 pass
if (req.http.Authorization) {
return (pass);
}
if (req.http.Accept-Encoding) {
if (req.url ~
"\.(bmp|png|gif|jpg|jpeg|ico|gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)$") {
unset req.http.Accept-Encoding;
} elseif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elseif (req.http.Accept-Encoding ~ "deflate") {
set req.http.Accept-Encoding = "deflate";
} else {
unset req.http.Accept-Encoding;
}
}
if (req.url ~
"\.(css|js|html|htm|bmp|png|gif|jpg|jpeg|ico|gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)
($|\?)") {
unset req.http.cookie;
return (hash);
}
# 把真实客户端 IP 传递给后端服务器 后端服务器日志使用 X-Forwarded-For 来接收
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;
}
}
return (hash);
}
# hash 事件(缓存事件)
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
return (lookup);
}
# 缓存命中事件
sub vcl_hit {
if (req.method == "PURGE") {
return (synth(200, "Purged."));
}
return (deliver);
}
# 缓存不命中事件
sub vcl_miss {
if (req.method == "PURGE") {
return (synth(404, "Purged."));
}
return (fetch);
}
# 返回给用户的前一个事件 通常用于添加或删除 header 头
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
#取消显示 php 框架版本的 header 头
unset resp.http.X-Powered-By;
#取消显示 web 软件版本、Via(来自 varnish)等 header 头 为了安全
unset resp.http.Server;
unset resp.http.X-Drupal-Cache;
unset resp.http.Via;
unset resp.http.Link;
unset resp.http.X-Varnish;
#显示请求经历 restarts 事件的次数
set resp.http.xx_restarts_count = req.restarts;
#显示该资源缓存的时间单位秒
set resp.http.xx_Age = resp.http.Age;
#显示该资源命中的次数
set resp.http.hit_count = obj.hits;
#取消显示 Age 为了不和 CDN 冲突
unset resp.http.Age;
#返回给用户
return (deliver);
}
# pass 事件
sub vcl_pass {
return (fetch);
}
#处理对后端返回结果的事件(设置缓存、移除 cookie 信息、设置 header 头等) 在 fetch 事件后自动调用
sub vcl_backend_response {
#开启 grace 模式 表示当后端全挂掉后 即使缓存资源已过期(超过缓存时间) 也会把该
资源返回给用户 资源最大有效时间为 5 分钟
set beresp.grace = 5m;
#后端返回如下错误状态码 则不缓存
if (beresp.status == 499 || beresp.status == 404 || beresp.status == 502) {
set beresp.uncacheable = true;
}
#如请求 php 或 jsp 则不缓存
if (bereq.url ~ "\.(php|jsp)(\?|$)") {
set beresp.uncacheable = true;
} else { //自定义缓存文件的缓存时长,即 TTL 值
if (bereq.url ~ "\.(css|js|html|htm|bmp|png|gif|jpg|jpeg|ico)($|\?)") {
set beresp.ttl = 15m;
unset beresp.http.Set-Cookie;
} elseif (bereq.url ~ "\.(gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)($|\?)") {
set beresp.ttl = 30m;
unset beresp.http.Set-Cookie;
} else {
set beresp.ttl = 10m;
unset beresp.http.Set-Cookie;
}
}
#返回给用户
return (deliver);
}
sub vcl_purge {
return (synth(200,"success"));
}
sub vcl_backend_error {
if (beresp.status == 500 ||
beresp.status == 501 ||
beresp.status == 502 ||
beresp.status == 503 ||
beresp.status == 504) {
return (retry);
}
}
sub vcl_fini {
return (ok);
}
其他两台web安装nginx:
(启动命令)
(会报错往里追加uuid就行)