Varnish

一、Varnish简介

    Varnish 的作者Poul-Henning Kamp是FreeBSD的内核开发者之一,他认为现在的计算机比起1975年已经复杂许多。在1975年时,储存媒介只有两种:内存与硬盘。但现在计算机系统的内存除了主存外,还包括了CPU内的L1、L2,甚至有L3快取。硬盘上也有自己的快取装置,因此Squid Cache自行处理物件替换的架构不可能得知这些情况而做到最佳化,但操作系统可以得知这些情况,所以这部份的工作应该交给操作系统处理,这就是 Varnish cache设计架构。 

    varnish项目是2006年发布的第一个版本0.9.距今已经八年多了,此文档之前也提过varnish还不稳定,那是2007年时候编写的,经过varnish开发团队和网友们的辛苦耕耘,现在的varnish已经很健壮。很多门户网站已经部署了varnish,并且反应都很好,甚至反应比squid还稳定,且效率更高,资源占用更少。相信在反向代理,web加速方面,varnish已经有足够能力代替squid。



二、Web缓存部分首部介绍

    程序能够缓存是因为程序具有局部性,时间局部性和空间局部性。缓存一般存储为键值,key为访问路径的url经过hash计算后的结果,value为服务器内容,一般缓存存储的是热点数据,切缓存中存储的数据不会大于后端web服务器的程序数据大小。缓存的对象是有生命周期的,当缓存空间耗尽的时候会根据LRU算法(最近最少使用)或其他算法对缓存空间进行清理。缓存中一般只能缓存非客户私有数据,客户私有数据和带cookie的数据可以进行缓存,但是存在风险,所以一般不对客户私有数据进行缓存。

    缓存处理的步骤:接收请求 --> 解析请求 (提取请求的URL及各种首部)--> 查询缓存 --> 新鲜度检测 --> 创建响应报文 --> 发送响应 --> 记录日志(此图不一定准确,只是为了好理解)

wKioL1Ysl-yz4WCYAAL8FDdACqQ325.jpg

新鲜度检测机制:

  过期日期:

    HTTP/1.0 Expires(Expires:Thu, 04 Jun 2015 23:38:18 GMT)

    HTTP/1.1 Cache-Control: max-age(Cache-Control:max-age=600)

  条件式请求首部:

    If-Modified-Since:基于请求内容的时间戳作验正;

    If-None-Match:基于被请求变量的实体值ETag;ETag是一个可以与Web资源关联的记号

  有效性再验正:revalidate

    如果原始内容未改变,则仅响应首部(不附带body部分),响应码304 (Not Modified)

    如果原始内容发生改变,则正常响应,响应码200;

    如果原始内容消失,则响应404,此时缓存中的cache object也应该被删除;


缓存控制机制(Cache-Control):

  请求:

    no-cache:不要给我缓存的内容,要从Web服务器现取的内容

    max-age:只接受Age值小于max-age的值,并且没有过期的缓存对象

    (Age:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时候)

    max-statle:可以接受过期的缓存对象,但是过期时间必须小于此值

    min-fresh:接收其新鲜生命期大于当前Age和min-fresh值之和的缓存对象

  响应:

    public:可以用Cache内容回应任何用户

    private:只能缓存内容回应先前请求该内容的那个用户

    no-cache:可以缓存,但是只有在跟Web服务器验证了其有效后,才能返回客户端

    no-store:不允许缓存



三、Varnish的结构和工作流程

wKioL1YsoXvB_donAAE02BxXtwc916.jpg

①、Management进程

Management进程主要实现应用新的配置、编译VCL、监控varnish、初始化varnish以及提供一个命令行接口等。Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。


②、Child/Cache 进程

Commad line 线程 : 管理接口

Storage/hashing 线程 :完成hash并进行缓存存储

Log/stats 线程:查看记录日志并统计各种状态

Accept 线程:接收新的连接请求并响应;

Backend Communication 线程:管理后端主机线程,当缓存中没有缓存需要有此进程交由后端服务器处理

Worker 线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;

Object Expiry 线程:从缓存中清理过期内容;


③、Varnish日志

为了与系统的其它部分进行交互,Child进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个worker线程都使用了日志数据缓存。共享内存日志大小一般为90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。varnish提供了多个不同的工具如varnishlog、varnishncsa或varnishstat等来分析共享内存日志中的信息并能够以指定的方式进行显示。


