Linux之Varnish

2016-03-08  本文已影响767人  魏镇坪

缓存的基础知识

1、程序本身具有局部性

2、命中率

3、缓存系统的特性

4、缓存系统一般处理步骤

5、新鲜度检测机制

6、HTTP缓存相关首部

缓存时需要考虑到的特殊首部
Authorization:跟授权相关的首部
cookie:用户识别相关的首部
Vary:accept-encoding:所能接受的字符编码格式

以上三种首部未特性情况下是不予缓存

通常与缓存相关的方法:
1、GET
2、HEAD

7、常见的缓存服务开源解决方案

Varnish

varnish对比squid的优点

varnish对比squid的缺点

varnish的工作进程特性


* Management(主进程)
* 实现应用新的配置
* 编译VCL
* 监控Varnish的子进程(其management每隔几秒进行子进程探测,如较长时间没有回应探测它将重启一个子进程)
* 初始化varnish
* 提供命令行接口
* Child/Cache
* accept : 接收新的连接请求,交由worker线程处理
* worker : 用于处理并响应用户请求
* expiry : 管理过期缓存,从缓存中清理过期的Cache
* Vcl compiler
* 把配置文件编译成VCL格式
* C compiler
* C编译器,vcl compiler调用c compiler
* 日志
    shared memory log,共享内存日志大小默认一般为90M+,分为两部分组成,前一部分为计数器,后一部分请求响应的相关数据,日志保存在一个共享的内存空间,只能保存最近最新的日志,需要使用工具,把日志不断的导出,以实现长期保存       
    * `varnishlog` : 其以守护进程方式运行,需要将其重启才会把日志导入到本地磁盘,类似于httpd日志的comm格式
    * `varnishncsa` : 其与varnishlog类似,但日志的格式与httpd的combind格式类似
varnish使用单进程多线程模型,其worker stats类似于一个线程池,所有的资源将整合在一个工作区中,以降低线程在申请或修改内存时,出现的竞争的可能性,当多个线程同时访问同个资源时,工作区对资源以施加锁保证用户的请求在资源争用时,后来的线程处于等待状态,以协调线程的工作。
    }
    
* 不支持循环
* 受状态引擎的变量,变量的可调用位置与state engine有密切相关性
* 支持终止语句,使用ruturn()返回一个action,其没有返回值
* 可自定义变量
* //,#,/* */: 用于注释,会被编译器忽略
* “域”专用,只能一个域有效
* 操作符: `=,==,~,!,&&,||`
* 条件判断语句的写法:

```
单分支:
if (condition) {
    ....;
} else {
    ....;
}

多分支:
if (condition) {
    ...;
} elseif {
    ...;
} else {
    ...;
}
```
* 变量赋值:`set name = value`
* 撤消变量的值:`unset name`

安装及配置Varnish

Centos 6

Centos 7

varnish主配置文件参数说明

注意:varnish配置文件修改不应重启服务,而是手动加载配置文件

Varnish管理工具

varnishd命令

其可以在/etc/sysconfig/varnish的配置文件中完成

2、varnishadm命令

其是通过连接varnish服务端,可以实现varnish服务的管理操作,实现动态加载VCL的配置文件

3、Varnishtop命令

内存日志区域查看工具

4、varnishstat命令

varnish的运行统计数据

5、varnishlog命令

启动以comm格式记录日志到磁盘

6、varnishncsa命令

启动以combind格式记录日志到磁盘

VCL Engine

vcl engine是varnish通过VCL配置语言定义的缓存策略,state engine之间有相关性,上级engine通过return指明下级engine,常用的引擎(varnish version 3),如下:

Vcl engine 完整的工作流程示意图(1)

Vcl engine 完整的工作流程示意图(2)

Vcl engine常见工作流程

1、查询缓存未命中的工作流
2、查询缓存命中的工作流
3、未识别的HTTP方法工作流
4、不予缓存的工作流
5、完整的工作流
6、状态引擎说明

VCl引擎中常用变量

可用于recv,hash,pipe,pass引擎中。

* `client.ip` : 客户端IP地址
* `server.hostname` : 服务器的主机名(缓存服务器)
* `server.ip` : varnish服务器的IP
* `server.port` : varnish服务器的端口
* `req.request` : 客户端的请求方法
* `req.url` : 客户端请求的URL
* `req.proto` : http协议版本
* `req.backend` : 用于服务此次请求的后端主机
* `req.backend.healthy` : 后端主机的健康状态
* `req.http.HEADER` : 引用请求报文中指定的首部,哪req.http.host
* `req.hash_always_miss`
* `req.hash_ignore_busy`
* `req.can_gzip` : 客户端是否能够接受GZIP压缩格式的响应内容
* `req.restarts` : 此请求被重启的次数

