缓存能分担许多web访问的压力,缓存为王的今天,缓存犹为重要.

varnish 是一款非常轻量级,也很强大的一款提供缓存服务的应用.

varnish的配置是通过VCL缓存策略工具实现的,这个工具是一种简单的编程语言,用户可以自定义变量、

有好几个内置的函数和变量,但是它的函数不支持接受参数,而且没有返回值。使用VCL编写的缓存策略通常保存至.vcl文件中,

其需要编译成二进制的格式后才能由varnish调用。事实上,整个缓存策略就是由几个特定的子例程如vcl_recv、

vcl_fetch等组成,它们分别在不同的位置(或时间)执行,如果没有事先为某个位置自定义子例程,varnish将会执行默认的定义。

   VCL策略在启用前,会由management进程将其转换为C代码,而后再由gcc编译器将C代码编译成二进制程序,

所以varnish的安装和运行依赖于gcc库。编译完成后,management负责将其连接至varnish实例,

即子进程。编译时会检查语法是否有误,避免了装载错误语法的VCL。一但编译完成并且没有语法错误就会被装载,

同时可以保存好几份配置,当你觉得之前的配置策略更科学时,调用之前的配置即可. 只要调用库中的配置策略,就可以使规则生效,

无需重启或者reload.所以修改配置策略的代价很小。配置的策略只有在varnish重启时才会清除,当然,也可以手动清理,

可以使用varnishadm的vcl.discard命令完成。

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

(1)file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果内存够大,条件允许); 可以指定其所保存的位置,大小,以及缓存分配粒度, 即每次分配的大小,直到size大小为止不再分配.使用方法  file[,path[,size[,granularity]]]

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

(3)persistent(experimental):与file的功能相同,但可以持久存储数据(即重启varnish数据时不会被清除);仍处于测试期,悲催,好不容易有一个能持久保存的,还不稳定;

varnish的进程分两类,

 

一, 管理进程, (master进程),其工作职责如下

1,读入配置文件

2,调用合适的存储类型 ,varnish支持将缓存写入磁盘

3,创建/读入相应大小的缓存文件 (但是这个功能还处于测试阶段,建议暂时不要使用)

4,初始化管理,将缓存文件结构空间关联到存储结构体 ,以待分配,

5,fork出多个空闲子进程并监控各child进程 

二,工作进程   (child子进程)

1,将前面打开的存储文件整个mmap到内存

2,创建并实始化空闲的结构体,用来存储缓存对象,

3,由诸多线程各司其职负责完成相关的工作:

主进程 fork 子进程,主进程等待子进程的信号,子进程退出后,主进程重新启动子进程,子进程生成若干线程。

Accept 线程:接受请求,将请求分配到空闲子进程上,并让空闲的work线程响应用户请求

Work线程: work线程有多个,从队列领取请求,并对请求处理,处理完成后,继续领取下一个请求进行处理

work线程处理时,会读取该请求的url, 以此判定本地缓存中是否有该缓存对象命中,如果命中直接构建响应报文,

如果没有,则去上游服务器查找数据,并缓存至本地再构建响应报文响应请求.

Epoll 线程: 一个请求处理称作一个 session,在 session 周期内,处理完请求后,会交给 Epoll 处理,监听是否还有事件发生。

Expire 线程:对于缓存的对象,根据过期时间,组织成二叉堆,该线程周期检查该堆的根,处理过期的文件。

线程之间的关系:

      worker:处理用户请求

      accept: 接收用户请求

当缓存空间耗尽:

    需要清理缓存空间了,可以使用LRU算法清理,(LRU指最近最少使用的)

 

查看一下varnish的内置函数 

1、vcl_recv函数

用于接收和处理请求,当请求到达并成功接收后被调用,通过判断请求的数据来决定如何处理请求。  
此函数一般以如下几个关键字结束:    
pass:表示进入pass模式,把请求控制权交给vcl_pass函数。    
pipe:表示进入pipe模式,把请求控制权交给vcl_pipe函数。

