HTTP协议缓存和CDN以及varnish

一、简述http协议缓存原理及常用首部讲解

  • cache:缓存

    • 程序的运行具有局部性特征:
      • 时间局部性:一个数据被访问过之后,可能很快会被再次访问到;
      • 空间局部性:一个数据被访问时,其周边也有可能被访问到;
  • cache:命中

    • 热区:经常被访问的数据区域;
    • 缓存命中率:判断缓存的利用率的衡量指标,hit/(hit+miss):取值范围(0,1);
      • 页面命中率:基于页面数量进行衡量
      • 字节命中率:基于页面的体积进行衡量
    • 缓存的生命周期(缓存清理有2种场景):
      • 1.缓存项过期
      • 2.缓存空间用尽:LRU算法(最近最少使用)
    • 缓存与否:
      • 私有数据:private cache,不可缓存
      • 公共数据:public or private cache,可以缓存
  • 缓存处理步骤:

    • 接收请求 --> 解析请求(提前请求的URL及各种首部) --> 查询缓存 --> 新鲜度检测 --> 构建响应报文 --> 发送响应报文 --> 记录日志;
    • 在Web服务器上是通过http协议中的请求和响应首部来定义缓存时间的;
  • http协议缓存的原理:
    基于nginx的反代服务时,为了加速性能,可以开启nginx缓存;如果这nginx为负载均衡器时,还要承担缓存的功能,在高并发下,会面临带宽瓶颈;因此在规模交大时,会在反代服务器后面添加专门用于缓存的服务器,来提供缓存功能。这样让代理功能的服务器只负责代理,让缓存功能的服务器只负责缓存,当前端主机请求资源时,它所指向的上游服务器就不在是真正的服务器,而是缓存服务器,他们之间是通过http请求和http响应报文来通信;因此,代理服务器取资源时缓存服务器如果本地未能命中,会到后端服务器读取数据,取到数据后按照缓存策略是否可缓存,如果可缓存就把数据缓存到本地,并响应给前端主机;如果缓存服务器能命中,则缓存服务器直接响应,省去了到后端读取数据的过程。


    HTTP协议缓存和CDN以及varnish_第1张图片
    http缓存.png
  • 缓存有效性判断机制:

    • 过期时间:Expires

      • HTTP/1.0:Expries;过期
        绝对时长控制机制,有缺陷和局限性,如果时区不同,会有影响;
      • HTTP/1.1
        Cache-Control:maxage=
        Cache-Control:s-maxage=
        相对时长控制机制
    • 条件式请求:

      • Last-Modified/:上次修改时间
      • If-Modified-Since:基于文件的修改时间戳来判别;
      • Etag/If-None-Match:基于文件的校验码来判别;
    • 有些网站的内容,一秒钟就会改变n次,这种极端场景中,缓存依然有可能是不能被命中的;面对这种场景,进行有效性再验证时基于时间戳就不是很有效了;
      这种情况,可根据文件的标签进行验证,给每个页面资源加一个扩展标签Etag,每个资源内容的标记使用校验码,只要内容不变校验码就不变,所以这个扩展标签可认为是一个校验码;常见的请求首部为If-Modified-Since和If-None-Match两种;

        示例: 
        Expires:Thu, 13 Aug 2026 02:05:12 GMT
        Cache-Control:max-age=315360000
                ETag:"1ec5-502264e2ae4c0"
        Last-Modified:Wed, 03 Sep 2014 10:00:27 GMT
      
  • 请求报文用于通知缓存服务如何使用缓存响应请求:
    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":必须做重新校验;内容缓存后,即便命中时必须到后端服务器进行验证,跟no-cache相似;
    | "proxy-revalidate"
    | "max-age" "=" delta-seconds:可缓存有效时间,是相对时长
    | "s-maxage" "=" delta-seconds 公共缓存服务器可缓存时长
    | cache-extension