vcl定义后端服务器主机

定义的后端主机需要在recv中调用,必须会出错

定义后端服务器集群

varnish中可以使用director指令将一个或多个近似的后端主机定义成一个逻辑组,并可以指定其调度方法(也叫挑选方法)来轮流将请求发送至后端backend主机上,不同的director可以使用同一个后端主机,而某director也可以使用“匿名”后端主机(在director中直接定义),每个director都必须有其专用名,且在定义后必须在vcl中进行调用,VCL中任何可以指定后端主机的位置均可按需将其替换为调用某已定义的director

varnish检测后端主机的健康状态

varnish可以检测后端主机的健康状态,在判定后端主机失效时能自动将其从可用后端主机列表中移除,而一旦其重新变得可用还可以自动将其设定为可用,为了避免误判,varnish在探测后端主机的健康状态发生转变时(比如某次检测时某后端主机突然成为不可用状态),通常需要连续执行几次探测均为新状态才将其标记为转换后的状态

每个后端服务器当前探测的健康状态探测方法通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的backend_health查看或varnishadm的debug.health查看

```
示例1:
    backend web1 {
        .host = "www.zhenping.me";
        .probe = {
            .url = "/.healthtest.html";
            .interval = 1s;
            .window = 5;
            .threshold = 2;
        }
    }
    
示例2:
    可以将probe的机制定义为一个代码块,在backend中引用
    
    probe PRO_NAME {
        ....;
    }
    
    backend NAME {
        ...;
        .probe = PRO_NAME;
    }
    
```

移除单个缓存对象

purge用于清理缓存中的某特定对象及其变种(variants),因此,在有着明确要修剪的缓存对象时可以使用此种方式。HTTP协议的PURGE方法可以实现purge功能,不过,其仅能用于vcl_hit和vcl_miss中,它会释放内存工作并移除指定缓存对象的所有Vary:-变种,并等待下一个针对此内容的客户端请求到达时刷新此内容。另外,其一般要与return(restart)一起使用。下面是个在VCL中配置的示例

acl purgers {
            "127.0.0.1";
            "192.168.0.0"/24;
        }

        sub vcl_recv {
            if (req.request != "GET" &&
                req.request != "HEAD" &&
                req.request != "PUT" &&
                req.request != "POST" &&
                req.request != "TRACE" &&
                req.request != "OPTIONS" &&
                req.request != "DELETE" &&
                req.request != "PURGE") {    #需要添加PURGE方法,以不被送到PIPE引擎处理
                /* Non-RFC2616 or CONNECT which is weird. */
                return (pipe);
            }
            if (req.request != "GET" && req.request != "HEAD" && req.request != "PURGE") { #也需要添加PURGE方法不被送到PASS引擎,以确保PURGE方法可以到达HASH引擎
                /* We only deal with GET and HEAD by default */
                return (pass);
            }
            if (req.request == "PURGE") {
                if (!client.ip ~ purgers) {
                    error 405 "Method not allowed";
                }
                return (lookup);
            }
        }
        sub vcl_hit {
            if (req.request == "PURGE") {
                purge;
                error 200 "Purged";
            }
        }
        sub vcl_miss {
            if (req.request == "PURGE") {
                purge;
                error 404 "Not in cache";
            }
        }
        sub vcl_pass {
            if (req.request == "PURGE") {
                error 502 "PURGE on a passed object";
            }
        }

        客户端在发起HTTP请求时,只需要为所请求的URL使用PURGE方法即可,其命令使用方式如下:
        # curl -I -X PURGE http://varniship/path/to/someurl

使用示例1

#drop any cookies sent to wordpress
        sub vcl_recv {
            if(!(req.url ~ “wp-(login|admin)”)) {
                unset req.http.cookie;
            }
        }

使用示例2

sub vcl_recv {
            if (req.http.host ~ “(?i)^(www.)?zhenping.me$”) {
                set req.http.host = “www.zhenping.me”;
                set req.backend = www;
            } elseif (req.http.host ~ “(?i)^images.zhenping.me$”) {
                set req.backend = images;
            } else {
                error 404 “Unknown virtual host”;
            }
        }

使用示例3

sub vcl_recv {
    if (req.http.User-Agent ~ "iPad" || req.http.User-Agent ~ "iPhone" || req.http.User-Agent ~ "Android") {
        set req.http.X-Device = "mobile";
    } else {
        set req.http.X-Device = "Desktop";
    }
}