lookup: 表示进入hash,把请求控制权交给vcl_hash函数.  
error code [reason]:表示返回“code”给客户端,并放弃处理该请求,“code”是错误标识,例如200、405等,“reason”是错误提示信息。    
2、vcl_pipe函数    
此函数在进入pipe模式时被调用,用于将请求直接传递至后端主机,在请求和返回的内容没有改变的情况下,将不变的内容返回给客户端,直到这个链接关闭    
此函数一般以如下几个关键字结束:    
error code [reason] 
pipe    
3、vcl_pass函数    
此函数在进入pass模式时被调用,用于将请求直接传递至后端主机,后端主机应答数据后送给客户端,但不进行任何缓存,在当前连接下每次都返回最新的内容,  关键字结束:    
error code [reason] 
pass    
4、lookup    
表示在缓存里查找被请求的对象,并且根据查找的结果把控制权交给函数vcl_hit或者函数vcl_miss    
5、vcl_hit函数    
在执行lookup指令后,如果在缓存中找到请求的内容,将自动调用该函数    
此函数一般以如下几个关键字结束:    
deliver:表示将找到的内容发送给客户端,并把控制权交给函数vcl_deliver    
error code [reason] 
pass    
6、vcl_miss函数    
在执行lookup指令后,如果没有在缓存中找到请求的内容时自动调用该方法,此函数可以用于判断是否需要从后端服务器取内容    
此函数一般以如下几个关键字结束:    
fetch:表示从后端获取请求的内容,并把控制权交给vcl_fetch函数    
error code [reason] 
pass :去后端主机取数据时,额外再做一些操作    
7、vcl_fetch函数    
在从后端主机更新缓存并且获取内容后调用该方法,接着,通过判断获取的内容来决定是否将内容放入缓存,还是直接返回给客户端    
此函数一般以如下几个关键字结束:    
error code [reason]    
pass    可以不缓存    
deliver 也可以缓存    
8、vcl_deliver函数    
在缓存中找到请求的内容后,发送给客户端前调用此方法。此函数一般以如下几个关键字结束:    
error code [reason] 
deliver    响应客户端请求    
9、vcl_timeout 函数    
此函数在缓存内容到期前调用。一般以如下几个关键字结束:    
discard:从缓存中清除该内容。    
fetch    也可以去后端主机取数据    
10、vcl_discard函数    
在缓存内容到期后或缓存空间不够时,自动调用该方法,一般以如下几个关键字结束:    
keep:表示将内容继续保留在缓存中    
discard:从缓存中清除该内容。

  varnish的配置文件:vcl

    用于定义后端节点

    取缓存对象

    是否缓存

附,图1 ==>varnish工作机制图解

p_w_picpath

varnish 自己监听在哪个端口,如何缓存等,是通过varnish的命令行接口参数来定义的

[root@localhost~]# vim /etc/sysconfig/varnish => 查看配置文件

其配置文件默认是使用 Alternative 3, Advanced configuration高级模式配置的.  

NFILES=131072 => 所能够打开的最大文件数    MEMLOCK=82000 => 用多大内存空间保存日志信息    DAEMON_COREFILE_LIMIT="unlimited" => 进程核心转储所使用的内存空间,unlimited表示无上限    RELOAD_VCL=1 => 重新启动服务时是否重新读取VCL并重新编译的    VARNISH_VCL_CONF=/etc/varnish/default.vcl => 默认读取的VCL文件    VARNISH_LISTEN_PORT=80 => 监听的端口,默认监听6081    VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 => 管理接口监听的地址    VARNISH_ADMIN_LISTEN_PORT=6082 => 管理接口监听的端口 ,这个管理接口可以控制varnish的工作模式和特性    VARNISH_SECRET_FILE=/etc/varnish/secret => 使用的密钥文件    VARNISH_MIN_THREADS=1 => 最少线程数    VARNISH_MAX_THREADS=1000 => 最大线程数    VARNISH_THREAD_TIMEOUT=120 => 线程的超时时间    VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin => 基于文件存储时的文件路径    VARNISH_STORAGE_SIZE=1G => 存储文件的大小    VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}" => 存储的文件格式
VARNISH_MALLOC="malloc,128m"  =>如,想改成使用malloc的方式存储内存可以定义一个变量其实也就是定义和调用的关系 ,甚至是可以在后面相关的调用 -s 参数后,直接跟上要修改成的属性.
VARNISH_TTL=120 => 联系后端服务器的超时时间
DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \   -f ${VARNISH_VCL_CONF} \    -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \    -t ${VARNISH_TTL} \    -w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} \    -u varnish -g varnish \    -S ${VARNISH_SECRET_FILE} \    -s ${VARNISH_STORAGE}" => 使用定义的各高级配置的参数
-s  ${VARNISH_MALLOC} =>前面红字定义的,后面调用

有了以上的知识,我们开始配置一个varnish缓存,并高度上游服务器的一种组织架构,拓普图如下.

clipboard