二、简述回源原理和CDN常见多级缓存

  • 回源原理:
    回源是指浏览器在发送请求报文时,响应该请求报文的是源站点的服务器,而不是各节点上的缓存服务器,那么这个过程相对于通过各节点上的缓存服务器来响应的话就称作为回源。回源的请求或流量太多的话,有可能会让源站点的服务器承载着过大的访问压力,进而影响服务的正常访问。
    • 常规的CDN都是回源的。即:当有用户访问某一个URL的时候,如果被解析到的那个CDN节点没有缓存响应的内容,或者是缓存已经到期,就会回源站去获取。如果没有人访问,那么CDN节点不会主动去源站拿的。
    • 源站内容有更新的时候,源站主动把内容推送到CDN节点。
  • CDN:
  1. CDN:缓存网络,Content Delivery Network,即内容分发网络;加速器,向代理缓存。
  2. 基本思路:尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器器所构成的在现有的互联网基础之上的一层只能虚拟网络,CDN系统能够实现实时的根据网络流量和各个节点的连接、负载状况以及到永固的距离和相应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用胡可就近取得所需内容,解决互联网拥挤状况,提高用户访问网站的响应速度。
  3. 基本原理:广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。
  4. 服务模式:内容分发网络(CDN)是一种新型网络构建方式,它是为能在传统的IP网发布宽带丰富媒体而特别优化的网络覆盖层;而从广义的角度,CDN代表了一种基于质量与秩序的网络服务模式。
    简单地说,内容分发网络(CDN)是一个经策略性部署的整体系统,包括分布式存储、负载均衡、网络请求的重定向和内容管理4个要件,而内容管理和全局的网络流量管理(Traffic Management)是CDN的核心所在。通过用户就近性和服务器负载的判断,CDN确保内容以一种极为高效的方式为用户的请求提供服务。
    总的来说,内容服务基于缓存服务器,也称作代理缓存(Surrogate),它位于网络的边缘,距用户仅有"一跳"(Single Hop)之遥。同时,代理缓存是内容提供商源服务器(通常位于CDN服务提供商的数据中心)的一个透明镜像。这样的架构使得CDN服务提供商能够代表他们客户,即内容供应商,向最终用户提供尽可能好的体验,而这些用户是不能容忍请求响应时间有任何延迟的。
  5. GSLB:全局服务负载均衡器; 调度客户请求到不同的缓存服务器上;
  6. SLB:局部负载均衡器;

三、varnish

官方站点:http://www.varnish-cache.org

1.varnish架构:
HTTP协议缓存和CDN以及varnish_第2张图片
varnish.png
  • Management:主控进程,类似于nginx的master进程
    Management主控进程的主要作用:应用新配置,编译vcl,监控varnish子进程,初始化varnish,以及提供一个命令行接口;一般Management进程,会每隔几秒钟探测child进程是否运行正常,如长时间没没有收到所必须存在的CHild进程的相应,则Management会重启此Child子进程,兼具了watchdog功能

    • command line:负责提供命令行接口,通过命令行程序管理varnish的功能;command line交互接口有三种:
      • CLI interface:专用的命令行接口;
      • Telnet interface:使用telnet,但无法做验证;
      • Web interface:网页图形窗口,是商业版收费使用;
    • child process mgmt:负责管理各种varnish子进程;
    • initialisation:负责初始化缓存空间,加载配置文件,准备缓存目录,还包括调用vcl编译器等;
  • vcl compiler:配置接口
    vcl compiler通过调用外部的C cpmpiler(C编译器)进行编译,这就要依赖gcc编译器,然后生成一个二进制格式的配置对象,这个二进制格式的配置对象可以被各Child和cache子进程所加载读取相关配置;
    所以,每一次修改varnish配置文件,都要手动加载、编译、装载、使用;支持动态装载;

  • Child/cache:缓存线程

    • command line 命令行接口;
    • Storage/hashing 实现缓存管理,做哈希,存储;
    • Log/stats 日志及数据统计;
    • Accept 接收用户请求;
      Accept线程名字叫accepter,主要作用是接收新请求并响应用户请求的进程;
    • Backend communicaton 建立与后端服务器通信的连接;
    • Worker threads 工作线程,负责处理用户请求,例如从缓存加载用户请求内容并响应;
      Worker threads,主要作用是处理,child子进程会为每一个用户请求(如果需要处理),启一个worker线程,所以它单线程,单响应的;即是每一个请求用一个独立的线程响应的,而不像nginx中一个worker响应n个请求;
      整个varnish内部要运行n个线程,同时并发处理n个请求,而每一个服务器的并发能力是有限的,所以,对varnish也要定义并发响应上限;通常使用tread pool线程池进行定义;例如,一个线程池最多可定义2000个线程,如果要想能并发响应6000个请求,那么,可以启动3个线程池;相当于nginx中启动3个worker进程,每个worker进程并发响应2000个请求;但是,对于varnish,内部线程是看不到的,也就是使用ps aux等命令是看不到的;所以,从本质上讲同nginx比较相似;可理解nginx内部也是有线程的,也是看不见的,不是linux管理的独立线程;
  • Object expiry 做缓存数据的有效性验证、清理等;

  • Log file:日志文件
    所有接收用户请求,处理请求等的结果保存在日志文件中;默认,这个日志文件不是磁盘文件而是共享的内存空间SHM;这段内存空间大小的固定的86M,填满后,会滚动覆盖,类似于rrd数据库的工作方式;不能保存太多信息;
    所以,可以基于某些机制来实现保存日志信息:

    • varnishncsa机制:从varnish日志文件中规律性的加载数据,保存在磁盘文件中,日志格式类似于httpd中的combined日志类型;
    • varnishlog机制:也是从log file中加载内容,持久保存在键中,同varnishncsa机制,日志格式是carnish原生的自己日志的格式,内容非常丰富,冗余信息较多;
    • varnishstat:是从log file 中读取数据展示出来做分析,提供统计数据;
    • varnishshtop: 是排序功能,跟top命令一样,把资源访问量等信息,进行统计展示后排序显示;例如进程所占用的内存空间,cpu时间等;
    • varnishhist:查看日志历史信息;

...

