Linux Varnish
Web Cache:一般的大型网站架构都会使用缓存,缓存一般位于前段代理与后端服务器之间,当用户发出请求以后,会首先通过代理查找缓存,如果缓存中有相关的数据就直接反回给客户端,如果没有就继续向后端真实提供数据的原始服务器请求相关数据,然后再返回给客户端;你可能会有疑问,这样引入缓存难道不会增加网络延迟吗?当然会,但是引入缓存的效益是可以完全抵消引入缓存带来的延迟的,因为如果不使用缓存,客户端的所有请求都会被发送到后端原始服务器,这样一来对后端原始服务器的压力就会急剧上升,尤其是访问量巨大的大型网站,更是扛不住的,还有一种原因就是缓存可以直接缓存某些用户请求的动态内容的结果,无需服务器再执行一遍,这样也会节省很多时间,所以这种情况下引入缓存是利大于弊的;
缓存之所以存在的原因:
之所以有缓存这一说法,是因为程序(指令和数据)具有局部性;其局部性包括时间局部性和空间局部性;
时间局部性:对于同一程序,刚刚被访问到的数据,在接下来有很大可能还会再次被访问到;
空间局部性:一个数据被访问到,与之较近的数据也可能被访问到;所以才有热区数据这一说法;
正是因为程序的此种特性,所以缓存才有存在的意义;
缓存的存储形式:
缓存在内存中一般是以key-value的形式存在的,其中key一般为访问路径,其可以是URL或者IP地址等,并且key一般都会被hash处理以后再进行存储,因为被hash以后的数据查找起来方便、速度快(O(1));value一般为访问路径所对应的资源;
缓存命中率:
如果在缓存中有用户需要的数据我们称为缓存命中;
命中率:hit/(hit+miss)
LRU:当缓存空间被耗尽时,如果有新的缓存进来,缓存服务器就会根据LRU(最近最少使用)算法将一些缓存清理掉,用来存储新的缓存数据;并且缓存也是有生命周期的(比如缓存过期),会定期进行清理;
缓存数据类型:可缓存的和不可缓存的;
可缓存的:比如网站的图标,各种图片、文本等
不可缓存的:带有用户私有信息的,比如cookie信息等;
缓存处理的步骤:
接收请求→解析请求(提取请求中的URL及各种HTTP首部)→查询缓存→新鲜度检测(缓存是否过期)→创建响应报文→返回响应→记录日志
新鲜度检测机制:
过期日期:
HTTP/1.0通过http response中的expires首部指定资源过期时间(绝对时间|相对时间);
HTTP/1.1通过http response中的Cache-Control:max-age来指定资源过期时间;
有效性再验证(revalidate):
在返回缓存前先到服务器验证一下,询问服务器自己的某个缓存资源是否过期,能否继续使用,服务器会进行检测:
1.如果服务器中的原始内容没有改变,则仅返回响应首部(不附带body部分),响应码为304(not modified)
2.如果服务器中的原始内容发生改变,则正常响应,响应码为200;
3.如果响应内容不存在了,则响应404;此时缓存中的对象(object)也应该删除;
条件式请求首部:
If-Modified-Since:基于时间戳验证,自从某个时间点之后资源是否发生改变;
If-Unmodified-Since:基于时间戳验证,自动某个时间点之后资源是否没有发生改变;
通常使用在变化周期慢的资源中;
If-Match:基于Etag验证资源是否匹配;
If-None-Match:基于Etag验证资源是否不匹配;
通常使用在变化周期快的资源中;
Varnish (v4):相对于squid来说比较轻量级;
Varnish是一个缓存HTTP数据的反向代理。它接收来自客户端的请求并尝试从缓存中回答它们。如果Varnish无法使用缓存应答来自客户端的请求,它会将请求转发给后端,获取后端服务器的响应,然后将响应数据存储在缓存中再将其响应给客户端。
当我们使用缓存时一般在前端的负载均衡调度器上建议使用基于URI的负载均衡调度算法,使对同一资源的请求始终发往同一缓存服务器;
Varnish Arch:
Varnish程序运行起来以后会启动一个管理(Management)进程和一个或多个子进程(Child)组成,其中一个子进程可以生成多个线程,然后通过线程对请求完成响应等操作;其中管理进程负责编译配置文件并从配置文件中读取配置参数、管理并监控子进程、完成varnish的初始化、提供CLI管理接口等;子进程负责加载管理进程编译好的配置文件中的参数,然后提供相应的缓存服务;子进程还负责接收命令行参数、存储缓存、hash数据、记录日志、统计数据、清理过期缓存、接收连接请求、处理用户请求、与后端服务器交互等;
Note:Varnish的配置文件是使用VCL编写的,当Varnish程序读取配置文件是并不是像ningx这种软件一样,直接读取配置文件中的参数来设置各种功能的,而是首先将编写好的配置文件通过VCL编译器调用C编译器编译成二进制代码,然后才能被Varnish加载使用的;
Varnish的日志功能:
Varnish日志使用的是Shared Memory Log(共享内存日志),其默认大小一般为90MB;并且分为两部分:一部分为计数器,另一部分为请求相关的日志数据;
VCL:Varnish Configuration Language
Varnish的配置文件就是使用这种语言编写的,其为基于域的简单编程语言;
Varnish的缓存都是存放在内存中的,所以对内存的操作效率就极为关注,通常情况先一般的软件都是使用malloc()和free()函数来分配和释放内存,但是varnish使用的并不是这个,而是另外一种更高效的组件jemalloc来管理内存,可以实现并发malloc;
Varnish存储缓存的方式:可以通过varnishd 的”-s”选项来进程设置;
file:将所有缓存放在单个文件中,varnish进程内部会对这些存储在一起的各个缓存作区分,存储在磁盘中,所以磁盘IO性能可能成为瓶颈,但是重启会失效;
malloc:基于内存作缓存,性能高,大小有限,重启失效;
persistent:基于文件的持久存储;(暂时是实验开发中的功能);
配置Varnish的三种方法:
1.varnishd应用程序的命令行参数参数;
用于设置监听的套接字、使用的存储类型等;
通过”-p”选项指明各种参数;可在运行时实时配置;
Note:可以通过查看/usr/lib/systemd/system/varnish.service的内容,进行了解;
2.VCL:配置缓存系统的缓存机制;
通过vcl配置文件进行配置,先编译后应用,依赖于C编译器;
Varnish配置:
配置文件:
/etc/varnish/default.vcl
varnish使用的vcl编写的配置文件,设置缓存机制;
/etc/varnish/varnish.params
varnish启动时使用的参数,其中定义的变量会被/usr/lib/systemd/system/varnish.service调用;也可以在命令行中使用varnishd命令手动指定启动时加载的参数及文件;
监听端口:
服务端口默认监听在6081号端口上;
管理端口默认监听在6082号端口上;
实例:
三台主机,一台centos7(ip:192.168.80.139)运行varnish v4,两台centos6(clone1,ip:192.168.80.131、clone2)作为后端服务器运行httpd;
Centos7:
vim /etc/varnish/default.vcl
backend default {
.host = "192.168.80.131"; 指定后端服务器地址,也可以使用主机名;
.port = "80"; 指定后端服务器WEB服务监听的接口;
}
Centos6:clone1
service httpd start
for i in {1..10} ; do echo "<h1>page $i on clone1</h1>" > /var/www/html/test$i.html ; done
Centos6:clone2
service httpd start
for i in {1..10} ; do echo "<h1>page $i on clone2</h1>" > /var/www/html/test$i.html ; done
Centos7:
systemctl start varnish.service
浏览器键入:http://192.168.80.139:6081/test1.html
管理工具介绍:varnishadm
-S:指定秘钥文件
-T:指定被管理主机的连接的地址和端口
例子: varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 →即可进入管理接口;
使用help获取帮助信息;
varnish> help
help [<command>] 获取帮助
ping [<timestamp>] ping测试
auth <response>
quit 退出CLIL界面
banner
status 列出子进程的状态信息
start 启动子进程
stop 停止子进程
vcl.load <configname> <filename> 加载.vcl配置文件
configname:自定义配置文件名
filename:未编译的.vcl文件
例子:varnish> vcl.load test1 default.vcl
vcl.inline <configname> <quoted_VCLstring>
vcl.use <configname> 指定欲使用.vcl配置文件
vcl.discard <configname> 删除编译后的指定配置文件
vcl.list 列出可用的.vcl配置文件
param.show [-l] [<param>] 列出支持的参数
param.set <param> <value> 设置参数
panic.show
panic.clear
storage.list 显示使用的存储机制
vcl.show [-v] <configname> 显示.vcl文件编译之前的内容
backend.list [<backend_expression>] 列出后端服务器列表
backend.set_health <backend_expression> <state>
ban <field> <operator> <arg> [&& <field> <oper> <arg>]... 手动清理缓存中的缓存对象
ban.list
日志工具:
varnishlog
varnishncsa
其他工具:
varnishtop:日志条目排序
varnishstat:缓存统计信息
Varnish的VCL配置文件:
当请求进入Varnish代理中时,Varnish服务会对其进行各种各样的检查,比如是否允许其访问、其访问的资源类型(动|静)、是否含有cookie、请求头部中的方法为何、怎么将请求转发至后端、服务器端响应的资源是否进行缓存等都是需要考虑的;这些配置都是要在.vcl文件中设定的;为了解决上面的多种问题,Varnish设计了一种类似netfilter的钩子函数的机制,在Varnish中称之为状态引擎;
Varnish(v3)接收请求后的流程:请求首先会被vcl_recv()这个状态引擎接收,然后判断这个请求是否为一个可缓存对象(比如PUT和POST方法就不可缓存),如果不是就将请求传到vcl_fetch()这个状态引擎中,到后端服务器取相应资源,然后再经过vcl_delive()r这个状态引擎构建响应报文,响应客户端;如果是可缓存对象,首先会交给vcl_hash()进行URL的hash计算,然后将hash后的结果与缓存中的key进行比较,如果有匹配的key则为命中(hit),然后经过vcl_hit()进行缓存处理,最后经过vcl_deliver()构建响应报文,响应给客户端,如果没有匹配成功则为未命中(miss),则会通过vcl_miss()做未命中处理,然后通过vcl_fetch()去后端服务器取资源,最后通过vcl_deliver()构建响应报文,响应给客户端;
Note:从上面可以看出来,各状态引擎之间有相关性;前一个状态引擎(engine)如果可以有多种下游的状态引擎,则上游的状态引擎需要使用return()指明要转移至的下游状态引擎;
处理上面提到的那几种状态引擎之外还有一些常用的:
vcl_pass():如果请求头部的方法为PUT、POST或者带有cookie或者需要验证则就会使用此状态引擎;
vcl_error():直接由Varnish在前端构成错误页面信息,告诉客户端有错误发生;
vcl_pipe():当遇到自己无法理解的后端响应时,自己什么也不做,直接将请求传递给后端服务器;
Varnish服务接收请求后的流程图(v4):
图片来自varnish-book;
vcl_recv():req.
vcl_hash():req.
vcl_hit():req.、obj.
vcl_miss():req.、bereq.
vcl_backend_fetch():req.、bereq.、beresp.
vcl_deliver():resp.
VCL语法:
1.使用//、#、/*foo*/作为注释;
2.每个状态引擎都可以当做是varnish的子例程,可以通过”sub”关键字进行定义;
3.不支持循环;支持很多内置的状态变量;
4.使用return(action)来选定其下游状态引擎;
5.使用域作为配置段,比如 sub req_recv { };
6.可以自定义设置;
7.支持操作符:=、==、~、!、&&、||;
8.使用if作条件判断,可以嵌套;
if (CONDTION) {
} else {
}
9.通过”set”来设定变量值;
set name=value
10.通过”unset”来撤销设定的变量值;
unset name
11.VCL变量
client
client.ip:客户端的IP地址;
server
server.ip:接收客户端连接的IP地址;
server.hostname:服务器的主机名;
req
req.http.*:调用request报文中http协议的指定的HEADER首部;
req.http.X-Forwarded-For
req.method | req.request:指定请求方法;
req.url:请求的URL
req.proto:客户端使用的HTTP协议版本,通常为“HTTP / 1.1”或“HTTP / 2.0”;
req.ttl:查找高速缓存返回响应的最大时间限制;
resp
resp.proto:用于响应的HTTP协议版本;
resp.status:用于响应的HTTP状态码;
resp.http.*:响应报文中的HTTP头部信息;
bereq
bereq.method:请求方法
bereq.url:请求的URL,从req.url复制而来
bereq.http.*:由Varnish发往后端主机的请求报文中的http协议的指定HEADER首部;
bereq.backend:后端主机;
bereq.backend:指明使用的后端主机;
beresp
beresp.status:后端主机响应的状态码信息;
beresp.reason:后端主机返回的HTTP状态消息(原因短语);
beresp.http.*:调用后端主机返回的http报文中指定的HEADER首部;
brresp.ttl:后端服务器响应的内容的余下的生存时长;
obj
obj.proto:存储在对象中的HTTP协议版本;
obj.status:存储在对象中的HTTP状态码;
obj.reason:存储在对象中的HTTP原因短语;
obj.http.*:存储在对象中HTTP标头;
obj.hits:从缓存中命中次数;
obj.ttl:存储在对象中的ttl值;
storage
storage.<name>.free_space:查看缓存空间空闲的大小;
storage.<name>.used_space:查看缓存空间已使用的大小;
Type: BYTES
变量详解:https://varnish-cache.org/docs/6.2/reference/vcl.html#local-server-remote-and-client
支持虚拟主机:
sub vcl_recv {
if (req.http.host == www.guowei.com) {
set bereq.http.host = “www.GW.com”;
}
}
强制对某资源的请求不检查缓存:
sub vcl_recv {
if (req.url ~ “\.jpg$” || req.url ~ “(?i)\.png$”) {
return(pass);
}
}
只要是.jpg或者.png结尾的请求都不查找缓存,并且不区分大小写“(?i)”;
对特定类型的资源取消其私有的cookie标识:
sub vcl_backend_response {
if (beresp.http.cache-control !~ “s-maxage”) {
if (bereq.url ~ “(?i)\.jpg$”) {
set beresp.ttl = 600s;
unset beresp.http.Set.-Cookie;
}
if (bereq.url ~ “(?i).\css$”) {
set beresp.ttl = 300s;
unset beresp.http.Set-Cookie;
}
}
实现负载均衡:
需要在配置文件中添加:import directors;
backend name { }:设置后端服务器属性;
例子:
vcl 4.0;
import directors;
# Default backend definition. Set this to point to your content server.
backend clone1 {
.host = "192.168.80.131";
.port = "80";
}
backend clone2 {
.host = "192.168.80.134";
.port = "80";
}
sub vcl_init {
new web = directors.round_robin();
web.add_backend(clone1);
web.add_backend(clone2);
}
sub vcl_recv {
if (req.url ~ "/test3.html$") {
return(pass);
} else {
set req.backend_hint = web.backend();
}
}
后端主机的健康状态检测方式:
.url:判定后端主机健康与否要请求的url;
.expected_response:期望的响应状态码,默认为200;
例子:
backend clone1 {
.host = "192.168.80.131";
.port = "80";
.probe = {
.url = "/test1.html";
}
}
backend clone2 {
.host = "192.168.80.134";
.port = "80";
.probe = {
.url = "/test1.html";
}
}
动静分离:
backend clone1 {
.host = "192.168.80.131";
.port = "80";
.probe = {
.url = "/test1.html";
}
}
backend clone2 {
.host = "192.168.80.134";
.port = "80";
.probe = {
.url = "/test1.html";
}
}
sub vcl_recv {
if (req.url ~ "/test4.html$") {
set req.backend_hint = clone1;
} else {
set req.backend_hint = clone2;
}
}
注:根据马哥视频做的学习笔记,如有错误,欢迎指正;侵删;