[root@localhost~]# vim /etc/sysconfig/varnish

VARNISH_LISTEN_PORT=80 =>修改当前的监听端口. 其它的参数都使用默认

# service varnish start    =>启动varnish服务.

[root@php5_6 ~]# ps aux|grep varnish    
root 2052 0.0 0.3 143684 3476 pts/0 S+ 13:17 0:00 vim /etc/varnish/default.vcl    
root 2094 0.0 0.3 143684 3500 pts/1 S+ 13:45 0:00 vim /etc/sysconfig/varnish    
root 2214 0.0 0.1 112300 1192 ? Ss 14:22 0:00 /usr/sbin/varnishd -P /var/run/varnish.pid -a :80 -f /etc/varnish/default.vcl -T 127.0.0.1:6082 -t 120 -w 50,1000,120 -u varnish -g varnish -S /etc/varnish/secret -s file,/var/lib/varnish/varnish_storage.bin,1G    
varnish 2215 0.7 0.4 2273404 4572 ? Sl 14:22 0:01 /usr/sbin/varnishd -P /var/run/varnish.pid -a :80 -f /etc/varnish/default.vcl -T 127.0.0.1:6082 -t 120 -w 50,1000,120 -u varnish -g varnish -S /etc/varnish/secret -s file,/var/lib/varnish/varnish_storage.bin,1G =>可以看到, varnish的缓存文件存放的位置以及大小    
root 2364 0.0 0.0 103252 824 pts/3 S+ 14:26 0:00 grep varnish

# cp /etc/varnish/default.vcl    /etc/varnish/cl1.vcl     =>将默认的vcl配置拷贝一份,

# vim /etc/varnish/cl1.vcl    =>编辑策略一这个配置文件

backend default {
  .host = "172.16.26.6";
  .port = "80";
}
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;  如果有 req.http.X-Forwarded-For信息,为其添加上请求的客户端地址
        } else {
            set req.http.X-Forwarded-For = client.ip; 没有req.http.X-Forwarded-For信息,将其信息内容改为请求的客户端地址
        }
     }
     if (req.request != "GET" &&
       req.request != "HEAD" &&
       req.request != "PUT" &&
       req.request != "POST" &&
       req.request != "TRACE" &&
       req.request != "OPTIONS" &&
       req.request != "DELETE") {
         /* Non-RFC2616 or CONNECT which is weird. */
         return (pipe);    =>如果不满足以上的所有请求方法,无法识别,直接交给pipe处理,控制权转交给vcl_pipe,直接到上游服务器去了
     }
     if (req.request != "GET" && req.request != "HEAD") {
         /* We only deal with GET and HEAD by default */
         return (pass);    =>如果非GET, HEAD请求方式,说明请求的信息无需要缓存,直接交给pass,控制权转交给 vcl_pass处理
     }
     if (req.http.Authorization || req.http.Cookie) {
         /* Not cacheable by default */
         return (pass);  =>这和用户认证相关,也不缓存, 交给pass,控制权转交给 vcl_pass处理
     }
     return (lookup);  =>交给 vcl_hash处理
}

[root@php5_6 tmp]# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082  =>连接到本机的varnish的管理接口上    
200    
-----------------------------    
Varnish Cache CLI 1.0    
-----------------------------    
Linux,2.6.32-431.el6.x86_64,x86_64,-sfile,-smalloc,-hcritbit    
varnish-3.0.4 revision 9f83e8f    
Type 'help' for command list.    
Type 'quit' to close CLI session.    
varnish> vcl.load cl1 cl1.vcl    =>使用vcl.load 来读取配置好的策略文件, 这个读取会使用gcc编译器编译为二进制代码 如果中间的配置有误,则会报错,停止编译

200

VCL compiled.

varnish> vcl.use cl1  装载vcl应用策略是由management将其连接到子进程中.

200

clipboard[1]

此时我们尝试访问 varnish 缓存服务器,它就会给我们代理到上游服务器上去了.

我们想查看当前使用的缓存策略,可以使用vcl.show 策略名 来查看.

例如: varnish> vcl.show cl1

如果想丢弃该策略,也很简单,使用另一个vcl策略后,

varnish> vcl.discard cl1   =>用discard命令就可以清除一个策略了

varnish有许多内置的变量, 不同的变量,有自己特定的使用上下文.

 p_w_picpath

有了以上的认识,我们可以定制更多我们所需要的缓存特性了.比如说,我们想知道这次访问的缓存是否命中,怎么办呢?