2. varnish的程序环境
  • /etc/varnish/varnish.params:配置varnish服务进程的工作特性,例如监听的地址和端口,缓存机制;
  • /etc/varnish/default.vcl:配置各Child/Cache线程的缓存策略;
  • 主程序:/usr/sbin/varnishd
  • CLI interface:/usr/bin/varnishadm
  • Shared Memory Log交互工具:
    /usr/bin/varnishhist /usr/bin/varnishlog /usr/bin/varnishncsa /usr/bin/varnishstat /usr/bin/varnishtop
  • 测试工具程序:/usr/bin/varnishtest
    VCL配置文件重载程序:/usr/sbin/varnish_reload_vcl
  • Systemd Unit File:/usr/lib/systemd/system/varnish.service
  • varnish日志持久的服务: /usr/lib/systemd/system/varnishlog.service /usr/lib/systemd/system/varnishncsa.service
  • varnish的缓存存储机制(Storage Type):-s [name=]type[,options]
    varnish缓存可存在磁盘文件中,与nginx不同,因为nginx缓存是进行哈希后,用一个目录结构,还可指明目录层级,就是把文件的哈希码,按前几个字符进行目录分层结构,每个一个缓存项是单独一个文件存储的;
    而varnish的缓存则不是这样存储的,varnish缓存是把所有文件存储在一个文件中,在外部看来是一个巨大的单个文件,其内部是一个黑盒,varnish内部自行管理缓存,它知道到哪去加载缓存文件、缓存文件叫什么名等;
    用户对其内部管理机制不得而知,还有,基于这种方式缓存的数据,指对当前进程有效,意思是重启后,黑盒得重置,里面的所有缓存全部失效;所以,varnish不能随意重启!

    • malloc[,size]:内存存储,[,size]用于定义空间大小;重启后所以缓存项失效;
    • file[,path[,size[,granularity]]]:磁盘文件存储,黑盒;重启后所以缓存想失效;
    • persistent,path,size : 文件存储,黑盒;重启后所以缓存项有效;实验中可以使用;
  • varnish程序的选项:

    • 程序选项:/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/varnishparams文件

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
  • 命令行工具:varnishadm
    varnishadm -S /etc/varnish/secret -T [ADDRESS:]PORT
  • 配置文件相关:
    vcl.list vcl.load:装载,加载并编译; vcl.use:激活; vcl.discard:删除; vcl.show [-v] :查看指定的配置文件的详细信息;
  • 运行时参数:
    param.show -l:显示列表; param.show param.set
  • 缓存存储:
    storage.list
  • 后端服务器:
    backend.list
示例:
[root@localhost ~]# cat /usr/lib/systemd/system/varnish.service 
[Unit]
Description=Varnish Cache, a high-performance HTTP accelerator
After=network.target    
[Service]   
... 
EnvironmentFile=/etc/varnish/varnish.params #先加载环境文件;加载后里面的变量名就可直接调用了;  
Type=forking
PIDFile=/var/run/varnish.pid
PrivateTmp=true
ExecStart=/usr/sbin/varnishd \  #启动varnish
    -P /var/run/varnish.pid \   #指明pid文件
    -f $VARNISH_VCL_CONF \    #指明vcl配置文件,此处的变量就是在环境文件中定义的变量;
    -a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \
    -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \
    -S $VARNISH_SECRET_FILE \
    -u $VARNISH_USER -g $VARNISH_GROUP \
    -s $VARNISH_STORAGE \
    $DAEMON_OPTS   #表示为除以上常见选项外的变量; 
ExecReload=/usr/sbin/varnish_reload_vcl 
[Install]
WantedBy=multi-user.target

...

[root@localhost ~]# cat /etc/varnish/varnish.params    #这里定义的其实都是命令行选项:可查看unit file文件就可明白 /usr/lib/systemd/system/varnish.service
RELOAD_VCL=1 表示systemctl使用reload而不是restart命令时,会自动重新装载vcl配置文件,就是使vcl新配置文件生效; 
VARNISH_VCL_CONF=/etc/varnish/default.vcl 设置如何加载缓存策略配置文件;此路径可自定义;   
# VARNISH_LISTEN_ADDRESS=192.168.1.5 监听的地址;默认监听所有ipv4和v6地址;如果只期望监听在一个地址上时看,可启用;
VARNISH_LISTEN_PORT=6081 用来提供服务的监听端口,varnish常作为web服务的反代,所以要改为80;但实际varnish不会真正面向客户端,而面向的是代理服务器有可能是lvs或nginx,一般是nginx或ha proxy居多,nginx和ha proxy是可以直接支持端口映射的,类似于nat机制;所以,使用6081也没什么问题;    
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 为varnish的管理接口地址; 
VARNISH_ADMIN_LISTEN_PORT=6082 为varnish管理监听的端口; 
VARNISH_SECRET_FILE=/etc/varnish/secret 为varnish连接时要求认证,是预共享密钥文件,是由varnish自己生成的密钥;  
VARNISH_STORAGE="file,/var/lib/varnish/varnish_storage.bin,1G" 为varnish缓存的存储格式为file方式存储,为二进制格式文件,1G是大小;VARNISH_STORAGE只是一个被unit file文件调用的变量名而已,跟varnish没有关系;    
VARNISH_TTL=120 如果后端服务器没指明生存时长,就为此处的默认值;    
VARNISH_USER=varnish 运行varnish进程的用户名、组名;
VARNISH_GROUP=varnish   
#DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300" 指定额外的变量;还可指定很多变量,可使用man varnishd查看;例如可指明的选项有acceptor_sleep_max、ban_lurker_age、 ban_lurker_batch等等;
    其中:Run-Time Parameters运行时参数包括很多:
    thread_pools 线程池个数,需要定义;
        Default: 2 默认为2个;
        Minimum: 1 最少为1个;
    thread_pool_min 定义每个线程池中的最少个数;
        Default: 100 默认为100个;
        Maximum: 5000 最多为5000个;
    thread_pool_max 定义每个线程池中的最多个数;
        Default: 5000 默认为5000个;
        Minimum: 100 最少为100个;
    这些运行时参数,都可以使用-p param=value指定;
  • 还要配置缓存策略;/etc/varnish/default.vcl
