varnish原理说明

2530阅读 0评论2017-03-02 niao5929
分类:LINUX

1.varnish架构


varnish主要运行两个进程:Management进程和Child(或Cache)进程;
1.Management进程主要功能:
    通过命令行接口与命令行的控制指令进行交互;

    管理各个子进程,确保每个子进程都能正常工作,如果某个子进程挂掉,则自动让其启动起来;

    完成整个varnish的初始化,能够完成基于vcl的编译器去编译VCL的配置文件,并且检测vcl配置文件是否存在语法错误的,如果有语法错误则
    拒绝编译,因此对于配置文件的分析和启用是由主进程所去实现的,这样能够避免子进程加载错误配置从而导致缓存崩溃;


Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。


2.child进程主要功能:
    使用命令行接口与CLI命令行进行交互;

    用来实现将可以缓存的数据缓存下来,并且构建数据hash表;

    生成日志和状态信息;

    接收用户的请求并构建响应;

    与各后端服务器进行沟通来构建无缓存的响应;

    woker threads真正意义上接收用户请求并构建响应的内部的工作线程;

    缓存失效功能管理;

Child进程包含多种类型的线程,常见的如:
Acceptor线程:接收新的连接请求;

Worker线程:处理并响应用户请求;

Expiry线程:从缓存中清理过期cache object;


3.为了实现管理功能,通常有以下几类接口:
CLI interface:命令行接口;

Telnet interface:允许以telnet的方式连接,但是此方式并不安全;

Web interface:基于web页面来管理varnish;


4.varnish日志
子进程需要生成日志的,因为用户的请求以及自身构建的响应都是由子进程负责的,所以需要生成日志,日志是需要存放在指定日志文件中,日志文件实际是一段共享内存区域,这些内存共享区域需要一些专门观测的工具来观测服务器的工作状态;

为了与系统的其它部分进行交互,Child进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个worker线程都使用了日志数据缓存。

共享内存日志大小一般为90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。varnish提供了多个不同的工具如varnishlog、varnishncsa或varnishstat等来分析共享内存日志中的信息并能够以指定的方式进行显示。

例如,通过查看日志来查看缓存的命中率,varnish为了实现高效性默认将日志放在内存区域中,所以只会保存"最新的"日志信息,如果想保留更长时间的日志可以在日志写满之前定期存储到磁盘上去。而且varnish提供了许多日志分析工具,如:
varnishlog
varnishstat
varnishhist
varnishtop
varnishncsa


5.VCL
Varnish Configuration Language(VCL)是varnish配置缓存策略的工具,它是一种基于域(domain specific)的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用set自定义变量、支持if判断语句,也有内置的函数和变量等。

使用VCL编写的缓存策略通常保存至.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命令完成。


6.VCL的工作是由状态引擎来实现的,所谓的状态引擎就是当一个用户请求到达后,大致走到哪一层,我们在哪个步骤哪个位置大致做出哪些处理,这就为状态。


vcl的引擎:
vcl_recv:用于接受和处理请求。当请求到达并成功接收后被调用,根据客户端的请求作出缓存策略实现安全策略,仅处理可以识别的http方法,且只缓存get和head的方法,不缓存用户特有的数据;
        可以使用下面的终止语句,即通过return()向Varnish返回的指示操作:
            pass:绕过缓存,即不从缓存中查询内容或不将内容存储至缓存中;

            pipe:不对客户端进行检查或做出任何操作,而是在客户端与后端服务器之间建立专用“管道”,并直接将数据在二者之间进行传送;此
            时,keep-alive连接中后续传送的数据也都将通过此管道进行直接传送,并不会出现在任何日志中;

            lookup:在缓存中查找用户请求的对象,如果缓存中没有其请求的对象,后续操作很可能会将其请求的对象进行缓存;

            error:由Varnish自己合成一个响应报文,一般是响应一个错误类信息、重定向类信息或负载均衡器返回的后端web服务器健康状态检查
            类信息;

            简单的来说就是如果可以缓存return lookup,如果不可以缓存return pass,如果无法识别请求return pipe;

vcl_pipe:对于不可识别的请求不经行任何处理,建立管道将请求直接发往后端主机处理;

vcl_hash:通过哈希计算生成url的hash值,并在缓存中寻找资源,找到了就是缓存命中并交给vcl_hit引擎处理,找不到就是缓存未命中,交给vcl_miss引擎处理;


