目录
- 引言:缓存的概念
- 一、varnish缓存
- 1. 简介
- 2. 总体结构
- 2.1 两个主进程
- 2.2 Varnish的日志
- 2.3 VCL—varnish配置缓存策略的工具
- 二、Varnish的工作模式(more)
- 1. 状态引擎的处理流程
- 2. Vcl内置函数和状态引擎的核心
- 2.1 Vcl内置函数
- 2.2 vcl的配置语法
- 3. Vcl内置变量
- 三、配置与管理工具
- 1. varnish配置文件
- 2. 管理工具(more)
- 2.1 varnishd—启动命令
- 2.2 varnishstat—缓存信息统计
- 2.3 varnishtop—日志条目排名
- 2.4 varnishadm—控制正在运行的Varnish实例
引言:缓存的概念
熟悉一些名词:
- 时间局部性:一个数据被访问过之后,可能很快会被再次访问到;
空间局部性:一个数据被访问时,其周边的数据也有可能被访问到
- 数据缓存:例如MySQL到web应用服务器之间的缓存,服务器缓存的资源是数据缓存
页面缓存:接入层和应用层中间的缓存,服务器缓存的是可缓存的页面,这层就是缓存层
缓存命中率
:hit/(hit+miss),一般高于30%命中率则是正向收益,好的设计系统可以达到80%到95%以上- 字节命中率:按照数据的字节大小来计算命中率
请求命中率:按照请求的数量来计算命中率
- 代理式缓存:客户端访问缓存服务器,缓存服务器没有命中缓存时到后端服务器请求数据,此时它作为反向代理服务器工作,这种类型的缓存服务器叫做代理式缓存
旁挂式缓存:客户端亲自去查询数据库,并且将数据复制给缓存服务器一份,下次先去找缓存服务器,如果没有命中则再去数据库服务器查询,此时这种工作方式的缓存叫做旁挂式缓存,这个客户端叫做胖客户端(smart client)
- private cache:私有缓存,用户代理附带的本地缓存机制
public cache:公共缓存,反向代理服务器的缓存功能
CND:Content Delivery Network 内容投递系统
GSLB:全网均衡调度
缓存有效性判断机制
:- 过期时间
- 条件式验证
- Last-Modified/If-Modified-Since:基于文件的修改时间戳来判别
- Etag/If-None-Match:基于文件的校验码来判别
“过期时间验证”缓存是否失效颗粒度太大,如果页面刚刚缓存应用服务器发生了变化,结果客户端拿到的就是过期数据;从而加入了条件式验证缓存的失效性,每次客户端请求到达缓存服务器,缓存服务器都要拿本地的数据和应用服务器的数据比较时间戳,如果时间戳发生了变化则缓存新的数据;这样虽然粒度小了,但是还是会有问题,如果应用服务器在同一秒页面数据变化了三次,而缓存服务器拿到的是第一份数据,这样还是会发生数据失效的问题;从而又引入了Etag(扩展标记)来标记唯一的页面数据。此时虽然解决了数据失效性的问题,但是每次客户端的请求都要去后端服务器做比较,对缓存和应用服务器都是不小的压力,我们不得不采取折中的解决方案就是“过期时间验证+条件式验证”,将不经常变动的页面做过期时间验证,变动频繁的采用条件式验证。
请求报文用于通知缓存服务如何使用缓存响应请求:
cache-request-directive =
"no-cache" 不能使用缓存系统中的缓存响应我,必须先去应用服务器做缓存验证
"no-store" 不能使用缓存系统中的缓存响应我,必须去应用服务器请求响应我
"max-age" "=" delta-seconds
"max-stale" [ "=" delta-seconds ]
"min-fresh" "=" delta-seconds
"no-transform"
"only-if-cached"
cache-extension
响应报文用于通知缓存服务器如何存储上级服务器响应的内容:
cache-response-directive =
"public" 所有缓存系统都可以缓存
"private" [ "=" <"> 1#field-name <"> ] 仅能够被私有缓存所缓存
"no-cache" [ "=" <"> 1#field-name <"> ],可缓存,但响应给客户端之前需要revalidation,即必须发出条件式请求进行缓存有效性验正
"no-store" ,不允许存储响应内容于缓存中
"no-transform" 不能转换格式
"must-revalidate" 必须重新验证
"proxy-revalidate"
"max-age" "=" delta-seconds 私有缓存最大缓存时长
"s-maxage" "=" delta-seconds 公共缓存最大缓存时长
cache-extension
Web Page Cache解决方案:squid和varnish,它们的关系就像Apache和Nginx
一、varnish缓存
1. 简介
Varnish cache,是一套高性能的开源反向网站缓存服务器(reverse proxy server)、HTTP加速器 ,很多门户网站已经部署了varnish,并且性能要比squid高上许多,甚至比squid还稳定,且效率更高,资源占用更少。
特点:
- Varnish可以使用内存也可以使用硬盘进行数据缓存
- 支持虚拟内存的使用
- 有精确的时间管理机制
- 状态引擎架构:通过特定的配置语言设计不同的语句
- 以二叉堆格式管理缓存数据
优势:
- 稳定性强:将worker单独分开;
- 速度快:采用“page Cache”技术,所有缓存数据直接从内存读取;
- 支持多并发;
- 通过管理端口,使用正则批量清除部分缓存;或者通过页面清;
- 通过fork打开多进程处理
劣势:
- 进程一旦crash或重启,缓存的数据将从内存中完全释放
- 在多台varnish实现负载均衡时,每次请求都会落到不同的varnish服务器中,造成url请求可能会穿透到后
- 劣势解决方案
- a. 在varnish的后端添加squid/nignx代理,这样防止了当varnish缓存被清空时,瞬间大量的请求发往web服务器
- b. 在负载均衡上做url哈西,让单个url请求固定请求到一台varnish服务器上
2. 总体结构
如上图所示,主要进程:Management、Cacher。
2.1 两个主进程
2.1.1 Management进程
Management进程主要实现应用新的配置、编译VCL、监控varnish、初始化varnish以及提供一个命令行接口等。Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。
Manager管理的接口:
- CLI interface :命令行接口
- Telnet interface :telnet接口
- Web interface :Web管理接口
2.1.2 Child/Cacher进程
进程包括多个线程:
- Accept 线程:接收新的连接请求并响应
- Worker 线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;
- Object Expiry 线程:从缓存中清理过期内容;
- Commad line 线程 : 管理接口
- Storage/hashing 线程 :缓存存储
- Log/stats 线程:日志管理线程
- Backend Communication 线程:管理后端主机线程
- Varnish依赖“工作区(workspace)”以降低线程在申请或修改内存时出现竞争的可能性。在varnish内部有多种不同的工作区,其中最关键的当属用于管理会话数据的session工作区。
2.2 Varnish的日志
为了与系统的其它部分进行交互,Child进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个worker线程都使用了日志数据缓存。共享内存日志大小一般为90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。
varnish提供了多个不同的工具如varnishlog、varnishncsa或varnishstat等
来分析共享内存日志中的信息并能够以指定的方式进行显示。
2.3 VCL—varnish配置缓存策略的工具
Varnish Configuration Language(VCL)是varnish配置缓存策略的工具,它是一种基于域(domain specific)的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用set自定义变量、支持if判断语句,也有内置的函数和变量等。
使用VCL编写的缓存策略通常保存至.vcl文件中(默认文件:$varnish_home/default.vcl
),其需要编译成二进制的格式后才能由varnish调用,即编写的.vcl文件由VCL compiler来编译,VCL compiler调用C compiler来编译后由management来读取生效(读取是及时的),编译后management让各Child进程来应用生效(因为编译成sharedobject为各子进程各读取了一份)。事实上,整个缓存策略就是由几个特定的子例程如vcl_recv、vcl_fetch等组成,它们分别在不同的位置(或时间)执行,如果没有事先为某个位置自定义子例程,varnish将会执行默认的定义。
VCL策略在启用前,会由management进程将其转换为C代码,而后再由gcc编译器将C代码编译成二进制程序。编译完成后,management负责将其连接至varnish实例,即child进程。正是由于编译工作在child进程之外完成,它避免了装载错误格式VCL的风险。因此,varnish修改配置的开销非常小,其可以同时保有几份尚在引用的旧版本配置,也能够让新的配置即刻生效。编译后的旧版本配置通常在varnish重启时才会被丢弃,如果需要手动清理,则可以使用varnishadm的vcl.discard命令完成。
(1)Receive 状态,也就是请求处理的入口状态,根据 VCL 规则判断该请求应该是 Pass 或Pipe,或者进入 Lookup(本地查询)。
(2)Lookup 状态,进入此状态后,会在 hash 表中查找数据,若找到,则进入 Hit 状态,否则进入 miss 状态。
(3)Pass 状态,在此状态下,会进入后端请求,即进入 fetch 状态。
(4)Fetch 状态,在 Fetch 状态下,对请求进行后端的获取,发送请求,获得数据,并进行本地的存储。
(5)Deliver 状态, 将获取到的数据发送给客户端,然后完成本次请求。
二、Varnish的工作模式(more)
说在前面:关于“内置函数/状态引擎”这两个名词概念:你只需记住varnish的缓存策略是基于vcl进行编写的
,因此可以叫vcl内置函数/vcl状态引擎,也可以叫做varnish内置函数/varnish状态引擎;“内置函数”与”状态引擎“大致可以理解为同一事物的不同体现,”内置函数“可以在.vcl文件中直接体现,而”状态引擎“是”内置函数“在实现缓存的处理过程的一种抽象,怎么理解就见仁见智吧。
1. 状态引擎的处理流程
上面提到VCL是配置varnish缓存策略的工具,因此缓存的处理流程还是围绕VCL展开。
缓存策略的进行是由vcl状态引擎来实现的,所谓的状态引擎就是当一个用户请求到达后,根据处理逻辑到达不同阶段,在每个阶段中需要做出的处理动作可以理解为这个状态。
- 图椭圆形部分称为
varnish的状态节点
,又称为vcl状态引擎
,还有种叫法为varnish的vcl内置函数
。 - 红色的线条:没有查询缓存/缓存中数据过期/缓存中没有数据 情况下的流程
- 橙黄色的线条:直接将匹配数据通过内置函数vcl_pipe送往后台主机的流程
- 绿色的线条:查询缓存数据命中且数据没有过期/经后台主机返回的数据经被缓存后返回给客户流程
- 蓝色的线条:数据没有命中缓存向后台主机发出查询的流程
- 黑色的线条:数据从后台主机返回给varnish缓存的流程
2. Vcl内置函数和状态引擎的核心
2.1 Vcl内置函数
基于vcl配置的varnish缓存策略,(结合上节)总结其作用如下:
vcl_recv:用于接受和处理请求。当请求到达并成功接收后被调用,通过判断请求的数据来决定如何处理请求。例如如何响应、怎么响应、使用哪个后端服务器等。
vcl_fetch:根据服务器端的响应作出缓存决策,如判断获取的内容来决定是将内容放入缓存,还是直接返回给客户端。
vcl_pipe:对于无法理解的用户请求,将请求直接发往后端主机;
vcl_hash:自定义hash生成时的数据来源
vcl_pass:用于将请求直接传递至后端主机,后端主机在应答数据后将应答数据发送给客户端,但不进行任何缓存。
vcl_hit:从缓存中查找到缓存对象时要执行的操作;
vcl_miss:从缓存中款查找到缓存对象时要执行的操作;
vcl_deliver:将用户请求的内容响应给客户端时用;
vcl_error:在varnish端合成错误响应而时;
因此,其大致流程可分为如下:
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
2.2 vcl的配置语法
(1) //, #, /*comment*/用于注释;
(2) sub $NAME 用于定义函数;
(3) 不支持循环;
(4) 有众多内置变量;
(5) 支持终止语句,没有返回值;
(6) “域”专用语言;
(7) 操作符: =, ==, ~, !, &&, ||
pipe 无法理解请求方法,管道直接送到后台
pass 不查缓存
常用语句:
if else
set name=value
unset name
req.http.HEADER:调用请求报文中http协议的指定的变量
req.request:请求方法
3. Vcl内置变量
太多了,现学现查:
官方连接:https://varnish-cache.org/docs/6.3/reference/vcl.html#varnish-configuration-language
三、配置与管理工具
$varnish_home/default.vcl
:编写缓存策略的核心配置,通过官网学习最新的Varnish子进程的使用。:配置varnish服务启动时,进程的工作特性,例如监听的地址和端口,缓存机制;(注:自”5.0“版本开始,已经没有此参数文件,且官方给出的参考是可以从命令行设置启动的运行参数。$varnish_home/varnish.params
1. varnish配置文件
涉及知识包括:vcl内置函数、常用变量、条件语句等,上文中已做一些基础入门。
需要深入学习请点击进阶传送门
2. 管理工具(more)
2.1 varnishd—启动命令
通过varnishd命令来启动varnish服务:
varnishd -a 0.0.0.0:80 -s malloc,10g -t 2592000 -w 200,4000,300 -h classic,300000 -p thread_pools=10 -p session_linger=100 -p listen_depth=4096 -p lru_interval=3600 -p sess_workspace=9437184 -p http_resp_size=4194304 -p thread_pool_workspace=9437184 -u admin -g admin -f /path/varnish.vcl
下面分析下上面主要的几个参数:
-a 0.0.0.0:80 #设置varnish监听本机80端口的请求
-s malloc,10g #分配10G的内存用于缓存
-t 2592000 #设置缓存对象过期时间为30天
-w 200,4000,300 #设置线程池中最小线程和最大线程数及线程空闲时间
-h classic,300000 #设置hash算法classic,bucket推荐为缓存对象数的10倍默认为16383,simple_list算法不推荐生产环境使用,critbit算法是一个几乎无锁的树
-p thread_pools=10 #设置线程池大小
-p session_linger=100 #让session重用的时间,重用session可提高性能,这个值设的太大如果没有重用就浪费资源,如果太小重用率太低(大家自己权衡设置)
-p listen_depth=4096 #监听队列的深度
-p sess_workspace=9437184 #session工作内存的大小,vcl操作中需要用到这些内存
-p http_resp_size=4194304 #后端请响应允许的最大字节数,这个的内存就是从上面的sess_workspace分配的
-p thread_pool_workspace=9437184 #设置线程池内存大小,vcl中处理请求和响应将用到
-u admin #以admin用户运行varnish服务
-g admin #以admin组运行varnish服务
-f /path/varnish.vcl #指定vcl配置
2.2 varnishstat—缓存信息统计
#显示指定参数的当前统计数据
varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss
#列出指定配置段的每个参数的意义
varnishstat -l -f MAIN -f MEMPOOL
2.3 varnishtop—日志条目排名
-1:打印统计信息一次并退出,而不是持续更新的显示
-i taglist:可以同时使用多个-i选项,也可以一个选项跟上多个标签
-I <[taglist:]regex>:对指定的标签的值基于regex进行过滤
-x taglist:排除列表
-X <[taglist:]regex>:对指定的标签的值基于regex进行过滤,符合条件的予以排除
2.4 varnishadm—控制正在运行的Varnish实例
#登录管理程序
varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
[sleepy↓]