3. VCL:varnish configuration language;
  • 配置缓存策略的工具
  • 基于“域”的配置语言
    • 使用{};跟nginx一样配置段使用{};
    • 域类似于iptables中的钩子;
      用户请求到达varnish进程时,先分析请求是否允许,如果不允许则直接丢弃;
      如果允许则要查缓存,查看是否缓存命中,如果命中,则要到缓存中取内容(要查看缓存是否过期,是否要检查有效性验证等);
      如果缓存中没命中,要到后端取数据,取内容后,是否要缓存,缓存多长时间,缓存后应如何响应给客户端都可进行控制等等;
      所以,在不同的位置进行控制,这类似于钩子;在每个位置加上一个钩子,在钩子上写配置项,而这每一个位置就叫做一个域;生效配置只对一个域(位置)有效;
      vcl是基于域的配置语言,支持使用正则表达式,自定义变量,if语句,还有内置函数等等;
      vcl策略在启用之前,必须先由management进程转换成C代码,就是由vcl compiler转换成C代码,接着由GCC编译器将其编译成二进制格式的才能被各Child/cache子进程使用;
      所以,编写的vcl能生效,要安装gcc编译器;这是个依赖关系;
      另外,编译后的版本,可以是手动即时加载,但是一旦varnish进程关闭,配置也就消失;配置文件每次启动后,会默认加载编译生效;所以,要把长期使用的vcl配置放在vcl配置文件中,这个配置文件通常需要手动指定位置,且并没有固定格式的文件要求;不过,默认会读取一个default.vcl的配置文件;