vcl_hit:从缓存中找到了缓存对象,如果不缓存或缓存过期等情况(pass)就交由vcl_pass处理,缓存(deliver)就交由vcl_deliver处理;


vcl_miss:从缓存中未找到缓存对象,如果不缓存(pass)就交由vcl_pass处理,否则就到后端主机去取;


vcl_pass:用于将不缓存的请求直接传递至后端主机来获取资源并发送给客户端,而不进行任何缓存,每次都返回最新内容。


vcl_fetch:根据服务器端的响应作出缓存决策,如果是可缓存的资源就缓存并交由vcl_deliver处理,如果是不缓存的资源就不进行缓存并交由vcl_deliver处理;


vcl_deliver:将获取到的资源或报错封装成响应报文返回给客户端;


vcl_error:如果资源出现权限等问题而合成错误响应时,由varnish直接构建错误响应报文;

因此,其大致流程可分为如下:
可缓存->vcl_hash->cache命中->vcl_hit->vcl_deliver->response

可缓存->vcl_hash->cache未命中->vcl_miss->vcl_fetch->vcl_deliver->response

不可缓存(pass)->vcl_pass->vcl_fetch

无法识别请求(pipe)->vcl_pipe->response

错误->vcl_error->vcl_deliver->response

如果不理解可以对照VCL引擎介绍参考下图:


更加详细的处理流程:

因为varnish3.0到4.0版本有较大变化,所以贴上4.0版本的流程图:
先看看4.0版本的变化:
(1)、vcl配置文件需明确指定版本:即在vcl文件的第一行写上 vcl 4.0;
(2)、vcl_fetch函数被vcl_backend_response代替,且req.*不再适用vcl_backend_response;
(3)、后端源服务器组director成为varnish模块,需import directors后再在vcl_init子例程中定义;
(4)、自定义的子例程(即一个sub)不能以vcl_开头,调用使用call sub_name;
(5)、error()函数被synth()替代;
(6)、return(lookup)被return(hash)替代;
(7)、使用beresp.uncacheable创建hit_for_pss对象;
(8)、变量req.backend.healty被std.healthy(req.backend)替代;
(9)、变量req.backend被req.backend_hint替代;
(10)、关键字remove被unset替代;
官方文档:







4.0及4.1版本,VCL的引擎介绍:
client端:
vcl_recv:在请求开始时,请求完全被接受和解析时,重启或符合ESI时被调用。它决定了是否处理该请求,如何处理,也可能修改请求并决定如何进一步处理。

        通过以下关键字调用return()来结束:
        synth(status code, reason),它替换了error;
        Transition to vcl_synth with resp.status and resp.reason being preset to the arguments of synth().
    
        pass:Switch to pass mode. Control will eventually pass to vcl_pass.
        使用pass mode。处理跳转到vcl_pass

        pipe:Switch to pipe mode. Control will eventually pass to vcl_pipe.
        使用pipe mode。处理跳转到vcl_pipe

        hash:Continue processing the object as a potential candidate for caching. Passes the control over to vcl_hash.
        继续处理作为缓存的潜在候选对象。 处理跳转到vcl_hash

        purge:Purge the object and it's variants. Control passes through vcl_hash to vcl_purge.
        删除对象和它的变体。控制pass通过vcl_hash到vcl_purge

vcl_pipe:调度进入管道模式。此模式下,请求被直接转到后端,并将后端响应原样返回给客户端。基本上,varnish会分解一    个TCP协议,反复重排字节。pipe模式下的连接,vcl_pipe后没有其他VCL子进程会被调用;
        通过以下关键字调用return()来结束:
        synth(status code, reason)
   
        pipe

vcl_hash:在vcl_recv调用后为请求创建一个hash值时调用此函数;此hash值将作为varnish中查找缓存对象的key;
        通过以下关键字调用return()来结束:
        lookup:Look up the object in cache. Control passes to vcl_purge when coming from a purge return in vcl_recv. Otherwise control
        passes to vcl_hit, vcl_miss or vcl_pass if the cache lookup result was a hit, a miss or hit on a hit-for-pass object (object with
        obj.uncacheable == true), respectively.
        在缓存中查找对象.当vcl_recv返回一个purge return时,处理跳转到vcl_purge。另外,如果缓存查找结果为命中,未命中或命中但不缓存
        对象(obj.uncacheable == true)时,处理分别转移到vcl_hit,vcl_miss或vcl_pass;