④、VCL(Varnish Configuation Language)简介

        Varnish Configuration Language (VCL)是varnish配置缓存策略的工具,它是一种基于“域”(domain specific)的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用set自定义变量、支持if判断语句,也有内置的函数和变量等。使用VCL编写的缓存策略通常保存至.vcl文件中,其需要编译成二进制的格式后才能由varnish调用。事实上,整个缓存策略就是由几个特定的子例程如vcl_recv、vcl_fetch等组成,它们分别在不同的位置(或时间)执行,如果没有事先为某个位置自定义子例程,varnish将会执行默认的定义。

        VCL策略在启用前,会由management进程将其转换为C代码,而后再由gcc编译器将C代码编译成二进制程序。编译完成后,management负责将其连接至varnish实例,即child进程。正是由于编译工作在child进程之外完成,它避免了装载错误格式VCL的风险。因此,varnish修改配置的开销非常小,其可以同时保有几份尚在引用的旧版本配置,也能够让新的配置即刻生效。编译后的旧版本配置通常在varnish重启时才会被丢弃,如果需要手动清理,则可以使用varnishadm的vcl.discard命令完成。


⑤、Varnish 的后端存储

Varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。后端存储的类型包括:

  • file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);

  • malloc:使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象;

  • persistent(experimental):与file的功能相同,但可以持久存储数据(即重启varnish数据时不会被清除);仍处于测试期;

Varnish无法追踪某缓存对象是否存入了缓存文件,从而也就无从得知磁盘上的缓存文件是否可用,因此,file存储方法在varnish停止或重启时会清除数据。而persistent方法的出现对此有了一个弥补,但persistent仍处于测试阶段,例如目前尚无法有效处理要缓存对象总体大小超出缓存空间的情况,所以,其仅适用于有着巨大缓存空间的场景。


   一个Management可以启动多个child/cache,每个child/cache子进程内部生成多个worker threads响应用户请求。Varnish是单进程多线程模式的,每个线程处理一个用户请求,但是不是所有的线程都用于用户请求,部分线程工作于Backend Communication、Log/stats等


三、Varnish的安装和配置

这里使用Centos 7通过epel源安装Varnish

[root@C7node1 /]# yum install varnish
jemalloc.x86_64 0:3.6.0-1.el7
#jemalloc 一个性能非常卓越的内存分配器,由于C提供的内存分配效率过低,所以这里使用了更高性能的内存管理器
#用户malloc内存缓存对象使用
[root@C7node1 /]# rpm -ql varnish
/etc/logrotate.d/varnish                #日志回滚
/etc/varnish/default.vcl                #varnish的配置文件
/etc/varnish/varnish.params                #varnish的参数,用于配置varnish作为缓存的工作属性
/usr/lib/systemd/system/varnishlog.service              #从共享内存中抽取第二段数据日志
/usr/lib/systemd/system/varnishncsa.service             #从共享内存中抽取第二段数据日志

配置varnish的三种应用:

①、varnishd应用程序的命令行参数(/etc/varnish/varnish.params)

Varnishd常用选项

    -p param=value            # set parameter。配置参数

    -r param[,param...]        # make parameter read-only。设定只读参数列表

    -f file            # VCL script,读取VCL配置文件

    -a address:port        # HTTP listen address and port。指定http地址端口

    -d                  # debug。运行于调试模式    

    -s [name=]kind[,options]     # Backend storage specification。指定存储类型

        #   -s malloc[,<size>]

        #   -s file,<dir_or_file>,<size>,<granularity>

        #   -s persist{experimental}

    -T address:port           # Telnet listen address and port。指定管理接口

    -S secret-file            # Secret file for CLI authentication。指定管理密钥文件

    -t                   # Default TTL。前端服务器varnish连接各后端服务器时超时时间

②、varnishd Child/cahce:实时参数(child的进程数,worker的线程数)

    varnishadm

        param.set

③、vcl:配置缓存系统的缓存机制;

    通过vcl配置文件进行配置;先编译,后应用;依赖于c编译器;



四、Varnish工具的使用

①、varnishadm

[root@C7node1 varnish]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082
help [<command>]                   #查询帮助
ping [<timestamp>]                  #测试ping通信
auth <response>            
quit                          #退出varnishadm  
status                         #查看Child的状态
start                          #开启Child
stop                            #停止Child
vcl.load <configname> <filename>           #编译加载指定vcl
vcl.inline <configname> <quoted_VCLstring>    
vcl.use <configname>                  #使用指定vcl
vcl.discard <configname>                #删除指定vcl
vcl.list                        #列出所有vcl
vcl.show <configname>                                    #查看编译好的vcl的配置文件
param.show [-l] [<param>]               #查看运行参数
param.set <param> <value>               #设置运行参数
panic.show                                               #查看Child恐慌挂掉的信息
panic.clear                                              #清除信息
storage.list                                            #查看使用的storage列表
backend.list [<backend_expression>]                     #查看backed后端服务器
backend.set_health <backend_expression> <state>          #调整backed状态
ban <field> <operator> <arg> [&& <field> <oper> <arg>]... #清理缓存中的缓存对象
ban.list                                                   #列出定义的ban规则