Varnish Finite State Machine
  • vcl即叫做域专用的编程语言; 同时也叫做状态引擎:state engine
    • VCL存在多个状态引擎,状态之间存在相关性,但彼此间相互隔离;每个引擎使用return(x)来退出当前状态,并转入下一状态;不同的状态引擎,能够使用的x还各不相同;
    • 不同的状态的引擎,其x是不尽相同的;
  • 当varnish处理一个用户请求时,首先需要分析http本身,例如从http首部获取到请求方法、请求的url以及是否是合法的方法等等,这些分析结束后,就需要做决策,例如是查缓存,还是到后端服务器读取;而这一切的操作过程,都是在vcl状态引擎之间进行的;
  • 常见流程:
  1. 其中一种响应方式有可能是不运行客户端访问,直接拒绝;
  2. 还有可能的第二种方式,请求进来时分析发现,是正常请求,于是判断资源类型的是否是可缓存的,如果是可缓存的,例如用户请求的get方法,因为get方法是可以查缓存的,就开始查找缓存,如果缓存命中了直接返回给客户端;
  3. 如果发现请求的资源类型缓存未命中,则varnish要到后端服务器读取,取回来之后在响应给客户端;
  4. 如果发现请求的资源类型是不可缓存的,因为用post或put方法就不可缓存,于是直接到后端服务器读取;例如上传一个文件或提交一个表单;后端服务器响应给varnish后,再响应给客户端;
  • 状态引擎:

    • vcl_recv:收到请求,在此处可判定请求方法,如果是get或head,则送给vcl_hash引擎,因为是可缓存的;如果用的是其它方法就送给vcl_fetch()引擎,到后端取数据;
    • vcl_hash:查找缓存,有可能命中或未命中,命中就交给vcl_hit()引擎,未命中交给vcl_miss()引擎;
    • vcl_hit():命中了,就到缓存中取数据,然后交给vcl_deliver()响应给客户端;
    • vcl_miss():未命中就交给vcl_fetch()到后端取数据;
    • vcl_miss():未命中就交给vcl_fetch()到后端取数据;
    • vcl_fetch():到后端服务器读取数据;
    • vcl_deliver():响应给客户端;


      HTTP协议缓存和CDN以及varnish_第3张图片
      vcl.png
  • varnish请求流程:
    vcl_recv()收到请求以后,可以使用的变量是req.*开头的所有变量,收到的可能请求情况有:hash,purge,pass,pipe,synth都交给vcl_hash();
    哈希完以后有两种结果:要么查缓存,要么不查缓存;
    如果查缓存,交给hash lookup,如果命中交给vcl_hit,未命中交给vcl_miss,如果是hit-for-pass则交给vcl_pass(),如果是busy则处于等待状态;
    另一种情况是不查缓存,如果理解不了则使用vcl_pipe,要做purge修剪缓存则交给vcl_pugre();
    到后端取数据时,FETCH_DONE为取得数据,FETCH_FAIL则表示没取得数据,请求的数据不存在;
    只要知道,状态引擎可以对用户请求作出处理,处理后使用return指明下一步到哪;

      vcl_recv的默认配置:
          
              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_init:在处理任何请求之前要执行的vcl代码:主要用于初始化VMODs;
    • vcl_fini:所有的请求都已经结束,在vcl配置被丢弃时调用;主要用于清理VMODs;
  • vcl的语法格式:
    (1) VCL files start with vcl 4.0;固定行,指明兼容版本
    (2) //, # and /* foo */ for comments; 注释符
    (3) Subroutines are declared with the sub keyword; 例如sub vcl_recv { ...};
    (4) No loops, state-limited variables(不支持循环,受限于引擎的内建变量);
    (5) Terminating statements with a keyword for next action as argument of the return() function, i.e.: return(action);用于实现状态引擎转换;
    (6) Domain-specific;域专用

  • The VCL Finite State Machine有限状态机
    (1) Each request is processed separately;每个请求都单独处理
    (2) Each request is independent from others at any given time;每个请求在任何时候都是独立于其他请求的
    (3) States are related, but isolated;每个状态引擎是相关的,又是相互独立的
    (4) return(action); exits one state and instructs Varnish to proceed to the next state;退出一个状态引擎,并指示varnish进入下一个状态引擎
    (5) Built-in VCL code is always present and appended below your own VCL;

  • 三类主要语法:

          sub subroutine {
              ...
          }
          
          if CONDITION {
              ...
          } else {    
              ...
          }
          
          return(), hash_data()
    
  • VCL Built-in Functions and Keywords

    • 函数:
      regsub(str, regex, sub)
      regsuball(str, regex, sub)
      ban(boolean expression)
      hash_data(input)
      synthetic(str)
    • Keywords:
      call subroutine, return(action),new,set,unset
    • 操作符:
      ==, !=, ~, >, >=, <, <=
      逻辑操作符:&&, ||, !
      变量赋值:=
示例:obj.hits是内建变量,用于保存某缓存项的从缓存中命中的次数;
        [root@localhost ~]# vim /etc/varnish/default.vcl 
            #在其中sub vcl_deliver {...}段中添加如下内容
            if (obj.hits>0) {
                    set resp.http.X-Cache = "HIT via " + server.ip;
                } else {
                    set resp.http.X-Cache = "MISS via " + server.ip;
                }      
        
        [root@localhost ~]# varnishadm
        vcl.load test2 default.vcl
        200        
        VCL compiled.
        vcl.list
        200
        active          0 test1
        available       0 test2
        vcl.use test2
        vcl.use test2
        200        
        VCL 'test2' now active
  • 变量类型:
    • 内建变量:
      req.*:request,表示由客户端发来的请求报文相关;
      req.http.*:请求报文的各首部
      req.http.User-Agent, req.http.Referer, ...
      bereq.*:由varnish发往BE主机的httpd请求相关;
      bereq.http.*:发往后端请求报文的http首部
      beresp.*:由BE主机响应给varnish的响应报文相关;
      beresp.http.*:响应报文的各首部
      resp.*:由varnish响应给client相关;
      obj.*:存储在缓存空间中的缓存对象的属性;只读;
    • 常用变量:
      • bereq.*:发往后端主机的常用变量
        bereq.http.HEADERS:可定义的首部
        bereq.request:向后端服务器发送请求文本时的请求方法;
        bereq.url:请求的url;
        bereq.proto:请求的协议版本;
        bereq.backend:指明要调用的后端主机;
        req.http.Cookie:客户端的请求报文中Cookie首部的值;
        req.http.User-Agent ~ "chrome"
      • beresp.*:后端服务器响应的可用变量
        beresp.http.HEADERS:后端响应http的其它各首部;
        beresp.status:响应的状态码;
        reresp.proto:协议版本;
        beresp.backend.name:BE主机的主机名;
        beresp.ttl:BE主机响应的内容的余下的可缓存时长;
      • obj.*
        obj.hits:此对象从缓存中命中的次数;
        obj.ttl:对象的ttl值
      • server.*
        server.ip
        server.hostname
      • client.*
        client.ip
    • 用户自定义:
      set
      unset
HTTP协议缓存和CDN以及varnish_第4张图片
vcl变量可用范围.png

@R/W:能读能修改;R:只读;