vcl_hit:缓存对象查找成功时被调用。该命中对象可能不是新鲜的:它可能有个0值或负值的ttl用来宽限或保持时间;
        通过以下关键字调用return()来结束:
        deliver:Deliver the object. If it is stale, a background fetch to refresh it is triggered.
        发送缓存对象。如果缓存不是新鲜的,从后台获取后更新;

        miss:Synchronously refresh the object from the backend despite the cache hit. Control will eventually pass to vcl_miss.
        尽管缓存命中了也从后端同步更新对象.处理跳转到vcl_miss;

        pass:

        restart:Restart the transaction. Increases the restart counter. If the number of restarts is higher than max_restarts Varnish emits a guru 
            meditation error.
        重启事务。增加重启计时器值。如果重启次数超过max_restarts值,varnish会发出一个guru meditation错误;

        synth(status code, reason)

        fetch (deprecated):same as miss. Will get removed in a future version, triggers a VCL_Error log message.
        已弃用;

vcl_miss:在执行lookup指令后,在缓存中没有找到请求的内容或vcl_hit返回fetch时调用该方法,此函数可用于判断是否需  要从后端服务器获取内容,从哪一个后端获取内容;
        通过以下关键字调用return()来结束:
        fetch:Retrieve the requested object from the backend. Control will eventually pass to vcl_backend_fetch.
        从后端重新得到请求对象.处理转移到vcl_backend_fetch.;

        pass

        restart

        synth(status code, reason)

vcl_pass:调度进入pass模式。该模式下,请求被转到后端,后端的响应被转到该客户端上,但是并不会被缓存。在同一个客户 端连接上提交的后续请求正常处理;
        通过以下关键字调用return()来结束:
        fetch:pass模式下处理-发起一个后端请求;

        restart

        synth(status code, reason)

vcl_purge:操作purge执行后调用此函数,将避开所有他的变种;
        通过以下关键字调用return()来结束:
        restart

        synth(status code, reason)

vcl_deliver:在缓存中找到请求的内容发送给客户端前调用此方法;在除了vcl_synth结果之外其他任何对象被发送给客户端  前被调用;
        通过以下关键字调用return()来结束:

        deliver:将缓存对象发给客户端;

        restart

        synth(status code, reason)

vcl_synth:调用返回一个合成对象.合成对象在VCL中生成,不是从后端获取。它的body可通过使用synthetic()来构造;
        vcl_synth定义的对象不会被缓存且与vcl_backend_error定义的对象相反
        A vcl_synth defined object never enters the cache, contrary to a vcl_backend_error defined object, which may end up in cache.
        通过以下关键字调用return()来结束:
        deliver:直接将vcl_synth定义的对象发给客户端而不调用vcl_deliver;

        restart


后端:
vcl_backend_fetch:在向后端发送请求前调用。在此子进程中,请求到达后端前可修改该请求;
        通过以下关键字调用return()来结束:

        fetch:从后端获取对象;

        abandon:丢弃后端请求。除非该后端请求是后端获取的,否则处理跳转到vcl_synth并且客户端返回一个503状态码;

vcl_backend_response:获得后端主机的响应后被调用.
        对于304响应,varnish会在调用vcl_backend_response前修改beresp:
                如果gzip状态改变,Content-Encoding会被清除且Etag被削弱;
                任何没有出现在304响应报文中的header是从现有缓存对象中复制来的.如果出现在现有的缓存对象中Content-Length会被复制,否则丢弃;
                状态被设置为200;

        beresp.was_304标志着此条件响应处理已发生。

        注意:后端条件请求和客户端条件请求是独立的,所以,不管后端请求是否是有条件的,客户端都会接收304响应;

        通过以下关键字调用return()来结束:
        deliver:对于304响应,创建一个更新的缓存对象。否则,从后端获取对象的body且开始发送给任何等待的客户端请求,也可能并行发送;

        abandon:丢弃后端请求。除非该后端请求是后端获取的,否则处理跳转到vcl_synth并且客户端返回一个503状态码;

        retry:重试后端事务。增加重试计数器的值,如果重试次数超过max_retries,那么处理会跳转到vcl_backend_error上;

vcl_backend_error:如果从后端获取失败或超出max_retries的值时被调用。
        VCL中产生合成对象,这些对象的body通过使用synthetic()函数构建;

        通过以下关键字调用return()来结束:

        deliver:发送或可能缓存由vcl_backend_error定义的如果是从后端获取的对象,也被称为"backend synth";

        retry

