缓存的基础知识
1、程序本身具有局部性
时间局部性

过去访问到的数据,也有可能被两次访问

空间局部性

一个数据被访问到时,离它最近的文件可能马上也会被访问


2、命中率
文档命中率

从文档个数进行衡量

字节命中率

从内容大小进行衡量


3、缓存系统的特性
缓存对象

有生命周期,且是定期清理的

缓存空间耗尽

使用LRU(最近最少使用算法)或者MRU算法进行缓存项清理

不可缓存项

用户私有数据


4、缓存系统一般处理步骤
接收请求
解析请求

提取请求的URL及各种首部

查询缓存
新鲜度检测
创建响应报文
发送响应报文
记录日志


5、新鲜度检测机制
过期日期
HTTP/1.0 : expires(其是一个绝对时间)

HTTP/1.1 : Cache-Control: max-age=600(其是一个相对时间)

产效性再验证(revalidate)

1) 如果原始内容未改变,则仅响应首部信息,响应码为304(not modified)
2) 如果原始内容发生了改,则正常响应,响应码为200
3)如果原始内容消失,则响应404,此时缓存中的cache object也应该被删除

条件式请求方式
If-Modified-Since : 基于请求内容的时间戳作验证
If-unmodified-Since
If-Match

If-None-Match : 结合Etag对文件做MD5校验


6、HTTP缓存相关首部
age : 一个缓存对象从产生到此刻为止,经过的多少时间

Cache-Control请求首部
no-cache : 不要从缓存中返回内容

max-age : 相对过期时间,是以秒为单位

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

min-fresh : 接受其新鲜生命期大于其当前age跟min-fresh值之各的缓存对象

Cache-Control响应首部
no-cache : 可以缓存,但要跟web服务器再验证

no-store : 不允许缓存

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

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

max-age : 本响应包含的对象的过期时间

s-maxage : 公共缓存的最大生命周期

must-revlidate : 每次响应给客户端时,必须做有效性验证

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

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

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


7、常见的缓存服务开源解决方案
varnish : 专用于web服务的缓存

squid : 类似nginx,apache,但比varnish稳定

Varnish
varnish对比squid的优点

1、varnish的稳定性很高,两者在完成相同负荷的工作时,squid服务器发生故障的几率要高于varnish,因为squid要经常重启
2、varnish访问速度更快,其采用了"Visual Page Cache"技术,所有缓存数据都直接从内存中读取,而squid是从硬盘读取,因而varnish在访问速度方面会更快
3、varnish可以支持更多的并发连接,因为varnish的TCP连接释放要比squid快,因而在高并发连接情况下可以支持更多TCP连接
4、varnish可以通过管理端口,使用正则表达式批量的清除部分缓存,而squid是做不到的。
5、squid属于单进程使用单核CPU,但Varnish是通过fork形式打开多进程来做处理,所以是合理的使用所有核来处理相应的请求

varnish对比squid的缺点

1、varnish进程一旦Hang、Crash或者重启,缓存数据都会从内存中完全释放,此时所有请求都会发送到后端服务器,在高并发情况下,会给后端服务器造成很大的压力
2、在varnish使用中,如果单个vrl的请求通过HA/F5,每次请求不同的varnish服务器时,被请求的varnish服务器都会被穿透到后端,而同样的请求会在多台服务器上缓存 ,也会造成varnish的缓存资源浪费,也会造成性能下降

varnish的工作进程特性


varnish工作进程示意图

2


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的进程工作特性

varnish启动或有2个进程master(management)进程和child(worker)进程,master读入存储配置命令,进行初始化,然后fork并监控child,child则分配线程进行cache工作,child还会做管理线程生成很多的worker线程
child线程主线程初始化过程中,将存储大文件整个加载到内存中,如果该文件超出系统的虚拟内存,则会减少原来配置MMAP大小,然后继续加载,这时候创建并初始化空间存储结构体,放在存储管理的struct中,等待分配
接着varnish某个负责接受http连接的线程开始等待用户请求,如果有新http连接,但这个线程只负责接收,然后唤醒等待线程池中的work线程,进行请求处理
work线程读入uri后,将会查找已有的object,命中直接返回,没有命中则会从后端服务器中取出来,放到缓存中,如果缓存已满,会根据LRU算法释放旧的Object,对于释放缓存,有一个超时线程检测缓存中所有object的生命周期,如果缓存过期(ttl),则删除,释放相应的存储内存


