HTTP Web Cache
程序资源的访问具有局部性
时间局部性:一个被访问过的资源很有可能在近期被再次访问。
空间局部性:一个被访问过的资源,它的周边资源很有可能被访问到。
如何衡量缓存的有效性?
衡量一个缓存的有效性,主要标准是命中率,命中率计算指标有两种,一是文档命中率,是指一个uri指定的资源被命中;二是字节命中率,是指缓存命中的数据的大小。
公式:命中率=hit/(hit+miss)。
缓存哪些内容?
数据具有热点性:热点即经常被访问到的数据。一般遵循二八法则,经常被访问的是20%的数据,其他80%不会被经常访问。那么我们的缓存就应该缓存这20%的热点数据,而非缓存所有数据。
注意:用户的私有数据,不应该被服务器端缓存,可以缓存在客户端的浏览器端。
缓存的清理机制
缓存是有生命周期的,例如缓存周期为60秒。超过60秒的缓存向会被视为“过期缓存”,从而被清理
内存空间是有限的,当内存被耗尽时,依据LRU(Least Recently Used的缩写,即最近最少使用)算法清理指定缓存项。
缓存的处理步骤
接收请求--->解析缓存(提取URL和各种首部)--->查询缓存--->缓存命中--->“新鲜度检测”(用来检测缓存是否过期) --->从缓存获取资源构建响应报文--->发送响应报文--->记录日志
提供http cache 的开源软件
squid和varnish,他们的关系类似于httpd和NGINX
缓存控制机制
过期日期:通过Expires和Cache-Control控制
- Expires : HTTP/1.0
- Cache-Control: max-age: HTTP/1.1
单纯使用Expires(过期日期)来控制,存在缺陷。当处于不同时区的用户访问时,因为时间差异,可能造成提供资源为已经过期的。所以在http/1.1版本加入了Cache-Control机制。见下图
Cache-Control
请求报文中为什么还需要缓冲控制项?
请求报文中的缓存控制项是用来告诉服务器自己是否接受缓存内容等信息
cache-request-directive =
"no-cache"
| "no-store" (backup)
| "max-age" "=" delta-seconds
| "max-stale" [ "=" delta-seconds ]
| "min-fresh" "=" delta-seconds
| "no-transform"
| "only-if-cached"
| cache-extension
cache-response-directive =
"public":资源可以放在公共缓存上(公共缓存是指非用户浏览器上的缓存)。
| "private":可以由私有缓存缓存。
| "no-cache":资源可以被缓存,但是在使用缓存时,必须先向服务器端检测资源是否过期。
| "no-store":资源不允许被缓存
| "must-revalidate":必须重新校验, 资源可以被缓存,但是在使用缓存时,必须先向服务器端校验。
| "max-age" " : 缓存有效时长
| "s-maxage":公共缓存资源可缓存时长
http1.0新鲜度检测机制
1st) 用户第一次访问资源,客户请求资源,向缓存服务器查询,无此资源,然后访问服务器获取资源,服务器构建响应报文,并缓存(缓存期限10s)。最后将用户请求资源发送给用户。
2nd) 用户在十秒内再一次请求了此资源,直接使用缓存资源响应客户
varnish
3rd) 10s以后用户又访问此资源,由于缓存已经过期所以还是得向服务器获取资源,并缓存
这种基于Expires的新鲜度检测机制存在,当缓存过期后,而后端资源没有改变,但还得重新去服务器获取资源,并缓存的缺陷。
http1.1新鲜度检测机制
有效性再验证:revalidate
使用缓存响应请求前,先向服务器验证资源是否过期
- 资源没有改变,则仅响应首部,状态码为304(not modified)
- 资源改变,则直接有服务器响应,状态码为200
- 若资源不存在了,则直接响应404,并删除缓存项。
有效性再验证相关的请求首部
基于原始内容的最近一次修改时间
IF-Modified-Since:自从某时间到现在资源是否修改
IF-unmodified-Since:自从某时间到现在资源是否没有修改
基于文件标签进行
有些网站的部分特定资源,其内容可能每秒都在不停的变化,此时可以基于文件的标签。给每个资源添加一个扩展的标签。如果资源变化了,则其标签也随之改变。
IF-Match:如果匹配,则使用缓存。
IF-None-Match:如果不匹配,基于Etag的比较进行
检测机制图解
1st) 第一次访问
2nd) 再次访问时
如果资源没有改变,服务器仅响应一个首部信息(其状态码为304),然后使用缓存,响应客户端
如果资源已改变:服务器直接响应请求状态码200,并添加缓存。
总结:此方法存在,无论缓存是否过期都会去向服务器发送验证请求的缺陷,所以可以和expire(基于时间的检测机制)一起搭配之用,即只有在缓存过期后采取服务器验证资源。
Varnish
官方站点:
www.varniash-cache.org
varnish架构
Management:
command line:命令行接口,常用的有CLI interface、telnet interface、web interface。
child process mgmt:子进程管理
initialisation:初始化,例如家在配置文件,初始化配置等。
VCL compiler:缓存策略配置接口;基于“域”的简单编程语言;初始化过程,还包括调用VCL compiler (varnish control language),它用来配置varnish如何缓存缓存项,例如如何缓存,缓存时间,如何响应用户,而不是用来控制服务如何运行的
C compiler:使用VCL compiler配置完后,需要使用C compiler进行编译,将其编译成一个二进制格式的配置对象--->Shared object
Shared object:可以被各个child子进程读取,用来控制child的运行。
child/cache
command line:命令行接口
storage/hashing:缓存存储和管理
log/stat:日志记录和数据统计
accept:接受用户请求的
backend communication:与后端主机通信,从后端服务器取得缓存内容
worker threads:工作进程
object expiry:资源缓存有效期检测
log file
varnish日志记录。它并不是一个文件,而是一段内存空间(shm--->shared memory),该内存空间大小固定,当写满时,滚动覆盖。
varnishlog:将log file内存中数据归并到磁盘文件中,原生的varnish日志格式
varnishstat:将log file内存中数据,展示分析。
varnishhist:查看历史存储缓存内容
varnishtop:排序,将log file内存中数据排序
varnishncsa:将log file内存中数据归并到磁盘文件中,日志格式类似于http的combined
varnish内存空间
shared memory
child进程为了能与系统其他进程进行交互,提供一个共享的内存区域,通过调用文件系统接口来实现交互。来记录日志此区域被称为共享内存(shared memory log)。如果其他程序需要读取此共享内存中的数据,只需持有次内存的写锁即可。为了减少竞争,每个进程都有各自的缓存,先将数据缓存至各自缓存,然后写入共享内存。
共享内存分为两部分
计数器:记录缓存命中次数等信息
用户请求数据:存储用户请求的数据
varnish缓存内容存储位置。
file:
自管理的文件系统,不同于nginx将缓存按照KV结构存储,varnish将所有缓存存储于一个大文件中,对于用户相当于一个黑盒。需要特别注意的是,虽然将数据存储在磁盘中但是,此文件数据,重启服务器后消失,缓存失效。不能持久保存。
malloc:
使用malloc()库调用,在服务器动是向内存请求指定大小内存空间。非持久保存。
persistent:
文件存储于磁盘中,持久保存。(处于测试期不建议使用(待验证))。
内存
内存缓存的缺点
当使用内存作为缓存空间时,较大空间的内存,会由于缓存管理不当,会造成大量碎片,导致性能下降明显。
varnish无法追踪某缓存项是否存入到了缓存文件中,从而也无法知道下一次启动服务后缓存项是否依然存在,所以缓存无法重启后生效。
对于varnish缓存本身来讲malloc基于内存缓存是,他的内存分配策略,效率很低。对于较大的内存空间,显得很吃力。所以谷歌研发了更加强大的malloc内存分配和回收工具,所以为了提高内存管理效率,可以使用第三发谷歌开发的malloc,以实现高效内存管理。
varnish一般工作模型
varnish一般部署在服务器前端,作为反向代理或负载均衡服务器,。而且在varnish前还可不数nginx做反向代理。
该模型下如何提高varnish缓存命中率?
在nginx上使用lvs时,有个调度方法hash url算法。始终将来请求同一个地址的所有主机都发往同一个varnish缓存服务器,这样在第一个用户访问后被缓存下来,在第二个用户访问同一个资源时就可以命中缓存。
varnish安装
安装epel源:
yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
安装varnish:
yum -y install varnish
varnish安装生成文件(部分)
varnish配置
两个重要配置文件
/etc/varnish/default.vcl
用来配置vcl
详细文档:http://varnish-cache.org/trac/wiki/VCLExamples
/etc/varnish/varnish.params
用来配置varnish服务运行相关配置
/etc/varnish/varnish.params配置文件解读
注意此配置文件中的所有配置,其实都是由命令行指定的。我们可以查看vim /usr/lib/systemd/system/varnish.service。其内容如下图。启动服务的配置文件EnvironmetFile加载的正是/etc/varnish/varnish.params常用配置,都可使用参数指定。要配置额外参数统一使用$DAEMON_OPT,启动服务时可以使用 -p param=value其值可以使用man varnishd查看。
# Varnish environment configuration description. This was derived from
# the old style sysconfig/defaults settings
# Set this to 1 to make systemd reload try to switch VCL without restart.
RELOAD_VCL=1 ##是否开启reload VCL功能。1开启;2关闭。
# Main configuration file. You probably want to change it.
VARNISH_VCL_CONF=/etc/varnish/default.vcl ##VCL配置文件
# Default address and port to bind to. Blank address means all IPv4
# 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服务默认监听地址。
VARNISH_LISTEN_PORT=6081 ##varnish服务默认监听端口。
# Admin interface listen address and port
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 ##varnish管理接口监听地址。
VARNISH_ADMIN_LISTEN_PORT=6082 ##varnish管理接口监听端口。
# 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="malloc,256M"
##varnish存储机制设置。可以使用以下三种
malloc[,size]
file[,path[,size[,granularity]]]
persistent,path,size
# User and group for the varnishd worker processes
VARNISH_USER=varnish ##运行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" 线程池超时时长。
vcl: Varnish Configuration Language
VCL主要作用
允许用户或管理员自定义varnish缓存策略,然后由management,进行分析,转换为二进制代码,并链接至child子进程,从而生效。
vcl定义策略的方式
主要是通过附加在varnish内部的缓存处理机制实现,所以VCL也被称为基于“域”的简单编程语言。也可以理解为一种状态引擎,state engine
状态引擎
vcl存在多个状态引擎,个引擎之间存在相关性,但是彼此之间又相互隔离。每个状态引擎通过reture(x)退出当前状态,并转入下一个状态。
注意不同引擎,它的return(x)不尽相同