web缓存是对网页文档的临时缓存的信息技术,web缓存能有效降低带宽使用量和服务器负载,提高用户体验。为什么需要缓存呢? 经验研究发现程序的运行具有两个方面的局部性特征:
时间局部性:数据再次被访问到的几率很大
空间局部性:周围数据也可能被访问到的几率很大
根据局部性特征,将访问过的资源IO或加载到高速存储上,不再从源位置加载;另外把其周边的数据也加载到高速存储中,加速资源的访问。缓存就是使用了高速的设备和良好结构设计加速资源访问速度。
一、web缓存
web缓存加速器常常应用的于调度器之后,实际服务器或其他代理服务器之前,接受用户请求,命中后还需要检测缓存的可用性,如果可用则构建相应报文会应客户端,如果没有命中或缓存不可用,则自己做为反代服务器请求真实服务器端,根据设置,将返回结果缓存或不缓存,同样将请求返回给客户端。所以varnish同时也是一款web反向代理服务器。
web缓存控制机制主要是依靠http请求报文首部控制的,如http/1.0中引入了Exppires报文首部,它确认了一个过期时间,缓存过期意义是超过了缓存时间那么请求被转发到真实服务器去请求,并在此缓存;还有http/1.1中引入了Cache-Control系列报文首部,它们有如下内容:
来源 | header | 意义 |
request | no-cache | 可缓存,但响应前要到原服务器确认 |
no-store | 不可缓存 | |
max-age | 缓存最大时长 | |
reponse | s-max-age | 公共缓存时长 |
public | 标识可缓存公共项 | |
must-revalidate | 必须重新校验 |
进行有效性验证时需要使用条件式请求首部,如
If-Modified-Since:基于原始内容的最近一次修改的时间戳进行判断文件是否已更新;
If-Unmodified-Since:基于原始内容的最近一次修改的时间戳进行判断文件是否未更新;
有可能有些内容,刷新时间非常短,缓存基于时间戳是无效的,这时基于ETag,内容校验码,再查看其是否匹配。基于Etag验证。
If-Match:基于Etag的比较进行判断是否相同
If-None-Match:基于Etag的比较进行判断是否不相同;
新鲜度检测机制有效性再验正(revalidate):一般讲,条件式请求后端判断:
(1)如果原始内容未改变,则仅响应首部(不用附带body部分);响应码为304(Not Modified);
(2)如果原始内容发生了改变,则正常响应,响应码为200;
(3)如果原始内容消失,则响应为404,此时缓存中的缓存项也应该被删除;
二、varnish的安装配置
varnish安装包位于epel源中,要配置YUM源包含YUM源,安装好后看它安装后的文件:
其配置文件主要有:
/etc/varnish/varnish.params # 运行配置文件
/etc/varnish/default.vcl # 进程控制文件,是VCL语言定义的
/etc/varnish/secret # 服务端和varnishadm命令行的预共享密钥文件
先介绍下varnish.params中的配置:
# varnish.params 配置文件
# Set this to 1 to make systemd reload try to switch vcl without restart. RELOAD_VCL=1 # 指明1时,sytemd reload varnish 尝试替换vcl文件,让新配置生效 # Main configuration file. You probably want to change it. VARNISH_VCL_CONF=/etc/varnish/default.vcl # 默认vcl文件位置,也可以改 -- # and IPv6 interfaces, otherwise specify a host name, an IPv4 dotted # quad, or an IPv6 address in brackets. VARNISH_LISTEN_ADDRESS=192.168.1.5 # 监听地址,默认为所有 VARNISH_LISTEN_PORT=80 # 缓存监听端口,在HA,nginx中支持端口映射,所有可以是随机端口上 # Admin interface listen address and port VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 # 管理接口的监听地址 VARNISH_ADMIN_LISTEN_PORT=6082 # 管理接口的端口 # Shared secret file for admin interface VARNISH_SECRET_FILE=/etc/varnish/secret # 预共享密码文件 # Backend storage specification, see Storage Types in the varnishd(5) # man page for details. #VARNISH_STORAGE="file,/var/lib/varnish/varnish_storage.bin,1G" VARNISH_STORAGE="malloc,128M" # 缓存设置,可用只有file和malloc, # malloc[,size] # file[,path[,size[,granularity]]],granularity 粒度 # Default TTL used when the backend does not specify one VARNISH_TTL=120 # 默认缓存时长 # User and group for the varnishd worker processes VARNISH_USER=varnish VARNISH_GROUP=varnish # Other options, see the man page varnishd(1) # DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300" # 线程池个数,线程池内默认,最大,最小,这些是运行时参数,使用时去掉"#"
可以从其服务配置文件看出,文件中的每个名字为其配置宏变量名称,其中最后的DAEMON指定了varnish可以用-p name=value 格式指定多个运行时配置选项。
三、VCL varnish配置语言
VCL能够让用户定义缓存策略,通过Manager进程分析转化为C代码,VCL编译器嗲用C编译器编译其为二进制代码,连接至child进程生效。VCL其定义策略方式主要是通过varinish附加在varnish内部的几个所谓的缓存处理进程实现。
VCL存在多个状态引擎,状态之间存在相关性,但彼此间相互隔离;每个引擎使用return(x)来退出当前状态,转入下一状态;
(1) 请求的为可缓存:
(a) 命中:通过本地缓存响应;
(b) 未命中:到后端服务取得相应内容;
可缓存对象:先缓存再响应;
缓存:
定义缓存时间;
自定义缓存键;
不可缓存对象:不缓存而直接响应;
(2) 请求的为不可缓存:
(a) 到后端服务器取得相应内容;
VCL语法:
(1) //, #,/*...*/:注释,;作为语句结尾
(2) sub $name:定义子例程;
(3) 不支持循环,支持条件判断;
(4) 有内建变量;
(5) 使用终止语句return,没有返回值;
(6) 操作符:=, ==, !=, ~, &&, ||
有一些编程经历,学习VCL还是可以很快的,没有这方面经验的,只能多费些精力了。下面介绍些VCL的简单示例。
四、varnish基础运行实例
varnish的默认状态有其默认的状态转换机制,所以这里就算你不定义,varnish也是可以运行的。
这里实验的基础逻辑拓扑如下:
示例一.记录缓存命中情况到http报文首部
配置文件如下所示:
backend default { .host = "192.168.0.133"; # 后端主机IP地址 .port = "80"; # 后端主机端口,可以地址映射 } ... sub vcl_deliver { # Happens when we have all the pieces we need, and are about to send the # response to the client. # # You can do accounting or modifying the final object here. if (obj.hits>0){ set resp.http.X-Cache="HIT Via "+" "+server.ip ; }else { set resp.http.X-Cache="MISS Via "+" " + server.ip; } }
这里可以使用varnishadm来动态加载改变varnish缓存行为,
varnishadm [-t timeout] [-S secret_file] [-T address:port]
-t timeout
-S 指定预共享密钥secret文件
-T 指定varnish主机地址,可以省略端口
# varnish -S /etc/varnish/secret -T 127.0.0.1:6082 vcl.list 200 # 响应吗 available 0 boot # 使用状态(active正在使用的,available可用的)最后为二进制文件名称 active 0 reload_2016-05-22T01:41:40 vcl.show boot 200 # # This is an example VCL file for Varnish. # # It does not do anything by default, delegating control to the # builtin VCL. The builtin VCL is called when there is no explicit # return statement. .... vcl.load test1 default.vcl # 编译VCL文件,这里使用默认default.vcl,可以改变 200 VCL compiled. vcl.use test1 200 VCL 'test1' now active # 动态链接应用编译好的二进制文件到子进程,修改其缓存行为 vcl.list 200 available 0 boot available 0 reload_2016-05-22T01:41:40 active 0 test1 # 这里可以看到缓存变化
这里修改default.vcl编译,并使用:
vcl.load test2 default.vcl 200 VCL compiled. vcl.use test2 200 VCL 'test2' now active
此时访问网页,用firefox的调试端功能F12,可以看到出现了
X-Cache="HIT Via "+" "+192.168.0.132
示例二:设置一些页面不缓存
sub vcl_recv { # Happens before we check if we have this in cache already. # # Typically you clean up the request here, removing cookies you don't need, # rewriting the request, etc. if( req.url ~ "^/test.html$"){ return(pass); } }
再次请求test.html 永远都不会命中。
示例三:对于公共图片资源取消其私有标识,强行设定其缓存时长
vcl_backend_response: if (beresp.http.cache-control !~ "s-maxage") { if (bereq.url ~ "(?i)\.jpg$") { set beresp.ttl = 7200s; unset beresp.http.Set-Cookie; } if (bereq.url ~ "(?i)\.css$") { set beresp.ttl = 3600s; unset beresp.http.Set-Cookie; } }
此时对jpg图片或是css文件不在缓存。
五、VCL 其它示例
示例一:
acl purgers { "127.0.0.0"/8; "192.168.0.0"/16; } sub vcl_purge { return (synth(200, "Purged")); } vcl_recv{ ... if (req.method == "PURGE") { if (!client.ip ~ purgers) { return(synth(405,"Purging not allow for"+client.ip)); } return(purge); } ... }
# 使用curl -X PURGE 指定请求的方法;synth(CODE,REASON_PHRASE);返回状态码,状态原因短语。
示例二、负载均衡
import directors; backend websrv1 { .host .port } backend websrv2 { .host .port } sub vcl_init { new websrvs = directors.round_robin(); websrvs.add_backend(websrv1); websrvs.add_backend(websrv2); } sub vcl_recv { set req.backend_hint = websrvs.backend(); ... }
此时需要访问不能缓存的test.html,可以看出页面根据算法RR执行交替显示后端对应请求内容。
示例三、对后端主机做状态检测
varnish对于后端主机,需要自动取消无效的服务器,服务器再次有效时,重新加入后端主机组内
backend websrv1 { .host = "192.168.0.131"; .port = "80"; .probe = { .url = "/"; .interval = 1s; .window = 8; # 检测次数 .threshold = 5; # 最少成功次数 .timeout = 2s; # 超时时间 } backend websrv1 { .host = "192.168.0.133"; .port = "80"; .probe = { .url = "/"; .interval = 1s; .window = 8; # 检测次数 .threshold = 5; # 最少成功次数 .timeout = 2s; # 超时时间 }
此时停止133服务器的http服务,varnish将不再调度请求到133主机;
当133主机在启动http服务,服务再次生效后,varnish 再次将请求发送给133服务器。