②、varnishlog

③、varnishncsa

④、varnishtop

⑤、varnishstat



四、VCL配置文件及其相关参数

①、VCL的状态引擎

    state engine:各引擎之间存一定程度上的相关性;前一个engine如果可以有多种下游engine,则上游engine需要用return指明要转移的下游engine;

  • vcl_recv:接收客户请求

  • vcl_hash:当域名做泛解析的时候,对同一类的url做规范化

  • vcl_hit:命中缓存

  • vcl_miss:没有命中缓存

  • vcl_fetch:去后端服务器请求数据

  • vcl_deliver:投递给客户端

  • vcl_pipe:

  • vcl_pass:

  • vcl_error:

  • vcl_backend_fetch(V4新添加的)

  • vcl_backend_response(V4新添加的)

  • vcl_backend_error(V4新添加的)

  • vcl_purge(V4新添加的)

  • vcl_synth(V4新添加的)

wKioL1Y3i47jMwyWAASZ_3FssxE512.jpg


②、VCL语法

  • //、#或/* comment */  用于注释;会被编译器忽略

  • sub $name 定义函数 ,sub vcl_recv { }

  • 不支持循环,有众多内置变量 ,变量的可调用位置与state engine有密切相关性

  • 使用终止语句,return(action),没有返回值 

  • 域专用 

  • 操作符:=(赋值)、==(等值比较)、~(模式匹配)、!(取反)、&&(逻辑与)、||(逻辑或)

  • 条件判断语句:if(comment){ }else{ }

  • 变量赋值和撤销变量:set name=value,unset name



③、varnish中的内置变量:

  • client

        client.ip:客户端IP地址

  • server

        server.ip:varnish ip地址

        server.hostname:varnish主机名

  • req

   req.http.HEADERS:客户端发往varnish的请求报文的指定首部

        req.method:请求方法

        req.proto:请求协议版本

        req.ttl:请求ttl

        req.url:请求url

  • resp

        resp.http.HEADERS:varnish发往客户端的响应报文的指定首部

        resp.proto:响应协议版本

        resp.reason:响应的原因短语 

        resp.status:响应的状态码   

  • bereq

        bereq.http.HEADERS: 由varnish发往backend server的请求报文的指定首部;

        bereq.request:请求方法;

        bereq.url:请求的url

        bereq.proto:请求的协议版本

        bereq.backend:指明要调用的后端主机;

  • beresp

        beresp.proto:又backend server响应的协议版本

        beresp.status:后端服务器的响应的状态码

        beresp.reason:原因短语;

        beresp.backend.ip:后端服务器的IP地址

        beresp.backend.name:后端服务器的主机名

        beresp.http.HEADER: 从backend server响应的报文的首部;

        beresp.ttl:后端服务器响应的内容的余下的生存时长;

  • obj

        obj.ttl: 对象的ttl值;

        obj.hits:此对象从缓存中命中的次数;

  • storage


wKiom1Y3dm6Ae-ShAADegg0zUso684.jpg

官方文档:https://www.varnish-cache.org/docs/4.0/reference/vcl.html#varnish-configuration-language


④、varnish中的内置方法:

    return():终止vcl引擎

    hash_data(input):进行hash计算并放回对应的hash值

    regsub(str, regex, sub):正则表达式查找替换,之查找替换一次

    regsuball(str, regex, sub):正则表达式查找替换,替换所有

    ban(expression):缓存对象清理



四、实验总结

c7node1.wlw.com    192.168.0.56    Varnish服务器

C6node1.wlw.com    192.168.0.66    backend server 

C6node2.wlw.com    192.168.0.76    backend server 



定义在vcl_deliver中,向响应给客户端的报文添加一个自定义首部X-Cache

[root@C7node1 varnish]# vim /etc/varnish/wlw.vcl
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);
}

sub vcl_deliver {
    if(obj.hits > 0) {
        set resp.http.X-Cache = "HIT";

    }else{
        set resp.http.X-Cache = "Miss";
    }
}    

[root@C7node1 varnish]# varnishadm
vcl.load wlw wlw.vcl
200        
VCL compiled.
vcl.list 
200        
active          0 boot
available       0 wlw
vcl.use wlw
200        
VCL 'wlw' now active