vcl.load或vcl.discard
vcl_init:当VCL加载时被调用,经常用于初始化varnish模块(VMODs);
        通过以下关键字调用return()来结束:

        ok:正常返回,VCL继续加载;

        fail:终止加载VCL;

vcl_fini:当所有的请求退出VCL且VCL被弃用时被调用。常用于清理VMODs;
        通过以下关键字调用return()来结束:

        ok:正常返回,VCL被弃用;

官方说明:

7.varnish常用变量和内置函数
这是4版本的:
http://blog.chinaunix.net/uid-30212356-id-5706708.html

下面是老版本的,可以相互对照一下:
    7.1 在任何引擎中均可使用:
         now:获取当前系统当前时间
        .host:获取当前主机名和ip地址
        .port:后端服务器名称和端口

    7.2 用于处理请求阶段:
        client.ip,server.hostname, server.ip, server.port :不作解释

        req.request:请求方法

        req.url:请求的URL

        req.proto:HTTP协议版本

        req.backend:用于服务此次请求的后端主机;

        req.backend.healthy:后端主机健康状态;

        req.http.HEADER:引用请求报文中指定的首部;

        req.can_gzip:客户端是否能够接受gzip压缩格式的响应内容;

        req.restarts:此请求被重启的次数;

    7.3 varnish向backend主机发起请求前可用的变量

        bereq.request:请求方法

        bereq.url:请求url

        bereq.proto:HTTP协议版本

        bereq.http.HEADER:调用服务此次请求的后端主机的报文首部

        bereq.connect_timeout:等待与beckend建立连接的超时时长

    7.4 backend主机的响应报文到达本主机(varnish)后,将其放置于cache中之前可用的变量

        beresp.do_stream:流式响应(接收一个请求,响应一个请求)

        beresp.do_gzip:是否压缩之后再存入缓存;

        beresp.do_gunzip:如果从后端收到压缩格式的报文,是否解压缩在存放下来

        beresp.http.HEADER:获取httpd的首部信息

        beresp.proto:HTTP协议版本

        beresp.status:响应状态码

        beresp.response:响应时的原因短语

        beresp.ttl:响应对象剩余的生存时长,单位为秒钟;

        beresp.backend.name:此响应报文来源backend名称;

        beresp.backend.ip:获取后端响应ip

        beresp.backend.port:获取后端响应端口

        beresp.storage:强制varnish将缓存存储到缓存后端

    7.5缓存对象存入cache之后可用的变量

        obj.proto:响应时使用的协议

        obj.status:响应时使用的状态码

        obj.response:服务器返回响应报文的状态码

        obj.ttl:缓存对象生存时长

        obj.hits:缓存对象被用作响应时的次数

        obj.http.HEADER:调用对应的响应报文

   7.6 在决定对请求键做hash计算时可用的变量

        req.hash:指明把什么作为hash的键,作为缓存的键

    7.7 在为客户端准备响应报文时可用的变量

        resp.proto:指明使用什么协议响应

        resp.status:执行响应状态吗

        resp.response:返回响应的状态码

        resp.http.HEADER:调用响应报文状态码

8.varnish的后端存储
varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。
后端存储的类型包括:
    file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);

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

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

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


选择使用合适的存储方式有助于提升系统性,从经验的角度来看,建议在内存空间足以存储所有的缓存对象时使用malloc的方法,反之,file存储将有着更好的性能的表现。然而,需要注意的是,varnishd实际上使用的空间比使用-s选项指定的缓存空间更大,一般说来,其需要为每个缓存对象多使用差不多1K左右的存储空间,这意味着,对于100万个缓存对象的场景来说,其使用的缓存空间将超出指定大小1G左右。另外,为了保存数据结构等,varnish自身也会占去不小的内存空间。

为varnishd指定使用的缓存类型时,-s选项可接受的参数格式如下:
malloc[,size] 或

file[,path[,size[,granularity]]] 或

persistent,path,size {experimental}

注意:file中的granularity用于设定缓存空间分配单位,默认单位是字节,所有其它的大小都会被圆整。

附:缓存相关的HTTP首部
http://blog.chinaunix.net/uid-30212356-id-5701499.html

参考资料:






http://my.oschina.net/jean/blog/189910
上一篇:《Ceph分布式存储实战》可以让你成长为自主存储系统的构建和维护者
下一篇:Oracle操作系统认证方式