由以上的varnish工作机制流程图可以看到, 任务数据的响应都得经过deliver,那么我们就开启 vcl_deliver方法,

并由上表得知, deliver方法中可以使用 boj.hits变量

sub vcl_deliver {
    if (obj.hits > 0) {    =>如果访问命中次数大于0,说明命中了,给其响应报文添加一个http首部,
        set resp.http.X-Cache = "HIT via" + " " + server.hostname;
    } else {
        set resp.http.X-Cache = "MISS via" + " " + server.hostname;
    }
    return (deliver);
}

重新装载配置后访问,在调试器里能看到,http添加了请求首部 x-cache ,由于是第一次访问 其值是我们定义的

MISS via php5_6.cc           

clipboard[2]

第二次访问时,就命中了.

clipboard[3]

有一些页面,我们是不希望缓存的,那么我们又应该怎么实现呢?

比如说,我们不希望 /test/test.html被缓存, 试试看怎么实现.

我们可以在接收到用户请求的开始,就判段要访问的url,如果满足条件就直接到上游服务器取数据.

修改配置文件,我们需要在vcl_recv 函数的一开始就判断url是否包含有相应的路径.

sub vcl_recv {
    if (req.url ~ "^/test/test.html$") {    =>如果是访问指定的路径不走缓存,直接去上游服务器取数据.
        return(pass);
    }
}

这样设定了之后,访问相应的路径怎么着也缓存不了了.

clipboard[4]

req.request 有一种方法,叫作PURGE. 这是一种清理缓存的方法.

我们不能让所有人来都能清除缓存.所以我们必须要限定只有对应的权限控制列表才能清除缓存.

acl purgers {        =>我们定义一个访问列表, 只有在这个访问列表内的ip才有权限清除缓存
    "127.0.0.1";
    "172.16.26.6"; 
}
sub vcl_recv {    =>在此方法一开始就判断是否是 PURGE 类型的请求
    if (req.request == "PURGE") {    =>如果是PURGE请求,且客户端请求的ip不在权限列表内,则响应一个错误状态码和信息
        if (!client.ip ~ purgers) {
            error 405 "Method not allowed";
        }
        return (lookup); =>去查询缓存了.
    }
}
sub vcl_hit {
    if (req.request == "PURGE") {    =>如果命中了缓存,并且是PURGE请求类型,
        purge;        =>调用清除缓存的 purge方法
        error 200 "Purged"; =>用error函数返回一个正确的响应码,响应信息
    }
}
sub vcl_miss {
    if (req.request == "PURGE") { 
        purge;
        error 404 "Not in cache"; =>说明缓存不存在
    }
}
sub vcl_pass {
    if (req.request == "PURGE") {    =>PURGE方法的请求基本上不会到这里来,
        error 502 "PURGE on a passed object";    =>万一到了,响应一个错误码和错误信息回去.
    }
}

测试,经过一次访问后,存在缓存数据了,再次使用 -X 指定 PURGE方法 请求清除缓存,,

因为请求发起的ip在访问权限列表中,所以和预想的一样,请求成功,得到 200的状态码,并提示缓存清理成功

clipboard[5]

varnish还可以探测后端主机的健康状态,如果探测不到,会把它踢除, 后来探测到了,还可以把它加回来.
backend webapache1 {       
    .host = "172.16.26.1";        
    .probe = {           
        .url = "/.healthtest.html";           
        .interval = 2s;     =>隔几秒探测一次          
        .window = 5;     =>至少探测次数           
        .threshold = 2;     =>成功几次则认为后端主机是OK的      
    }    
}

如何使用多个后端主机呢?

backend webapache1 {
  .host = "172.16.26.1";
  .port = "80";
}
backend webapache5 {
  .host = "172.16.26.5";
  .port = "80";
}
director webservers random {
  .retries = 5;
  {
    .backend = webapache1;
    .weight = 2;
  }
  {
    .backend = webapache5;
    .weight = 2;
  }
}
sub vcl_recv {
    set req.backend=webservers;  ==>注意,需要在vcl_recv中指定使用的backend为你定义的
                                     director名称.这样才能有效调用,不然会报错.
}

clipboard[6]

此时重载vcl配置, 访问 就可以实现随机调用上游主机了.

我们还可以在varnish上实现动静分离

if (req.url ~ ".php$") {    =>如果url中,以.php结尾的,让其使用特定的backedn -> webapache1

        set req.backend=webapache1;

        return(pass);    =>并且不缓存

    }