[root@wlw ~]# curl -I http://192.168.0.56
X-Cache: Miss
[root@wlw ~]# curl -I http://192.168.0.56
X-Cache: HIT


让Varnish支持虚拟主机

sub vcl_recv {
    if (req.http.host == "www.wlw.com") {
        return (pipe);
    }
}
sub vcl_pipe{
    set bereq.http.host = "www.wlw.com";
}


强制对某资源的请求,不检查缓存

sub vcl_recv {
    if (req.url ~ "^/test1.html$") {
        return(pass);
    }
}
[root@C6node1 dz]# curl -I http://192.168.0.56/test1.html
HTTP/1.1 200 OK
X-Cache: Miss
[root@C6node1 dz]# curl -I http://192.168.0.56/test1.html
HTTP/1.1 200 OK
X-Cache: Miss
#可以看到多次请求都没有命中缓存

sub vcl_recv {
    if (req.url ~ "(?i)^/login" || req.url ~ "(?i)^/admin") {
        return(pass);
    }
}
#对/login或者/admin的目录都不查询缓存,(?i) 表示对正则匹配不区分大小写


对特定类型的资源(公共可用)取消其私有的cookie标识,并强行设定其可以varnish缓存的时长

sub vcl_backend_response {
    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;               
        }       
    }
}
#veresp.ttl为设置在varnish上的缓存时长



定义backend server和后端主机的健康状态检测,并实现资源分别分配

backend websrv1 {
    .host = "192.168.0.66";
    .port = "80"; 
    .probe = {
        .url = "/test5.html";
    }
}

backend websrv2 {
    .host = "192.168.0.76";
    .port = "80"; 
    .probe = {
        .url = "/test5.html";
    }
}

sub vcl_recv {
    if (req.url ~ "(?i)\.(jpg|png|gif)$") {
        set req.backend_hint = websrv1;
    } else {
        set req.backend_hint = websrv2;
    }                        
}
#定义两台后端主机

测试查看结果

backend.list 
Backend name                   Refs   Admin      Probe
websrv1(192.168.0.66,,80)      1      probe      Healthy 8/8
websrv2(192.168.0.76,,80)      1      probe      Sick 0/8
backend.list
200        
Backend name                   Refs   Admin      Probe
websrv1(192.168.0.66,,80)      1      probe      Healthy 8/8
websrv2(192.168.0.76,,80)      1      probe      Healthy 6/8
#这里因为192.168.0.76忘记关闭防火墙导致请求test5.html检测失败,关闭防火墙后检测通过并恢复



backend server的定义:

backend name {

    .attribute = "value";

}


.host: BE主机的IP;

.port:BE主机监听的PORT;

.probe: 对BE做健康状态检测;

.max_connections:并连接最大数量;


后端主机的健康状态检测方式:

probe name {

     .attribute = "value";

}


.url: 判定BE健康与否要请求的url; 

.request:手动定义请求报文的内容

.expected_response:期望响应状态码;默认为200;

.timeout:健康状态检测的超时时间,默认2s

.interval:每隔多长时间检测一次,默认5s

.window:最近检测的窗口,默认为8

.threshold:默认为3,只要在window定义的次数中有threshold次通过即为正常

.initial:当Varnish主机刚启动的时候检测后端主机多少次成功即为正常,默认为1



基于Varnish的负载均衡

backend websrv1 {
    .host = "192.168.0.66";
    .port = "80";
    .probe = {
        .url = "/test5.html";
    }
}

backend websrv2 {
    .host = "192.168.0.76";
    .port = "80";
    .probe = {
        .url = "/test5.html";
    }
}

import directors;

sub vcl_init {
    new mycluster = directors.round_robin();
    mycluster.add_backend(websrv1);
    mycluster.add_backend(websrv2);
}

sub vcl_recv {
    if (req.url ~ "(?i)\.(jpg|png|gif)$") {
        set req.backend_hint = websrv1;
    } else {
        set req.backend_hint = websrv2;
    }
    set req.backend_hint = mycluster.backend();
}

[root@C6node1 dz]# curl http://192.168.0.56/test1.html
Page 1 Web2
[root@C6node1 dz]# curl http://192.168.0.56/test2.html
Page 2 Web1
[root@C6node1 dz]# curl http://192.168.0.56/test3.html
Page 3 Web2
[root@C6node1 dz]# curl http://192.168.0.56/test4.html
Page 4 Web1
#基于资源的负载均衡,负载均衡算法:fallback, random, round_robin, hash



你可能感兴趣的:(能力,开发者)