缓存的重要性:
什么是缓存:
cache,电脑中为[高速缓冲存储器],是位于[CPU]和[主存储器]DRAM(Dynamic Random Access Memory)之间,规模较小,但速度很高的存储器,通常由[SRAM](Static Random Access Memory 静态存储器)组成。它是位于CPU与内存间的一种容量较小但速度很高的存储器。CPU的速度远高于内存,当CPU直接从内存中存取数据时要等待一定时间周期,而Cache则可以保存CPU刚用过或循环使用的一部分数据,如果CPU需要再次使用该部分数据时可从Cache中直接调用,这样就避免了重复存取数据,减少了CPU的等待时间,因而提高了系统的效率。
说白了就是把经常使用的数据放进缓存,由于缓存在CPU中比内存还快,当下次使用时直接从缓存提取即可,这样就减少了等待时间所以就很快,那么web的集群我们经常访问的一些静态页面或者说一些不经常变化的动静态数据都可以进行缓存,由于缓存系统特殊的管理机制,访问效率会较于之前高出很多,这样就可以有效的提高访问效率,减少后端web集群的访问压力
为什么要用缓存:
当访问量与web集群自身可以服务的数量,二者形成较大差距时就需要引入缓存来缓解后端压力,当然也不是所有数据都可以引入缓存,因为缓存意味着短时间内被缓存数据将不会被发生变化,如果被缓存数据仍然极高频度的发生变化那么缓存只会徒劳增加网络IO,那么合适的场景下引用缓存将提升网站响应速度,减少后端web集群的访问压力能够有效的提升并发能力
引用缓存前的模样:
用户 --> nginx --> php --> mysql
这里没有缓存每一次都会进行一次完整的访问
引用缓存后的模样:
用户 --> cache --> nginx --> php --> mysql
引入缓存后,用户请求的数据如果cache中有将不会进行访问nginx,立即返回给用户
web集群中如何用缓存:
web缓存方案一般常见的就是varnish和squid,当然nginx也算不过不是专业的缓存,毕竟要依赖第三方模块,而前者则是比较成熟的专业的方案选择,但是现在比较推崇的是varnish不过也是有原因的,以挪威一家报社的经验,3台varnish可以抵12台squid的性能...
Varnish是一款高性能的开源HTTP加速器(其实就是带缓存的反向代理服务),可以把http响应内容缓存到内存或文件中,从而提高web服务器响应速度。与传统的 squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点,很多大型的网站都开始尝试使用 varnish 来替换 squid,这些都促进 varnish 迅速发展起来。
既然varnish可以理解反向代理那么一些反向代理的功能他同样是具备的:除了自身的缓存管理以外,同样支持多后端主机的调度,cookie会话保持,读写分离和健康状态监测等等
varnish的组成结构:
Varnish是一款高性能的开源HTTP加速器(其实就是带缓存的反向代理服务),可以把http响应内容缓存到内存或文件中,从而提高web服务器响应速度。与传统的 squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点,很多大型的网站都开始尝试使用 varnish 来替换 squid,这些都促进 varnish 迅速发展起来。[挪威]最大的在线报纸 Verdens Gang 使用3台Varnish代替了原来的12台Squid,性能比以前更好。
-
主要由核心守护进程varnishd组成
-
Manager Process ## 管理进程,相当于nginx的主控进程,不处理用户请求,提供用户CLI接口用于管理
-
Cacher Process ## 缓存进程
- 线程Storage:完成缓存存储管理
- 线程Log/Stats:日志记录----->存入共享内存Shared Memory Log中
- 线程Worker threads:真正处理用户请求,通过线程池来定义,最大并发(线程池*线程池最大并发)
-
shared memory log
- varnishlog:读取日志文件,保存在磁盘中
- varnishstat:读取统计数据,计数器
-
VCL配置接口:varnish配置语言,连接Manager Process
- varnishadm:让varnish加载新配置文件
-
VCC Process:varnish的c编译器
整体流程:
用户通过varnishadm工具连接Manager Process的CLI接口来配置管理,完成后由VCC Process进行C-ompiler编译成Shared Object(共享对象)提供给Worker threads(工作线程)来使用,再后来就是一些常规日志处理(环形处理)
Varnish的请求处理流程:
varnish工作在集群的最前端,对于用户的请求大概要经过这么一个流程进行判断,所有以椭圆标注为varnish的引擎,这里很关键,因为后续的策略就是要在指定的引擎中配置
原理图:
请求到达vcl_recv进行vcl_hash判断若可以命中到缓存vcl_hit则直接交付vcl_deliver,否则进入vcl_miss未命中状态,进入后端获取vcl_backend_fetch,进行read beresp(header)读请求头判断,正常则进行vcl_backend_response响应(此时会去后端访问数据),响应后判断是否进行缓存,若缓存则cache,否则do not cache进入vcl_deliver(交付),完成请求。
-
vcl_recv收到请求,查找vcl_hash
-
若命中(传递值hit),交由vcl_hit
-
hit命中,直接从缓存中响应,交由vcl_deliver投递给客户端
-
未命中(传递值miss),交由vcl_miss
-
交由vcl_backend_fetch请求后端服务器 vcl_hash -(miss)-> vcl_miss --> vcl_backend_fetch --> vcl_backend_response --> vcl_deliver(中间判断是否对数据进行缓存)
- vcl_hit和vcl_miss也能交由给pass
-
-
若要删除缓存项(传递值purge),交由vcl_purge - 交由vcl_synh管理缓存,删除对应缓存 - vcl_hash -(purge)-> vcl_purge --> vcl_synth
-
若不能理解请求(传递值pipe),交由vcl_pipe,请求被直接送至后端服务器 - vcl_hash -(pipe)-> vcl_pipe
-
两个特殊引擎:
- vcl_init:在处理任何请求之前要执行的vcl代码:主要用于初始化vMODS
- vcl_fini:所有的请求都已经结束,在vcl配置被丢弃时调用,主要用于清理vMODS
如果还不明白则可以参照大表哥的汉化手绘版本,不过没有官方的详细
参考链接:
varnish的三种缓存存储机制( Storage Types):
· malloc[,size]
内存存储,[,size]用于定义空间大小;重启后所有缓存项失效;
· file[,path[,size[,granularity]]]
磁盘文件存储,黑盒;重启后所有缓存项失效;
· persistent,path,size
文件存储,黑盒;重启后所有缓存项有效;实验;
varnish安装基本使用
安装包已由epel源提供,目前Centos7上提供为4.0版本
[root@localhost ~]# yes|yum install varnish
...
软件包 varnish-4.0.5-1.el7.x86_64 已安装并且是最新版本无须任何处理
官方网址:http://varnish-cache.org/
4.0手册:http://book.varnish-software.com/4.0/
varnish的主配置文件:
[root@localhost ~]# ls /etc/varnish/
default.vcl ##配置varnish缓存策略配置文件
secret ##证书文件,在CLI配置时需要指定该文件验证
varnish.params ##关于varnish程序自身的管理,比如端口...
varnish类似于iptables,对用户的请求层层过滤,每一层都要进行策略匹配,CLI配置工具varnishadm
配置方式一(交互式):
[root@localhost ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
配置方式二(指令式):
[root@localhost ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 vcl.load t4 /etc/varnish/default.vcl
varnishadm介绍:
程序选项:/etc/varnish/varnish.params文件
-a address[:port][,address[:port][...],默认为6081端口;
-T address[:port],默认为6082端口;
-s [name=]type[,options],定义缓存存储机制;
-u user
-g group
-f config:VCL配置文件;
-F:运行于前台;
...
运行时参数:/etc/varnish/varnish.params文件, DEAMON_OPTS
DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300"
-p param=value:设定运行参数及其值; 可重复使用多次;
-r param[,param...]: 设定指定的参数为只读状态;
重载vcl配置文件:
~ ]# varnish_reload_vcl ##也可以在交互式中load-->use
帮助信息:
[root@localhost ~]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
varnish> help ##获取帮助
help []
ping []
auth
quit
banner
status
start
stop
vcl.load ##从default.vcl中加载配置并定义版本标签,不同版本标签在不重启服务可切换使用
vcl.inline
vcl.use ##切换当前使用vcl文件,必须先load
vcl.discard ##删除某个版本配置文件标签
vcl.list ##查看当前可切换的配置文件标签
param.show [-l] [] ##查看varnish程序的当前运行参数
param.set ##不停止服务修改程序运行参数,重启服务后失效
panic.show
panic.clear
storage.list ##查看当前存储状态信息
vcl.show [-v] ##查看当前vcl配置信息,加参数v显示缺省配置
backend.list [] ##查看后端主机列表及健康状态信息
backend.set_health ##设置后端主机健康状态信息(Sick|Healthy)
ban [&& ]... ##缓存清理工具,不过还有其他方法
ban.list
varnish> help vcl.load ##获取vcl.load帮助
varnish> vcl. ##自动补全提示
vcl.discard vcl.inline vcl.list vcl.load vcl.show vcl.use
VCL中的内置变量
内建变量:
req.*:request,表示由客户端发来的请求报文相关;
req.http.*
req.http.User-Agent, req.http.Referer, ...
bereq.*:由varnish发往BE(backend)主机的httpd请求相关;
bereq.http.*
beresp.*:由BE主机响应给varnish的响应报文相关;
beresp.http.*
resp.*:由varnish响应给client相关;
obj.*:存储在缓存空间中的缓存对象的属性;只读;
常用变量:
req.*:
req.http.HEADERS
req.request:请求方法;
req.url:请求的url;
req.proto:请求的协议版本;
req.backend:指明要调用的后端主机;
req.http.Cookie:客户端的请求报文中Cookie首部的值;
req.http.User-Agent ~ "chrome"
bereq.* :
bereq.http.HEADERS
bereq.request:请求方法;
bereq.url:请求的url;
bereq.proto:请求的协议版本;
bereq.backend:指明要调用的后端主机;
resp.*:
resp.http.HEADERS
resp.status:响应的状态码;
resp.proto:协议版本;
resp.backend.name:主机的主机名;
resp.ttl:主机响应的内容的余下的可缓存时长;
beresp.* :
beresp.http.HEADERS
beresp.status:响应的状态码;
reresp.proto:协议版本;
beresp.backend.name:BE主机的主机名;
beresp.ttl:BE主机响应的内容的余下的可缓存时长;
obj.*
obj.hits:此对象从缓存中命中的次数;
obj.ttl:对象的ttl值
server.*
server.ip:varnish主机的IP;
server.hostname:varnish主机的Hostname;
client.*
client.ip:发请求至varnish主机的客户端IP;
注:同样可以自定义变量
set 自定义变量
unset 取消设置
操作符:
==, !=, ~, >, >=, <, <=
逻辑操作符:&&, ||, !
变量赋值:=
varnish常见配置场景:
添加报头在响应信息中判断是否命中:
举例:obj.hits是内建变量,用于保存某缓存项的从缓存中命中的次数;我们添加在响应报文中用来判断是否从命中缓存
if (obj.hits>0) {
set resp.http.X-Cache = "HIT via" + " " + server.ip + obj.hits; ##HIT表示未命中,提供cache服务器地址,命中次数
} else {
set resp.http.X-Cache = "MISS from " + server.ip + obj.hits;
}
这里等于添加了一个自定义报头信息(X-Cache可自定义),来判断是否命中以及命中服务器并且显示次数
示例1:强制对某类资源的请求不检查缓存,定义在vcl_recv中;:
vcl_recv {
if (req.url ~ "(?i)^/(login|admin)") {
return(pass);
}
}
示例2:对于特定类型的资源,例如公开的图片等,取消其私有标识,并强行设定其可以由varnish缓存的时长; 定义在vcl_backend_response中;
if (beresp.http.cache-control !~ "s-maxage") {
if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)$") {
unset beresp.http.Set-Cookie;
set beresp.ttl = 3600s;
}
}
示例3:定义在vcl_recv中;
if (req.restarts == 0) {
if (req.http.X-Forwarded-For) {
set req.http.X-Forwarded-For = req.http.X-Forwarded-For + "," + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
varnish关于缓存裁剪:
缓存裁剪的四种方法:
方法一:PURGE
在vcl中默认有一个状态引擎purge,需要在req.recv中添加判断,对请求的信息url如果是PURGE这种请求则直接进行缓存清理,此访问的url请求强制缓存失效并清理
sub vcl_purge {
return (synth(200, "Purged"));
}
配置方法:##由于缓存清理安全性,限定来源IP来控制缓存清理这个敏感操作
vcl 4.0;
acl purgers { ##定义可操作的ip或段
"127.0.0.1";
"192.168.2.0"/24;
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purgers) {
return (synth(405, "Purging not allowed for " + client.ip));
}
return (purge);
}
}
注:curl -X 指定请求类型
curl -I 只打印
方法二:BAN
banning是一种非常灵活的缓存清理方法,可以在console中使用,也可以定义在配置文件中像PURGE一样
ban req.url ~ ^/news
ban req.url ~ ^/news && req.http.host "ilinux.io"
ban req.url ~ .js$ && req.http.host "ilinux.io"
ban req.url == /
写入配置文件中
sub vcl_recv {
if (req.method == "BAN") {
if (!client.ip ~ purgers) {
ban("req.http.host == " + req.http.host + " && req.url == " + req.url);
# Throw a synthetic page so the request won't go to the backend.
}
return(synth(200, "Ban added"));
}
}
注:其实就是类似于ban req.http.host == 192.168.2.128 && req.url == /javascripts
方法三:判断强制不访问缓存
vcl_recv {
if (req.url ~ "(?i)^/(login|admin)") {
return(pass);
}
}
Varnish的后端主机管理:
添加多台后台主机:
添加多台主机需要借助directors模块,这里添加的步骤为
导入调度模块并定义主机-->实例化主机组对象算法-->实例化对象添加主机-->vcl_recv调用主机组
实例化及各种定义过程应在vcl_recv与语法声明之间完成
vcl 4.0; ##语法声明
import directors; ##倒入调度器模块
probe www { #定义健康状态
.url="/"; ##检测时要请求的URL,默认为”/";
.timeout=1s; ##超时时间
.interval=1s; ##重新健康状态检测间隔
.window=8; ##基于最近的多少次检查来判断其健康状态;
.threshold=5; ##最近.window中定义的这么次检查中至有.threshhold定义的次数是成功的;
}
backend one {
.host = "192.168.2.129";
.port = "8080";
.probe = www; ##调用健康检查策略,主机不同可调用不同的健康判断策略
}
backend two {
.host = "192.168.2.130";
.port = "8080";
.probe = www;
}
backend three {
.host = "192.168.2.131";
.port = "8080";
.probe = www;
}
backend default {
.host = "192.168.2.129";
.port = "8080";
}
sub vcl_init {
new websrv = directors.round_robin(); ##实例化主机组对象算法(轮询,但不支持权重)
websrv.add_backend(one); ##添加主机
websrv.add_backend(two);
websrv.add_backend(three);
new appsrv = directors.random(); ##实例化主机组对象算法(随机)
appsrv.add_backend(one,10);
appsrv.add_backend(two,5);
}
sub vcl_recv {
set req.backend_hint = appsrv.backend(); ##在请求开始的最前端声明主机组,或动静分离的声明
...
}
动静分离配置
配置动静分离的前提是架构已经将静态页面和动态页面拆分开来,这里我们在配置中定义两个主机群组
vcl 4.0; ##语法声明
import directors; ##倒入调度器模块
probe www { #定义健康状态
.url="/"; ##检测时要请求的URL,默认为”/";
.timeout=1s; ##超时时间
.interval=1s; ##重新健康状态检测间隔
.window=8; ##基于最近的多少次检查来判断其健康状态;
.threshold=5; ##最近.window中定义的这么次检查中至有.threshhold定义的次数是成功的;
}
backend sta_1 {
.host = "192.168.2.129";
.port = "8080";
.probe = www; ##调用健康检查策略,主机不同可调用不同的健康判断策略
}
backend php_1 {
.host = "192.168.2.130";
.port = "8080";
.probe = www;
}
backend php_2 {
.host = "192.168.2.131";
.port = "8080";
.probe = www;
}
backend default {
.host = "192.168.2.129";
.port = "8080";
}
sub vcl_init {
new stasrvs = directors.round_robin(); ##实例化主机组对象算法
stasrvs.add_backend(sta_1); ##添加主机
new phpsrvs = directors.hash(); ##实例化主机组对象算法(对)
phpsrvs.add_backend(php_1,1); ##主机,权重
phpsrvs.add_backend(php_2,1);
}
sub vcl_recv {
if (req.url ~ "(?i)\.(css|js|jpg|jpeg|png|gif)$" { ##如果为列表中结尾则发送到stasrvs后端查询数据
set req.backend_hint = stasrvs.backend();
} else { ##不满足上一个条件判定为动态内容,根据cookie进行查询动态主机并进行服务
set req.backend_hint = phpsrvs.backend(req.http.cookie);
}
}
健康状态检查
backend BE_NAME {
.host =
.port =
.probe = {
.url=
.timeout=
.interval=
.window=
.threshold=
}
}
.probe:定义健康状态检测方法;
.url:检测时要请求的URL,默认为”/";
.request:发出的具体请求;
.request =
"GET /.healthtest.html HTTP/1.1"
"Host: www.magedu.com"
"Connection: close"
.window:基于最近的多少次检查来判断其健康状态;
.threshold:最近.window中定义的这么次检查中至有.threshhold定义的次数是成功的;
.interval:检测频度;
.timeout:超时时长;
.expected_response:期望的响应码,默认为200;
健康状态检测的配置方式:
(1) probe PB_NAME { } ##建议采用该方法,比较清晰
backend NAME {
.probe = PB_NAME;
...
}
(2) backend NAME {
.probe = {
...
}
}
varnish的并发配置:
关于并发这块的配置,其实和web服务器差不多,只不过每个产品的名称不一样罢了,这里简单介绍一下并发设置线程池的一些配置:
声明:属于varnish.params的一些参数,所以配置方式有两种:
修改配置文件(需要重启服务,永久有效)
在varnish.params中的DAEMON_OPTS后追加
DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300"
在CLI接口中配置(服务重启失效)
线程池相关:
线程相关的参数:使用线程池epoll机制管理线程;在线程池内部,其每一个请求由一个线程来处理; 其worker线程的最大数决定了varnish的并发响应能力;
官方资料:
最大并发连接数 = thread_pools * thread_pool_max
thread_pools:工作线程池数量,最好小于或等于CPU核心数量;
thread_pool_max:每线程池的最大线程数;
thread_pool_min:额外意义为“最大空闲线程数”;
thread_pool_timeout:线程空闲阈值。当线程处于空闲并且大于thread_pool_min值时多久进行清理,缺省300s;
thread_pool_add_delay:创建线程犹豫时间,缺省0s;
thread_pool_destroy_delay:在没有请求时清理空闲线程的犹豫时间,在犹豫期间内防止突然并发上升,毕竟线程创建一下子成本也很高,缺省1s;
计时器相关:
** send_timeout:发送客户端连接超时。如果在这么多秒内没有传输HTTP响应,则会话关闭。当反代客户端多久不响应varnish服务端,服务端关闭本次会话。缺省600s;
*** timeout_idle:客户端连接的空闲超时,这里指的是keep-alive会话保持,缺省5s;建议设置时间可以加大,这样可以提高套接字复用,减少三次握手提升性能
timeout_req: 接收客户端请求报文首部的最长时间。缺省2s;
cli_timeout:使用varnishadm连接CLI的会话超时时间。
varnish日志相关:
varnish的日志在内存中环形记录,是实时显示的,默认是不进行落地记录的,如果需要记录则需要额外配置,或者epel进行yum安装的需要启动另外的systemd服务才会进行记录。这一块不做仔细说明,因为varnish一般承载的都是大的流量访问,如果一旦开启日志记录,磁盘io会很快成为瓶颈,需要配置可以参考写的非常详细:
[root@localhost ~]# rpm -ql varnish|grep service
/usr/lib/systemd/system/varnish.service ##服务主程序
/usr/lib/systemd/system/varnishlog.service ##varnishlog 用于记录varnish 自身定义的日志格式
/usr/lib/systemd/system/varnishncsa.service ##varnishncsa 用于记录作类似apache/ncsa定义的日志格式
实时日志的查看:
varnishstat
varnishstat实用程序显示正在运行的varnishd(1)实例的统计信息。
参考:
1、varnishstat - Varnish Cache statistics
-1 ##因为是享top一样实时显示,-1表示打印成静态进行显示输出
-1 -f FILED_NAME ##varnishstat统计信息较多,但是可以分类,比如MAIN.*和MEMPOOL.*等等
-l:可用于-f选项指定的字段名称进行列表显示;
MAIN.cache_hit ##查看命中次数
MAIN.cache_miss ##查看未命中次数
# varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss
显示指定参数的当前统计数据;
# varnishstat -l -f MAIN -f MEMPOOL
列出指定配置段的每个参数的意义;
varnishtop
varnishtop实用程序读取varnishd共享内存日志,并显示最常出现的日志条目的持续更新列表。与使用合适的滤波-I,-i,-X 和-x的选择,它可以被用来显示要求的文件,客户端,用户代理,或被记录在日志中的任何其他信息的等级。
2、varnishtop - Varnish log entry ranking
-1 Instead of a continously updated display, print the statistics once and exit.
-i taglist,可以同时使用多个-i选项,也可以一个选项跟上多个标签;
-I <[taglist:]regex>:对指定的标签的值基于regex进行过滤;
-x taglist:排除列表
-X <[taglist:]regex>:对指定的标签的值基于regex进行过滤,符合条件的予以排除;
varnishlog和varnishncsa
两种日志格式也没的说,一般开启varnishncsa
varnishlog参考链接:
varnishncsa参考链接: