Web Cache
缓存:缓存就是数据交换的缓冲区(cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。由于缓存的运行速度比内存快很多,故缓存的作用就是帮助硬件更快的运行。
程序具有局部性:时间局部性,空间局部性。
缓存存储方式:key-value
Key:访问路径,url,hash
Value:web content
热点数据:局部性的数据;
命中率:hit/(hit+miss)
文档命中率:从文档个数进行衡量;
字节命中率:从内容大小进行衡量
注意:
缓存对象:生命周期;定期清理;
缓存空间耗尽:LRU(最近最少使用)
可缓存,不可缓存(用户私有数据)
缓存处理的步骤:
接收请求 à 解析请求(提取请求的URL及各种首部)à 查询缓存 à 新鲜度检测 à 创建响应报文 à 发送响应 à 记录日志
新鲜度检测机制:
过期日期:
HTTP/1.0 Expires
Expires:Thu,04 Jun 2015 23:38:18 GMT
HTTP/1.1 Cache-Control:max-age
Cache-Control:max-age=600
有效性再验证:revalidate
如果原始内容未改变,则仅响应首部(不附带body部分),响应码304(Not modified)
如果原始内容发生改变,则正常响应 , 响应码为200;
如果原始内容消失,则响应404,此时缓存中的cache object也应该被删除;
条件式请求首部:
If-Modified-Since:基于请求内容的时间戳验证;
If-Unmodified-Since
If-Match:
If-None-Match:
Etag: faiy89345
常用的开源解决方案:squid ,varnish,本文重点介绍varnish;
Varnish:
程序架构图:
管理进程:编译vcl并应用新配置;监控varnish;初始化varnish;CLI接口
Child/cache:
Acceptor:接收新的连接请求;
worker threads:处理用户请求;
Expiry:清理缓存中的过期对象;
日志:Shared Memory Log,共享内存日志大小默认一般为90MB,分为两部分,前一部分为计数器,后一部分请求相关数据;
vcl:Varnish Configuration Language
保存策略配置接口;
基于“域”的简单编程语言;
内存分配和回收:
malloc(),free()
varnish如何存储缓存对象?可以通过以下三种方式;
file:单个文件;不支持持久机制;
malloc:内存
persistent:基于文件的持久存储;
varnish的主要配置文件及工具:
配置varnish的三种应用:
1. Varnish应用程序的命令行参数;
监听的socket,使用的存储类型等等;额外的配置参数;
-p parm=value
-r param,param,…:设定只读参数列表;
/etc/varnish/varnish.params
2. –p选项指明的参数:
运行时参数:
也可在程序运行中,通过其CLI进行配置;
3. vcl:配置缓存系统的缓存机制;
通过vcl配置文件进行配置;
先编译,后应用;
依赖于c编译器;
Note:
1) varnish是一个web代理缓存服务器,既然是代理就要代表原始web服务器工作,所以,接收的用户请求会首先发送到varnish,那这样一来按理说就应该监听在80端口;其实不然,为了防止和本机的80端口起冲突,varnish默认是监听在6081端口的;这是因为虽然varnish为代理缓存服务器,但并不会让其直接面向用户,而是通过前端的nignx或者haproxy将用户的请求反代至varnish,由varnish完成缓存管理,如果本地缓存没有,varnish再去查找原始服务器,如果有就直接返回给前端反代服务器。
2) 6082端口为基于命令行工具去管理和控制varnish的接口,为了安全起见监听在本机的127.0.0.1的地址;
演示:使用varnish代理缓存后端web服务
实验环境:两台服务器,一台作varnish代理缓存服务器,一台作后端服务器;
Ip地址分配:
varnish缓存服务器:192.168.19.203
后端RS:192.168.19.143
1. 配置文件/etc/varnish/varnish.params如下,(一般默认不改变)
2. 编辑/etc/varnish/default.val文件,反向代理至后端服务器;
3. 启动varnish服务并查看端口;
4. 访问网站测试;
Note:后面需要加端口6081,
命令行工具:
varnishadm命令以及vcl配置文件的重载:
重载vcl配置文件:
varnish_reload_vcl
varnishadm命令:
varnishadm -S /etc/varnish/secret -T IP:PORT
vcl:
请求流程图:
state engine:各引擎之间存在一定程度上的相关性;前一个engine如果可以有多种下游engine,则说明engine需要用return指明要转移的下游engine;
vcl_recv
vcl_hash
vcl_hit
vcl_miss
vcl_fetch
vcl_deliver
vcl_pipe
vcl_pass
vcl_error
编程语言语法:
1) //,#,/* */用于注释;会被编译器忽略;
2) sub $name:用于定义子例程;
sub vcl_recv {
}
3) 不支持循环;
4) 有众多内置的变量,变量可调用位置与state engine有密切相关性;
5) 支持终止语句,return(action):没有返回值;
6) 域专用 ;
7) 操作符:=,==,~,!,&&,||
条件判断语句:
if (CONDTION) {
} else {
}
变量赋值:set name=value
unset name
req.http.HEADER:调用request报文中http报文中http协议的指定的HEADER首部
req.http.X-Forwarded-For
req.http.Auhtorization
req.http.cookie
req.request:请求方法
client.ip:客户端IP;
state engine workflow(v3):
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
state engine(v4)
vcl_recv
vcl_pass
vcl_pipe
vcl_hash
vcl_hit
vcl_miss
vcl_backend_fetch
vcl_backend_response
vcl_backend_error
vcl_purge
vcl_synth
https://www.varnish-software.com/book/4.0/chapters/VCL_Basics.html
sub vcl_recv {
if (req.method == "PRI") {
/* We do not support SPDY or HTTP/2.0 */
return (synth(405));
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
if (req.method != "GET" && req.method != "HEAD") {
/* We only deal with GET and HEAD by default */
return (pass);
}
if (req.http.Authorization || req.http.Cookie) {
/* Not cacheable by default */
return (pass);
}
return (hash);
}
定义在vcl_deliver中,向响应给客户端的报文添加一个自定义首部X-Cache
if (obj.hits>0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cahce = "MISS";
}
演示:通过判断想应报文缓存命中的次数来判断是否命中缓存;
1) 编辑配置文件/etc/varnish/default.vcl;
2) 重载配置文件,过程如下;
3) 浏览器测试;
Varnish中的内置变量:
变量种类:
client
erver
req
resp
bereq
beresp
obj
storage
bereq
bereq.http.HEADERS: 由varnish发往backend server的请求报文的指定首部;
bereq.request:请求方法;
bereq.url:
bereq.proto:协议的版本
bereq.backend:指明要调用的后端主机;
beresp
beresp.proto:后端服务器响应的协议版本
beresp.status:后端服务器的响应的状态码
beresp.reason:原因短语;
beresp.backend.ip:后端响应的ip
beresp.backend.name:BE主机的主机名
beresp.http.HEADER: 从backend server响应的报文的首部;
beresp.ttl:后端服务器响应的内容的余下的生存时长;
obj
obj.ttl: 对象的ttl值;
obj.hits:此对象从缓存中命中的次数;
server
server.ip
server.hostname
req
resp
演示1:强制对某类资源的请求不检查缓存,之类以login和admin为例;
1) 提供测试页面环境;
2) 访问测试;
可以看出,第一次没有命中,但是以后都能命中的。
3) 现在我们修改配置文件,在vcl_recv下添加以下内容,强行对此资源不检查缓存;
4) 重新装载配置文件,测试(装载配置文件过程,这里不再多说)
可以看出,这两次访问,都没有击中缓存;
演示2:设定多个后端主机,并分别以负载均衡和动静分离的方式调度;
动静分离方式调度:
1) 提供RS2的php页面测试环境;(注意,要安装httpd+php)
2) 编辑配置文件,添加内容如下;
3) 重载配置文件,并测试;
访问.php文件时:
Note:php右上角的图片没有显示出来,是因为静态资源发往了192.168.19.144这台主机,而这台主机上又没有这张图片。所以显示不出来;
访问html文件时:
负载均衡调度:使用前需要导入:import director
1) 编辑配置文件,首先导入import director模块,内容如下;
2) 重载配置文件,测试;
Note:varnish的负载均衡调度是对请求的不同资源的负载均衡调度,请求统一资源,它只会调度至一开始调度的服务器;
定义健康状态监测;
backend server的定义:
backend name {
.attribute = "value";
}
.host: BE主机的IP;
.port:BE主机监听的PORT;
.probe: 对BE做健康状态检测;
.max_connections:并连接最大数量;
后端主机的健康状态检测方式:
probe name {
.attribute = "value";
}
.url: 判定BE健康与否要请求的url;
.expected_response:期望响应状态码;默认为200;
.request:发出的具体请求
.window:基于最近的多少次检查来判断其健康状态;
.threshhold:最近.window中定义的这么多次检查中至有.threshhold定义的次数时成功的;
.interval:监测额度;
.timeout:超时时长;
演示3:设定varnish对后端主机做健康状态监测;
1) 使用varnishadm命令可以查看后端主机是否做了检测状态监测;
2) 编辑配置文件/etc/varnish/default.vcl,定义健康状态监测的probe;
方法一:
方法二:
3) 重载配置文件,查看后端主机是否开启健康状态检测;
4) 浏览器访问测试看是否能正常访问;
补充:
移除单个缓存对象
purge用于清理缓存中的某特定对象及其变种(variants),因此,在有着明确要修剪的缓存对象时可以使用此种方式。HTTP协议的PURGE方法可以实现purge功能,不过,其仅能用于vcl_hit和vcl_miss中,它会释放内存工作并移除指定缓存对象的所有Vary:-变种,并等待下一个针对此内容的客户端请求到达时刷新此内容。另外,其一般要与return(restart)一起使用。
下面是个在VCL中配置的示例。
acl purgers {
"127.0.0.1";
"192.168.0.0"/24;
}
sub vcl_recv {
if (req.request == "PURGE") {
if (!client.ip ~ purgers) {
error 405 "Method not allowed";
}
return (lookup);
}
}
sub vcl_hit {
if (req.request == "PURGE") {
purge;
error 200 "Purged";
}
}
sub vcl_miss {
if (req.request == "PURGE") {
purge;
error 404 "Not in cache";
}
}
sub vcl_pass {
if (req.request == "PURGE") {
error 502 "PURGE on a passed object";
}
}
客户端在发起HTTP请求时,只需要为所请求的URL使用PURGE方法即可,其命令使用方式如下:
# curl -I -X PURGE http://varniship/path/to/someurl
补充资料:
varnish的线程模型:
cache-worker线程
cache-main线程:此线程只有一个,用于启动caceh;
ban luker:
acceptor:
epoll:线程池管理器
expire:清理过期缓存
varnish定义其最大并发连接数:线程池模型:
thread_pools:线程池个数;默认为2;
thread_pool_max:单线程池内允许启动的最多线程个数;
thread_pool_min
thread_pool_timeout:多于thread_pool_min的线程空闲此参数指定的时长后即被purge;
varnish的param查看及改变:
param.show [-l] [param]
param.set [param] [value]
varnish的命令行工具:
varnishadm,
varnishtop: 内存日志区域查看工具
RxHeader User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 Safari/537.36
其中:
RxHeader:称为tag, 基于tag过滤,可使用-i或-x选项;
User-Agent起始的内容:称为日志信息,可使用-I或-X选项进行过滤;
-I regexp: 仅显示被模式匹配到的条目
-X regexp:仅显示不被模式匹配到的条目
-C: 忽略字符大小写;
-d: 显示已有日志;
varnishstat:
-f field, field, ...
-l: 列出所有可用字段
-x: xml输出格式
-j: json输出格式
varnishlog, varnishncsa