一、序言
缓存服务器是什么?
缓存在计算机系统中是用于减少处理器访问内存所需平均时间的部件。而在互联网系统中,缓存服务器是能够将用户所需要的资源不需要交由后端服务器去进行处理直接响应给用户的服务器,从而能够让用户能够更加快速的接收到资源,还能够给后端的服务器减轻负载。所以,缓存服务器在网站架构中是非常重要的一环,有着“缓存为王”的说法。
因为程序的局部性,所以导致缓存成为可能。这个局部性分为时间局部性与空间局部性
- 时间局部性:过去访问的程序在未来的时间可能会再次被访问到
- 空间局部性:一个数据被访问到了,那么离它较近的数据也可能会再次被问到
在缓存中,命中率是有效的判断缓机制是否合理的一个衡量标准。命中率又分为两种:
- 文档命中率:无论是图片还是文本文件统称为文档。从文档个数进行衡量
- 字节命中率:文档的内容很大的时候,缓存命中大量的字节。从内容大小进行衡量
而在缓存服务器中最著名的有两个软件:squid 和 varnish。squid 相当于 web 服务器领域中的 Apache,varnish 则相当于 Nginx 。这里我们主要说说 varnish 。
二、HTTP 协议中关于缓存的知识
缓存控制机制
1、Expires:基于时间控制的,表示一个内容被缓存之后的新鲜度的有效期限。这是一个固定的时间,但是可能服务器端与客户端的时间不一样,导致缓存提前失效
2、Cache-Control: 基于缓存控制,缓存控制有众多选项
请求(cache-request-directive):
no-cache:不要缓存的实体,要求现在从WEB服务器去取
max-age:只接受 Age 值小于 max-age 值,并且没有过期的对象,也就是说这个资源定义的 max-age 是三个小时,Age 是两个小时,那么这个时候客户端能够接受这个缓存的资源。但是如果 Age 变成了四个小时了,而这个时候 Age 要比 max-age 大,那么客户端就不会接受这个缓存。这个前提是缓存没有失效
max-stale:可以接受过期的对象,但是过期时间必须小于 max-stale 值。也就是这个缓存对象已经过期了,但是与后端的服务器联系不上了,所以缓存服务器将这个缓存响应给了客户端
min-fresh:接受其新鲜生命期大于其当前 Age 跟 min-fresh 值之和的缓存对象。如果 Age 是三个小时,min-fresh 是一个小时,那么能够接受这个缓存为四个小时,只要这个缓存不超过四个小时客户端都接受
响应(cache-response-directive):
public: 可以用 Cached 中内容回应任何用户;表示这是一个公共的缓存对象,谁都可以使用。 可存缓存于公共缓存中对象
private:只能用缓存内容回应先前请求该内容的那个用户,表示这是一个私有缓存,只能响应给特定用户。这也就表示用户的私有信息是可以被缓存的
no-cache:可以缓存,但是只有在跟WEB服务器验证了其有效性后,才能返回给客户端
max-age:本响应包含的对象的过期时间
no-store:不允许缓存,请求响应都可以使用的
新鲜度检测机制
对于HTTP/1.0与HTTP/1.1,有两种不同的检测机制
1、过期日期机制:
HTTP/1.0-Expires
HTTP/1.0 协议使用的是 Expires 缓存机制
Expires: Tue,17 Oct 2017 11:06:27 GMT
这是服务器指定的缓存有效期限,如果超过这个期限,则缓存失效
但是这有一个缺陷:如果客户端的时间比服务器端的时间要快,且快的时间超过了缓存时间,那么对于客户端来说,缓存永远是过期的。
Expires:-1
这是在浏览器中的响应头部查看到的。表示不缓存
HTTP/1.1-Cache-Control
HTTP/1.1 使用了Cache-Contorl机制,就是告诉客户端这个网页能够存活多长时间
Cache-Control:private, max-age=0
这是HTTP/1.2协议使用的机制,表示不缓存
2、有效性再验证:revalidate
当浏览器在访问过一个网站之后,会将其中的一些静态资源进行缓存,等到第二次使用到缓存中的资源的时候会向浏览器发送一个条件式请求
(1) 如果原始内容未发生改变,则仅响应首部(不附带body部分),响应码304(Not Modified)
(2) 如果原始内容发生改变,则正常响应,响应码为200
(3) 如果原始内容小时,则响应404,此时缓存中的cache object也应该被删除
条件式请求首部
If-Modified-Since: 基于请求内容的时间戳作验证。验证就是修改的时间戳是否一致,如果不一致那么则服务器端将最新的响应过来
注意:基于时间戳进行验证有一个缺陷,时间戳最短只能记录1s钟,如果某一个网站的页面在不到1s钟的时间发生了改变,这个时候该资源的时间戳是不会发生改变的。那么浏览器再次向服务器请求该资源是否发生了改变,那么服务器端会响应一个304的响应码。所以这个时候会使用到 If-None-Match
If-Match:如果匹配
If-None-Match: 基于Etag进行判断
Etag 就是一个随机的特征码,浏览器向服务器端验证这个特征码是否发生了改变,如果改变了,那么则将发生改变的资源响应过来
当使用有效性再验证时,浏览器发送的请求首部最常用的通常是 If-Modified-Since 与 If-None-Match
三、varnish 简介
varnish 是使用 “键-值”存储,键肯定是放置在内存中的。而值根据用户定义,可能放置在内存中,也可能放置在磁盘上。
1、varnish 的结构
Management:varnish的管理进程
Command line:能够在命令行的方式下接受用户的请求
Child processs mgmt:能够管理各个子进程
Initialization:初始化 varnish
Child/cache: 子线程
Command line:能够在命令行的方式下接受用户的请求
Storage/hashing:每个存储都是转换成 hash 方式键值存储
Log/stats:用来记录日志并统计各种数据
Accept: 接受新的连接请求
Backend communication:如果缓存没有命中,那么则需要将自己扮演成客户端去后端服务器上取得数据
worker threads:处理用户请求
Object expiry:清理缓存中的过期对象
2、varnish如何存储缓存对象
基于文件的缓存。varnish会将内容缓存在单个文件中,并在单个文件内部组织成一个varnish文件系统。由于这个是缓存在磁盘上的,所以
必然会产生大量的磁盘I/O,导致性能下降。这个方式有一个好处,就是在缓存较大的对象的时候性能会比较好。基于内存的缓存,将所有的缓存内容缓存至内存中。这样性能会好很多。在这里不建议给 varnish 划分太大的内存空间,因为 varnish 需要不断的进行创建新的缓存,将过期缓存清理,久而久之整个内存空间会千疮百孔。
基于文件的持久存储。可以重启缓存也不会丢失。但是不稳定,在 varnish 第三版本之前一直都是实验型项目,尽量不要使用
所以,在使用 varnish 的时候一般采用基于file的缓存机制,加上固态硬盘
3、varnish 的工作流程
vcl的状态引擎:
state engine:各引擎之间存在一定程度上的相关性,前一个engine如果可以有多种下游engine,则上游engine需要用return指明要转移的下游engine
vcl_recv: varnish 接受请求
vcl_hash: 对请求的 URL 进行 hash
vcl_hit:判断是否命中
vcl_miss:判断是否没命中
vcl_fetch:去后端取数据
vcl_deliver:封装报文
vcl_pipe:当前端请求到达vcl_recv后,如果前端请求的不是一个http请求,是一个无法理解的请求,那么则需要该函数将请求直接扔给后端服务器
vcl_pass: 不能够缓存的请求直接交给 vcl_fetch 引擎
vcl_error:当前端请求到达vcl_recv后,经过判断发现此IP是一个经常攻击服务器的IP,则直接合成一个error信息,返回给此IP。
如果通过判断,发现前端请求的是一个不存在的页面信息, 那么varnish就可以不用将此请求转交给后端服务器。因为后端服务器经过处理后返回的还是一个错误页面,那么varnish何不直接合成一个错误页面交给前端服务器呢?
以下是 varnish-v4 工作流程图,原理与第三版差别不大:
四、varnish 使用方法
配置文件:
/etc/varnish/default.vcl
/etc/varnish/varnish.params
以.vcl结尾的表示varnish工作进程的配置文件,除了可以使用这个配置文件进行配置外,也可以实时进行修改
以.params表示主程序的工作特性的参数
启动之后,默认监听端口:tcp/6081和tcp/6082
6081:监听在服务的端口
6082:监听在管理的端口
配置varnish的三种应用:
1、定义 varnish 主程序的工作特性(Management):varnishd应用程序的命令行参数
监听的socket,使用的存储类型等,额外的配置参数
-p param=value
-r param, param,..:设定只读参数列表
在 CentOS 7 上通过这个文件配置:/etc/varnish/varnish.params
2、定义各子程序的工作特性(Child):可在程序运行中,通过其CLI进行配置
3、定义线程的缓存机制(Cache):vcl , 配置缓存系统的缓存机制,通过编辑配置文件进行修改
/etc/varnish/varnish.params 配置文件选项:
VARNISH_VCL_CONF=/etc/varnish/default.vcl 主配置文件,表示从哪个配置文件中读取VCL参数
VARNISH_LISTEN_PORT=6081 监听的服务端口
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082 监听的管理端口
VARNISH_SECRET_FILE=/etc/varnish/secret 秘钥文件
VARNISH_STORAGE="malloc,256M" 表示缓存类型,这里以 malloc 类型缓存
malloc: 表示malloc缓存类型
256M:表示缓存大小
也可以使用以下格式,表示基于文件缓存,1G表示文件大小
VARNISH_STORAGE="file, /path/to/some/where ,1G"
VARNISH_TTL=120 表示varnish连接后端主机的超时时长
#DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300"
此选项是被注释的,可以自己定义,表示线程池,可以在运行时进行修改
/etc/varnish/default.vcl 配置文件选项
vcl 4.0; 表示支持vcl 4.0
backend default { 表示后端主机的IP与端口
.host = "127.0.0.1";
.port = "8080";
}
varnish 命令行的工作方式(CLI)
(1) varnishadm
这是一个能够连接 varnish 的管理界面命令,一般这样使用
# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
-S:指定密钥文件
-T:指定 IP:port
进入 CLI 之后使用的子命令
ping 查看子进程是否存在
status 查看子进程运行状态
vcl.list 列出当前所有可用的 VCL 配置文件
vcl.load <configname> <filename> 装载 VCL 的配置文件,并取名
vcl.inline <configname> <quoted_VCLstring>
vcl.use <configname> 使用已经装载的 VCL 配置文件
vcl.discard <configname> 删除已经装载的 VCL 配置文件
vcl.list 列出当前所有已经装载的 VCL 配置文件
例如:一次简单的应用
vcl.load test default.vcl 装载 default.vcl ,并命名为 test
vcl.list
200
active 0 boot 系统默认的,在启动 varnish 的时候就启动的一个配置文件,表示处于活动状态
available 0 test 这是手动装载的,表示处于可用状态
vcl.use test 表示使用 test 这个配置文件
200
VCL 'test' now active
vcl.list
200
available 0 boot 当切换之后系统默认的就处于可用状态
active 0 test test 就处于活动状态了
vcl.discard test 删除定义的配置文件
200
vcl.list
200
active 0 boot
param.show 查看 varnish 运行时的参数
param.set thread_pools 4 设置某个参数的值
param.show thread_pools 查看某一个参数的详细情况
panic.show
我们说内核会恐慌,那么如果线程的内存耗尽也会恐慌,那么我们可以使用这个命令查看具体原因
panic.clear 清楚恐慌信息
storage.list 显示当前使用的存储类型
200
Storage devices:
storage.Transient = malloc
storage.s0 = malloc
vcl.show boot 显示 VCL 配置文件的全部内容
backend.list 显示后端服务器
ban 当我们定义一个资源的缓存时间为 2h ,但是一个小时之后这个资源就已经被修改了,那么这个时候我们就可以使用这个命令来清楚缓存。规则还没说
ban.list 显示定义的清理缓存的规则
(2) varnishlog
直接回车,显示日志详情。只有访问的时候才会显示日志
(3) varnishncsa
直接回车,以 combined 的形式显示访问的时候的日志信息
5、varnish 的内置变量
变量种类:
client 客户端
server 服务端,varnish本身
req 客户端向varnish请求
resp varnish向客户端响应
bereq varnish向后端服务器请求
beresp 后端服务器向varnish响应
obj varnish 或者后端真实服务器响应的资源属性
storage varnish 缓存的资源的属性
bereq
bereq.http.HEADERS:由varnish发往backend server的请求报文的指定首部
bereq.request:请求方法
bereq.url:请求的路径
bereq.proto:请求的http的协议版本
bereq.bankup:后端主机
beresp
beresp.proto:后端服务器响应的协议版本
beresp.status:后端服务器响应的状态码
beresq.backend.ip:后端服务器的ip
beresq.backend.name:后端服务器的name
beresq.ttl:后端服务器响应的内容的生存时长
obj
obj.ttl:对象的ttl值
obj.hits:对象的命中次数
五、varnish 的应用
1、强制对某资源的请求,不检查缓存
vcl_recv {
if (req.url ~ "(?i)^/index.html$") { (?i)表示不区分大小写
return (pass);
}
}
vcl_recv {
if (req.url ~ "(?i)^/login" || req.url ~ "(?i)^/admin") {
return (pass);
}
}
对 http://IP/admin/index.html 或者 http://IP/login/index.html 的资源访问不让它从缓存中获得
2、对特定类型的资源取消其私有的cookie标识,并强行设置其缓存时长:
varnish-v4
sub vcl_backend_beresp {
if (beresp.http.cache-control !~ "s-maxage") {
if (bereq.url ~ "(?i)\.jpg$") {
set beresp.ttl = 3600s;
unset beresp.http.Set-Cookie;
}
if (bereq.url ~ "(?i)\.css$") {
set beresp.ttl = 600s;
unset beresp.http.Set-Cookie;
}
}
}
如果某一个资源的首部中的 cache-control 中没有设置 s-maxage(公共缓存时间) 值,如果后端的服务器返回的是一个图片类型的资源或者 css 类型的资源,那么则给该资源设置一个缓存时长,并且如果该页面有 cookie 值,那么则取消 cookie 值.
s-maxage 这是公共缓存的时长,表示这个资源的缓存是在 varnish 中进行缓存的,而不是在浏览器中进行缓存的时长,所以这个时候我们 在浏览器中是看不到这个缓存时长的
这个是在 varnish-v4 中进行定义的,如果我们是在 varnish-v3 中的话那么则要将该代码放置在 “sub vcl_backend_fetch” 中,并且在进 行路径定义的时候不要加上 be
vcl_v3
sub vcl_backend_fetch {
if (beresp.http.cache-control !~ "s-maxage") {
if (req.url ~ "(?i)\.jpg$") {
set beresp.ttl = 3600s;
unset beresp.http.Set-Cookie;
}
if (req.url ~ "(?i)\.css$") {
set beresp.ttl = 600s;
unset beresp.http.Set-Cookie;
}
}
}
3、对缓存进行修剪
(1) purge:
使用 PURGE 这个方法去请求某一个资源,能够将这个资源从缓存中清除
vcl 4.0;
acl purgers { 可以定义在开头的 vcl 4.0 之后
"127.0.0.0"/8;
"172.18.0.0"/16;
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purgers) { PURGE 这个操作很危险,所以要添加此类请求的访问控制 acl purgers
return(synth(405,"Purging not allow for" + client.ip));
}
return(purge);
}
}
sub vcl_purge {
purge 这个域系统有默认定义,所以这里不需要自定义加上 purge 这个域的定义
}
# curl -X PURGE http://IP/a.jpg
这样系统会将 a.jpg 这个缓存资源进行强制清理
purge 只能对某一个资源进行修剪
ban 对某一类资源进行修剪
(2) ban:
ban <field> <operator> <arg>
示例:
ban req.url ~ ^/javascripts
这个命令可以不用在配置文件中进行定义,而是直接在 VCL 的命令行中进行修剪即可
4、对后端服务器做负载均衡:
backend name {
.attribute = "value";
}
.host: BE主机的IP
.port:BE主机监听的PORT
.probe:对BE做健康状态检测
.max_connections:并发连接最大数量
5、后端主机的健康状态监测方式:
vcl 4.0;
probe healthy {
.url = /index.html;
.interval = 2s; 表示每 2s 检测一次
.timeout = 2s; 检测超时时长
.window = 6; 向后端服务器检测多少次
.threshold = 5; 检测成功多少次才能称之为健康
}
backend websrv1 {
.host = "172.18.49.80";
.port = "80";
.proby = healthy;
}
对主机进行人为标记健康状态
在 VCL 命令行模式下进行
(1) backend.set_health srv1 sick
将主机 srv1 标记为 down ,即使检测成功也无法访问
(2) backend.set_health srv1 auto
这表示探测结果由探测机制进行检测,决定状态
(3) backend.set_health srv1 healthy
将主机 srv1 标记为 healthy,即使检测失败也会发往这个主机
设定后端主机的属性:
backend websrv1 {
.host = "172.18.49.80";
.port = "80";
.proby = healthy;
.max_connections = 5000;
}
6、对后端两个主机进行代理
backend websrv1 {
.host = "172.18.49.80";
.port = "80";
.probe = { 这是健康状态查询请求的页面
.url = "/test1.html";
}
}
backend websrv2 {
.host = "172.18.49.81";
.port = "80";
.probe = {
.url = "/test1.html"
}
}
sub vcl_recv {
if (req.url ~ "(?i)\.(jpg|png|fig)$") {
set req.backend_hint = websrv1;
} else {
set req.backend_hint = websrv2;
}
}
7、对后端主机进行负载均衡
import directors;
要定义集群,那么首先要对创建一个新的集群对象,并将所定义的主机加在定义的集群对象中
sub vcl_init {
new mycluster = directors.round_robin(); 定义一个新的集群对象,集群的调度方式是论调
mycluster.add_backend(websrv1);
mycluster.add_backend(websrv2);
}
vcl_recv {
set req.backend_hint = mycluster.backend();
}
基于 cookie 的 session 绑定
sub vcl_init {
new h = directors.hash();
h.add_backend(srv1,1)
h.add_backend(srv2,1)
srv1 表示定义的某台后端的主机
1 表示权重
}
sub vcl_recv {
set req.backend_hint = h.backend(req.http.cookie)
}