varnish使用单进程多线程模型,其worker stats类似于一个线程池,所有的资源将整合在一个工作区中,以降低线程在申请或修改内存时,出现的竞争的可能性,当多个线程同时访问同个资源时,工作区对资源以施加锁保证用户的请求在资源争用时,后来的线程处于等待状态,以协调线程的工作。
varnish存储缓存机制
malloc
  基于内存存储,在内存中存储各缓存对象,时间久了会产生缓存碎片,如果分配的内存太大,会降低效率。varnish可能会激活大内存空间分配机制(在Centos6以后),这样也会降低缓存的查询效率


file
  所有缓存对象缓存在单个文件中,重启后将会失效,不支持持久机制,建议使用SSD存放缓存数据,一般用于大文件缓存,如图片等


persistent
  基于文件的持久存储,varnish重启了,缓存还有效,其目前为实验性项目,生产环境中不能使用


varnish内存分配回收机制


分配
malloc()函数
jemalloc()函数 : 其是malloc的并发实现

回收
free()函数


VCL编程语法
  VCL:varnish configuration language,又被称之为DSL(域)编译语言,其是参照C和perl语言的风格编写,基本格式如下:


sub NAME {
 ....;



    }

* 不支持循环
* 受状态引擎的变量,变量的可调用位置与state engine有密切相关性
* 支持终止语句,使用ruturn()返回一个action,其没有返回值
* 可自定义变量
* //,#,/* */: 用于注释,会被编译器忽略
* “域”专用,只能一个域有效
* 操作符: `=,==,~,!,&&,||`
* 条件判断语句的写法:

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