使用示例4(测试是否命中缓存)

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

使用示例5(隐藏后端服务软件版本)

 sub vcl_deliver {
       
        if (resp.http.Server) {
                unset resp.http.Server;   #出于安全考虑,需要将后端所使用的软件名称和版本隐藏起来
        }
     return (deliver);
 }

生产环境实例

acl purge {
          "localhost";
          "127.0.0.1";
          "10.1.0.0"/16;
          "192.168.0.0"/16;
        }

        sub vcl_hash {
          hash_data(req.url);
          return (hash);
        }

        sub vcl_recv {
          set req.backend = shopweb;
        #  set req.grace = 4h; 
          if (req.request == "PURGE") {
            if (!client.ip ~ purge) {
              error 405 "Not allowed.";
            }
            return(lookup);
          }
          if (req.request == "REPURGE") {
            if (!client.ip ~ purge) {
              error 405 "Not allowed.";
            }
            ban("req.http.host == " + req.http.host + " && req.url ~ " + req.url);
            error 200 "Ban OK";
          }
          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;
            }
          }
          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);
          }
          if (req.request != "GET" && req.request != "HEAD") {
            /* We only deal with GET and HEAD by default */
            return (pass);
          }
          if (req.http.Authorization) {
            /* Not cacheable by default */
            return (pass);
          }
         

          if ( req.url == "/Heartbeat.html" ) {
            return (pipe);
          }
          if ( req.url == "/" ) {
            return (pipe);
          }
          if ( req.url == "/index.jsp" ) {
            return (pipe);
          }

          if (req.http.Cookie ~ "dper=") {
            return (pass);
          }
          if (req.http.Cookie ~ "sqltrace=") {
            return (pass);
          }
          if (req.http.Cookie ~ "errortrace=") {
            return (pass);
          }
        #   if ( req.request == "GET" && req.url ~ "req.url ~ "^/shop/[0-9]+$" ) {
          if ( req.url ~ "^/shop/[0-9]+$" || req.url ~ "^/shop/[0-9]?.*" ) {
            return (lookup);
          }

         if ( req.url ~ "^/shop/(\d{1,})/editmember" || req.url ~ "^/shop/(\d{1,})/map" || req.url ~ "^/shop/(\d+)/dish-([^/]+)" ) {
            return (lookup);
          } 

          return (pass);
        #   return (lookup);
        }

        sub vcl_pipe {
          return (pipe);
        }

        sub vcl_pass {
          return (pass);
        }

        sub vcl_hit {
          if (req.request == "PURGE") {
            purge;
            error 200 "Purged.";
          }
          return (deliver);
        }

        sub vcl_miss {
          if (req.request == "PURGE") {
            error 404 "Not in cache.";
          }
        #   if (object needs ESI processing) {
        #     unset bereq.http.accept-encoding;
        #   }
          return (fetch);
        }


        sub vcl_fetch {
          set beresp.ttl = 3600s;
          set beresp.http.expires = beresp.ttl;
          #set beresp.grace = 4h;
        #   if (object needs ESI processing) {
        #     set beresp.do_esi = true;
        #     set beresp.do_gzip = true;
        #   }

          if ( req.url ~ "^/shop/[0-9]+$" || req.url ~ "^/shop/[0-9]?.*" ) {   
            set beresp.ttl = 4h;
          }

         if ( req.url ~ "^/shop/(\d{1,})/editmember" || req.url ~ "^/shop/(\d{1,})/map" || req.url ~ "^/shop/(\d+)/dish-([^/]+)" ) {
             set beresp.ttl = 24h;
          } 

          if (beresp.status != 200){
            return (hit_for_pass);
          }
          return (deliver);
        }

        sub vcl_deliver {
          if (obj.hits > 0){
            set resp.http.X-Cache = "HIT";
          } 
          else {
            set resp.http.X-Cache = "MISS";
          }
          set resp.http.X-Powered-By = "Cache on " + server.ip;
          set resp.http.X-Age = resp.http.Age;
          return (deliver);
        }

        sub vcl_error {
          set obj.http.Content-Type = "text/html; charset=utf-8";
          set obj.http.Retry-After = "5";
          synthetic {""} + obj.status + " " + obj.response + {""};
          return (deliver);
        }

        sub vcl_init {
          return (ok);
        }

        sub vcl_fini {
          return (ok);
        }
上一篇 下一篇

猜你喜欢

热点阅读