示例:强制对某资源的请求不检查缓存
    #在BE主机上添加2个路径和index.html资源
    [root@rs1 ~]# mkdir /var/www/html/{admin,login}
    [root@rs1 ~]# ls /var/www/html
    admin  index.html  login
    [root@rs1 ~]# vim /var/www/html/admin/index.html
        

Admin App

[root@rs1 ~]# vim /var/www/html/login/index.html

Login App

#在varnish主机上修改配置文件 [root@localhost varnish]# vim default.vcl #在 sub vcl_recv{...}中添加如下内容 if (req.url ~ "(?i)^/(login|admin)") { return(pass); } #回到varnish命令行:使vcl配置生效 [root@localhost ~]# varnishadm vcl.load test3 default.vcl 200 VCL compiled. vcl.list 200 available 0 test1 active 0 test2 available 0 test3 vcl.use test3 vcl.use test3 200 VCL 'test3' now active

访问测试:


HTTP协议缓存和CDN以及varnish_第5张图片
admin-x-cache.png
示例:不能使用curl访问
    #在varnish主机上修改配置文件
    [root@localhost varnish]# vim default.vcl
    #在 sub vcl_recv{...}中添加如下内容
        if (req.http.User-Agent ~ "(?i)curl") {
            return(synth(406));
            }
    #回到varnish命令行:使vcl配置生效
    [root@localhost ~]# varnishadm
    vcl.load test4 default.vcl
    200        
    VCL compiled.       
    vcl.use test4
    200        
    VCL 'test4' now active
    
    #client使用curl访问测试
    [root@localhost ~]# curl http://192.168.1.22
    
    
      
        406 Not Acceptable
      
      ...

...

示例:对于特定的资源,例如公开的图片等取消其私有标识,并强行设定其可以由varnish缓存时长
    #在BE主机上准备图片
    [root@rs1 ~]# find /usr/share -iname "*.jpg" -exec cp {} /tmp/image/ \;     
    #修改varnish的vcl配置,在default.vcl中的sub vcl_backend_response {...}中添加如下内容
     if (beresp.http.cache-control !~ "(?i)s-maxage") {
        if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)") {
                unset beresp.http.Set-Cookie;
                set beresp.ttl=3600s;
                }
        }
     #回到varnish命令行:使vcl配置生效
    vcl.load test1 default.vcl
    200        
    VCL compiled.
    vcl.list
    200        
    available       0 boot
    active          0 reload_2018-09-02T21:33:43
    available       0 test1
    
    vcl.use test1
    200        
    VCL 'test1' now active

使用client访问测试:


HTTP协议缓存和CDN以及varnish_第6张图片
vcl-1.png
示例:使BE主机访问日志里记录client的真实ip地址
        #在sub 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命令行:使vcl配置生效
        vcl.load test2 default.vcl
        200        
        VCL compiled.
        vcl.use test2
        200        
        VCL 'test2' now active
        vcl.list
        200        
        available       0 boot
        available       0 reload_2018-09-02T21:33:43
        available       0 test1
        active          0 test2
    #修改BE主机的访问日志格式
    [root@rs1 conf]# vim httpd.conf
         LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    #使用client访问后查看access_log
    [root@rs1 conf]# tail -2 /var/log/httpd/access_log
    192.168.1.109.192.168.1.109 - - [02/Sep/2018:20:21:31 +0800] "GET / HTTP/1.1" 200 26 "-" "curl/7.29.0"
    192.168.1.113.192.168.1.113 - - [02/Sep/2018:20:24:10 +0800] "GET / HTTP/1.1" 200 26 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"
  • 缓存对象的修剪:purge, ban

    示例:

    在sub vcl_recv {...}中定义执行purge操作

      if (req.method == "PURGE") {
          return(purge);
          }
    

    定义purge的操作方式

      sub vcl_purge {
                  return (synth(200,"Purged"));
              }
    

    回到varnish命令行:使vcl配置生效

      vcl.load test3 default.vcl
      200        
      VCL compiled.
      vcl.use test3
      200        
      VCL 'test3' now active
      vcl.list
      200        
      available       0 boot
      available       0 reload_2018-09-02T21:33:43
      available       0 test1
      available       0 test2
      active          0 test3
    

    使用client访问测试:

      [root@node2 ~]# curl -I http://192.168.1.22
      HTTP/1.1 200 OK
      Date: Sun, 02 Sep 2018 14:18:40 GMT
      Server: Apache/2.4.6 (CentOS) PHP/5.4.16
      Last-Modified: Sat, 01 Sep 2018 13:13:42 GMT
      ETag: "1a-574cf13c0f7fa"
      Content-Length: 26
      Content-Type: text/html; charset=UTF-8
      X-Varnish: 32825 54
      Age: 29
      Via: 1.1 varnish-v4
      X-Cache: HIT via192.168.1.22
      [root@node2 ~]# curl -X PURGE http://192.168.1.22
      
      
        
          200 Purged
        
        
          

    Error 200 Purged

    Purged

    Guru Meditation:

    XID: 58


    Varnish cache server

  • 此种方法比较危险,用户只要使用purge方法,缓存就被清除,因此,对于这种请求,应该添加访问控制机制;

      示例:
      #在default.vcl配置文件中定义访问控制,位置放在vcl 4.0之下即可
          acl purge {
              "127.0.0.1";
              "192.168.0.0/24";
              }
      #在sub vcl_recv{...}中添加访问控制内容
          if (req.method == "PURGE") {
                  if (!client.ip ~ purgers ) {
                          return(synth(405,"Purging not allowed for"+ client.ip));
                          }
                  return(purge);
                  }
      #回到varnish命令行:使vcl配置生效
      vcl.load test4 default.vcl
      200        
      VCL compiled.
      vcl.use test4
      200        
      VCL 'test4' now active
      #使用client访问测试
      [root@node2 ~]# curl -X PURGE http://192.168.1.22
      
      
        
          405 Purging not allowed for192.168.1.109
        
        
          

    Error 405 Purging not allowed for192.168.1.109

    Purging not allowed for192.168.1.109

    Guru Meditation:

    XID: 32834


    Varnish cache server

  • 禁用匹配到的缓存项:banning

    • (1) varnishadm:
      ban

    • (2) 在配置文件中定义,使用ban()函数;

            示例:
                (1) #在BE主机上准备访问环境
                [root@rs1 conf]# mkdir /var/www/html/javascripts
                [root@rs1 conf]# vim /var/www/html/javascripts/test1.js
                #使用client多访问几次有缓存后
                [root@node2 ~]# curl -I http://192.168.1.22/javascripts/test1.js
                HTTP/1.1 200 OK
                Date: Sun, 02 Sep 2018 15:00:38 GMT
                Server: Apache/2.4.6 (CentOS) PHP/5.4.16
                Last-Modified: Sun, 02 Sep 2018 14:59:27 GMT
                ETag: "6-574e4abccb0ee"
                Content-Length: 6
                Content-Type: application/javascript
                X-Varnish: 75 32846
                Age: 30
                Via: 1.1 varnish-v4
                X-Cache: HIT via192.168.1.22
                Connection: keep-alive
                #到varnish命令行模式,手动清理
                ban req.url ~ ^/javascripts
                200 
                #再次使用client访问测试
                [root@node2 ~]# curl -I http://192.168.1.22/javascripts/test1.js
                ...
                X-Cache: Miss via192.168.1.22
                ...
                (2) 在配置文件中定义,使用ban()函数;
                #在 sub vcl_recv {...}中定义如下内容
                if (req.method == "BAN") {
                    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"));
                }               
                #回到varnish命令行,使配置文件生效
                vcl.load test5 default.vcl
                200        
                VCL compiled.
                vcl.use test5
                200        
                VCL 'test5' now active
      
  • varnish能够使用多个后端主机的方法:

    • (1)定义后端主机的host和port
    • (2)定义一个组
    • (3)在vcl_recv{...}中使用set...调用
  • arnish还可为后端多个主机做负载均衡:

    • 还是先定义多个后端主机;
    • 然后使用:vcl_init子例程,创建一个负载均衡器;

    示例1:实现访问静态资源是反代给后端一台主机,访问动态资源是反代至另一台主机
    #首先要在配置文件中导入模块实现定义多个后端主机,在default.vcl中的vcl4.0下添加
    import directors;
    #准备2个后端主机 ,提供一个静态网页default和一个动态网页appsrv
    #后端php主机为:192.168.147.130
    ]# yum -y install php httpd
    ]# systemctl start httpd.service

          提供php动态资源测试页:
          ]# vim /var/www/html/index.php
          
          #后端静态主机为默认主机default
      
          #在vcl添加多台后端主机:
          ]# vim /etc/varnish/default.vcl
          添加:
          backend appsrv {
              .host = "192.168.147.130";
              .port = "80";
          }
          
          sub vcl_recv {
              if (req.url ~ "(?i)\.php$") {
                  set req.backend_hint = appsrv;
              } else {
                  set req.backend_hint = default;
              }
          }
          vcl.load test6 default.vcl
          200        
          VCL compiled.
          vcl.use test6
          200        
          VCL 'test6' now active
    

client使用浏览器访问测试:


HTTP协议缓存和CDN以及varnish_第7张图片
vcl2.png
HTTP协议缓存和CDN以及varnish_第8张图片
vcl3.png
示例2:
    import directors;    # load the directors

            backend server1 {
                .host = "192.168.147.130";
                .port = "80";
            }
            backend server2 {
                .host = "192.168.147.131";
                .port = "80";
            }

            sub vcl_init {
                new servers = directors.round_robin();
                servers.add_backend(server1);
                servers.add_backend(server2);
            }

            sub vcl_recv {
                # send all traffic to the bar director:
                set req.backend_hint = servers.backend();
            }
            vcl.load test9 default.vcl
            200        
            VCL compiled.
            vcl.use test9
            200        
            VCL 'test9' now active
  • 基于cookie的seeion sticky

      示例:
              sub vcl_init {
                  new h = directors.hash();
                  h.add_backend(one, 1);   // backend 'one' with weight '1'
                  h.add_backend(two, 1);   // backend 'two' with weight '1'
              }
    
              sub vcl_recv {
                  // pick a backend based on the cookie header of the client
                  set req.backend_hint = h.backend(req.http.cookie);
              }       
    
  • BE健康状态监测

    • 健康状态检测的配置方式:

      • probe PB_NAME { } backend NAME = { .probe = PB_NAME; ... }
      • backend NAME { .probe = { ... } }
    • .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;

        示例:
            probe check {
                .url = "/.healthcheck.html";
                .window = 5;
                .threshold = 4;
                .interval = 2s;
                .timeout = 1s;
            }
      
            backend default {
                .host = "10.1.0.68";
                .port = "80";
                .probe = check;
            }
      
            backend appsrv {
                .host = "10.1.0.69";
                .port = "80";
                .probe = check;
            }   
      
  • 设置后端的主机属性:

          backend BE_NAME {
              ...
              .connect_timeout = 0.5s;
              .first_byte_timeout = 20s;
              .between_bytes_timeout = 5s;
              .max_connections = 50;
          }
    
  • varnish的运行时参数:

    • 线程模型:
      cache-worker:每一个连接由1个worker进程响应,该线程负责处理请求;
      cache-main:该线程只有1个,整个缓存管理的主进程,负责启动缓存空间;
      ban lurker:该线程清理缓存空间,ban功能的实现;
      acceptor:
      epoll/kqueue:该线程负责管理线程池;
      expire: 该线程用来移除过期内容;
      backend poll: 负责健康状态检测;
      ...

    • 线程相关的参数:
      在线程池内部,其每一个请求由一个线程来处理; 其worker线程的最大数决定了varnish的并发响应能力;
      thread_pools:线程池数量, 最好小于或等于CPU核心数量;
      thread_pool_max:每一个线程池最大启动线程数量;默认5000个;每线程池的最大线程数;
      thread_pool_min:每一个线程池最少启动线程数量;默认100个; 额外意义为“最大空闲线程数”;
      最大并发连接数=thread_pools * thread_pool_max
      thread_pool_timeout:线程池的超时时间,表示一个线程空闲多长时间才认为是空闲,然后需要给它关掉;默认300秒;
      thread_queue_limit: 每个线程池队列的长度,每个线程池最多允许有多少个等待请求;默认20个;
      thread_stats_rate: 最多允许响应请求多少个时,必须把日记 写入日记内存空间;
      每个线程处理完后,要把处理的内容记录的日志中,如果线程繁忙,如果每一次请求处理完以后都要立即写到内存空间中去,对内存来说也很多压力或至少耽误响应用户的时间了,为了避免这个效果,可以先响应,然后再一次的往内存中写日志;最多允许处理多少个请求以后,必须写入日志内存空间中去;默认是10个;
      thread_pool_add_delay:Wait at least this long after creating a thread.
      thread_pool_destroy_delay:Wait this long after destroying a thread.
      workspace_thread: 每个线程额外提供多少内存空间,来作为请求处理的内存;默认2kbytes;
      注意:如果需要对varnish内存调优的话,需要调的是线程池的数量和每个线程池的最大并发响应数量、最小空闲线程数量;据说并发量不能超过6000个,否则不稳定;因为,它毕竟还是要从缓存中取内容的,不像nginx是纯粹的反代服务器;

    • Timer相关的参数:
      send_timeout:Send timeout for client connections. If the HTTP response hasn't been transmitted in this many seconds the session is closed.
      timeout_idle:Idle timeout for client connections.
      timeout_req: Max time to receive clients request headers, measured from first non-white-space character to double CRNL.
      cli_timeout:Timeout for the childs replies to CLI requests from the mgt_param.
      - 设置方式:
      vcl.param
      param.set
      - 永久有效的方法:
      varnish.params
      DEAMON_OPTS="-p PARAM1=VALUE -p PARAM2=VALUE"

  • varnish日志区域:

    • shared memory log
      计数器
      日志信息
      1、varnishstat - Varnish Cache statistics -1 -1 -f FILED_NAME -l:可用于-f选项指定的字段名称列表; MAIN.cache_hit MAIN.cache_miss # varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss # varnishstat -l -f MAIN -f MEMPOOL
      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> -x taglist:排除列表 -X <[taglist:]regex>
      3、varnishlog - Display Varnish logs
      4、 varnishncsa - Display Varnish logs in Apache / NCSA combined log format
  • 内建函数:
    hash_data():指明哈希计算的数据;减少差异,以提升命中率;
    regsub(str,regex,sub):把str中被regex第一次匹配到字符串替换为sub;主要用于URL Rewrite
    regsuball(str,regex,sub):把str中被regex每一次匹配到字符串均替换为sub;
    return()
    ban(expression)
    ban_url(regex):Bans所有的其URL可以被此处的regex匹配到的缓存对象;
    synth(status,"STRING"):purge操作;

你可能感兴趣的:(HTTP协议缓存和CDN以及varnish)