多分支:
if (condition) {
    ...;
} elseif {
    ...;
} else {
    ...;
}
```
* 变量赋值:`set name = value`
* 撤消变量的值:`unset name`
安装及配置Varnish
Centos 6
安装
yum install varnish : 默认安装为varnish2的版本

主进程配置文件

/etc/sysconfig/varnish

/etc/varnish/default.vcl : VCL引擎配置文件

服务管理脚本
/etc/rc.d/init.d/varnish : varnish服务主进程管理

/etc/rc.d/init.d/varnishlog : varnishlog服务管理

/etc/rc.d/init.d/varnishncsa : varnishncsa服务管理


Centos 7
安装
yum install varnish : 默认安装为varnish 4的版本

主进程配置文件
/etc/varnish/varnish.params : varnish服务主进程管理

/etc/varnish/default.vcl : VCL引擎配置文件

服务管理脚本
/usr/lib/systemd/system/varnish.service
/usr/lib/systemd/system/varnishlog.service
/usr/lib/systemd/system/varnishncsa.service

varnish主配置文件参数说明
RELOAD_VCL=1 : 是否启动加载VCL配置文件

VARNISH_VCL_CONF=/etc/varnish/default.vcl : varnish的VCL配置文件路径

VARNISH_LISTEN_PORT=6081 : 默认监听端口

VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 : 远程管理监听地址

VARNISH_ADMIN_LISTEN_PORT=6082 : 远程管理监听端口

VARNISH_SECRET_FILE=/etc/varnish/secret : varnish默认加载的密钥文件,其为远程连接varnish的共享密钥文件

VARNISH_MIN_THREADS=50 : varnish最少启动worker线程

VARNISH_MAX_THREADS=1000 : varnish最大启动worker线程数(据说超出5000就不稳定了)

VARNISH_THREAD_TIMEOUT=120 : 空闲线程的超时时间,超时后被父进程销毁

VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin : varnish的缓存存储位置文件,其是二进制格式

VARNISH_STORAGE_SIZE=1G : 缓存大小

VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}” : 基于文件存储缓存的设置方式

VARNISH_STORAGE_SHM=64M : 基于内存的存储缓存,指定缓存的大小,不可与文件的方式共存

VARNISH_STORAGE=“malloc,${VARNISH_STORAGE_SHM}” : varnish基于内存存储缓存的设置方式

注意:varnish配置文件修改不应重启服务,而是手动加载配置文件
Varnish管理工具
varnishd命令
其可以在/etc/sysconfig/varnish的配置文件中完成
Options
-f : vcl的配置文件

-a address[:port] :服务监听的IP地址及端口

-s[name=]type[,options] : varnish缓存的存储机制
options
malloc[,size]
file[,path[,size[,granularity]]] (力度)
persistent,path,size

-T address[:port] : 指定管理接口,默认6082

-p param=value : 指定参数

-r param : 指定只读参数2、varnishadm命令
其是通过连接varnish服务端,可以实现varnish服务的管理操作,实现动态加载VCL的配置文件

Options:


-S scret_file : 密钥文件,跟服务启动的一样,在varnish目录下存放

-T IP:PORT : 连接服务端的端口,默认为6082


varnish命令行命令


vcl.list : 查看vcl配置文件的列表

vcl.load NAME default.vcl : 加载vcl当前的配置文件为NAME

vcl.use NAME : 使用加载的配置文件

vcl.show NAME : 查看加载的配置文件详细配置信息

vcl.discard : 删除加载的配置文件

backend.list : 查看后端服务器列表及状态

param.show -l : 查看varnish主进程的运行选项参数

param.set <param> <value> : 在运行时设定主进程的运行参数

ping : 判断后端服务器的状态

status : 查看进行的运行状态

panic.show : 如果某个子进程曾经panic过,可以使用这个命令查看(panic本为恐慌,代表服务曾经崩溃过)

storage.list: 查看缓存存储机制

ban  <field> <operator> <arg> [&& <field> <oper> <arg> ]…. : 手动清除缓存

ban.list : 列出清除缓存的规则列表,由上一个参数定义

3、Varnishtop命令
内存日志区域查看工具
Options
-I REGEXP : 仅显示被模式匹配到的条目,过滤右边的信息条目

-X : 仅显示不被模式匹配到的条目,过滤右边的信息条目

-i : 过滤左边字段

-x : 指定的不显示,不指定的才显示

-C : 忽略字符大小写

-d : 显示已有的日志
varnishtop -i RxHeader
varnishtop -I ^User-Agent
varnishtop -I ^User-Agent -1 : 只显示一屏即退出
4、varnishstat命令
varnish的运行统计数据
Options
-f filed,filed,.... : 指明只显示哪些参数

-l : 列出所有字段

-x : 以XML格式输出

-j : 以json格式输出

5、varnishlog命令
启动以comm格式记录日志到磁盘
6、varnishncsa命令
启动以combind格式记录日志到磁盘
VCL Engine
vcl engine是varnish通过VCL配置语言定义的缓存策略,state engine之间有相关性,上级engine通过return指明下级engine,常用的引擎(varnish version 3),如下:
vcl_recv
由return(lookup)定义到vcl_hash引擎


vcl_hash
其是对缓存项进行hash计算


vcl_fetch
其是向后端服务器取文件的


vcl_hit
其下一个工作引擎可能是deliver,也有可能是pass(为pass的情况下:命中后需要把缓存强制删除)


vcl_miss
vcl_deliver

vcl_pipe
无法识别的方法,交由pipe管道引擎处理


vcl_pass
可以理解的方法,但不缓存,就经由pass


vcl_error

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

2

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

2

Vcl engine常见工作流程
1、查询缓存未命中的工作流

2

2、查询缓存命中的工作流

2

3、未识别的HTTP方法工作流

2

4、不予缓存的工作流

2

5、完整的工作流

2


restart

当命中后,把url进行了重写操作,就需要从vcl_recv重新开始,即为重启,这类操作被称之为restart,但varnish有一个内申机制,即重启了10次,仍然还在重启,此次请求将会被丢弃


error

类似于404类的请求错误


6、状态引擎说明

1、首先由vcl_init装载vcl引擎
2、再由vcl_recv将请求接入,分析是否服务于此请求,并指定如何服务,可能交由下一个(pipe,pass,hash)处理
3、vcl engine可用的return函数

return(lookup) : 送给vcl_hash引擎处理

return(pass) : 交给vcl_pass引擎处理

return(pipe) : 交由vcl_pipe引擎处理

error CODE : 返回错误和相应的CODE给客户端

return(deliver) : 交由vcl_deliver直接投递
return(hit_for_pass)

return(restart) : 重启请求


VCl引擎中常用变量


1、在任何引擎中均可使用


now : 获取当前系统当前时间

.host : 后端主机或主机名

.port : 后端主机的端口或服务名


2、用于处理一个请求阶段
可用于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 : 此请求被重启的次数


3、vanish向backend主机发起请求前可用的变量


bereq.request : 请求方法

bereq.url
bereq.proto
bereq.http.HEADER

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


4、当后端服务器响应varnish,但未放置缓存之前


beresp.do_stream : 表示流式响应
流式响应:当后端backend主机响应一个10M大小的文件,10M的文件是由多个数据报文组成,当varnish接收到一个报文时,就直接将报文发给客户端,也不是等待数据报文接收完整后再发出,这就被称之为流式响应


beresp.do_gzip : 从后端服务器收到的响应报文,要不要压缩以后存储下来


beresp.do_gunzip : 从后端服务器收到的响应报文,如果压缩了,要不要解压缩后再存下来
beresp.http.HEADER
beresp.proto

beresp.status : 响应状态码

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

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

beresp.backend.name : 此响应报文来源的backend主机名称
beresp.backend.ip
beresp.backend.port

beresp.storage : 缓存后端


5、缓存对象存入cache之后可用的变量,大多数为只读


obj.response : 服务端所返回的原因短语

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

obj.ttl : 指明当前的对象缓存的还有多少时长

obj.hits : 这个缓存对象已经命中多少次(大约值)

obj.http.HEADER : 后端服务器的响应首部


6、决定对请求的健做hash计算时可用的变量


req.hash : 把什么内容做hash键,做查询的健


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


resp.proto : 指定使用什么协议来响应
resp.status

resp.response
resp.http.HEADER


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


1、定义格式:
  backend NAME {
      .host=     #后端backend server IP地址
      .port=    #后端backend server 端口
  }


2、后端服务器需要vcl_recv中调用,示例如下
  示例1:

  sub vcl_recv {
      ...
      if(req.url ~ "test.html") {
          set req.backend = NAME;
      } else {
          set req.backend = NAME1;
      }

  示例2:

  sub vcl_recv {
      ...
      set req.backend = NAME;
  }


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


1、定义方法
  backend web1 {
      .host = "www.zhenping.me";
      .port = "80";
  }

  director webservers random {
      .retries = 5;
      {
          .backend = web1;
          .weight = 2;
      }
      {
          .backend = {
              .host = "www2.zhenping.me";
              .port = "80";
          }
      .weight = 3;
      }
  }

  以上为两种定义方法示例


2、调用方法
  示例:

  sub vcl_recv {
      ....
      set req.backend = webservers;
  }


3、backend调度方法


round-robin
其是对资源对象的轮询调度方法(当访问index1.html到后端server1,当访问index2.html到后端server2),其没有参数


random


其是对后端backend server的随机调度方法,也是建议使用的方法,其支持以下参数


.weight = # : 权重

.retires = # : 来设定查找一个健康后端主机时的尝试次数

varnish2.1.0之后,random挑选方法又多了两种变化形式client和hash,client类型的director使用client.identity作为挑选因子,这意味着client.identity相同的请求都将被发送至同一后端主机,clinet.itdentity默认为client.ip,但也可以在VCL中将其修改为所需要的标识符,类似的,hash类型的director使用hash数据作为挑选因子,这意味着对同一个URL的请求将被发往同一个后端主机,其常用于多级缓存的场景中,然后,无论是client还是hash,当其倾向于使用后端主机不可用时将会重新挑选新的后端主机



fallback


用于定义备用服务器,其更多的是冗余的作用,以下示例
director b3 fallback {
  { .backend = web1; }
  { .backend = web2; }
  { .backend = web3; }
}

注意: 只有web1不可用才会使用到web2,web2和web1同时不可用时,才会使用到web3,如果web2不可用,但web1可用,它也会使用web1




varnish检测后端主机的健康状态
varnish可以检测后端主机的健康状态,在判定后端主机失效时能自动将其从可用后端主机列表中移除,而一旦其重新变得可用还可以自动将其设定为可用,为了避免误判,varnish在探测后端主机的健康状态发生转变时(比如某次检测时某后端主机突然成为不可用状态),通常需要连续执行几次探测均为新状态才将其标记为转换后的状态
每个后端服务器当前探测的健康状态探测方法通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的backend_health查看或varnishadm的debug.health查看


.probe中探测常用指令


url : 探测后端主机健康状态时请求的URL,默认为/

.request : 探测后端主机健康状态时所请求内容的详细格式,定义后,它会替换.rul指定的探测方法,如下:
.request =
  "GET /.healthtest.html HTTP/1.1"
  "Host:www.zhenping.me"
  "Connection: close" #探测时关闭长连接


.window : 设定在判定后端主机健康状态时基于最近多少次的探测进行,默认是8次


.threshold : 在window中指定的次数中,至少有多少次是成功的才判定后端主机是正常健康运行,默认是3次

.initial : varnish启动时对后端主机至少需要多少次的成功探测,转储同.threshold


.expected_response : 期望后端主机的响应状态码,默认是200

interval : 探测请求的发送周期,默认是5秒

.timeout : 每次探测请求的过期时长,默认为2秒


```
示例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)^p_w_picpaths.zhenping.me$”) {
                set req.backend = p_w_picpaths;
            } 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);
        }