Varnish 是一款高性能且开源的反向代理服务器和 HTTP 加速器,其采用全新的软件体系机构,和现在的硬件体系紧密配合,与传统的squid 相比,varnish 具有性能更高、速度更快、管理更加方便等诸多优点,很多大型的网站都开始尝试使用 varnish 来替换 squid,这些都促进varnish 迅速发展起来。
varnish主要运行两个进程:Management进程和Child进程(也叫Cache进程)。它们的工作原理大致如下图:
Management进程主要实现应用新的配置、编译VCL、监控varnish、初始化varnish以及提供一个命令行接口等。Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。
Child进程包含多种类型的线程,常见的如:Acceptor线程:接收新的连接请求并响应;Worker线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;Expiry线程:从缓存中清理过期内容;
Varnish依赖“工作区(workspace)”以降低线程在申请或修改内存时出现竞争的可能性。在varnish内部有多种不同的工作区,其中最关键的当属用于管理会话数据的session工作区。
为了与系统的其它部分进行交互,Child进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个worker线程都使用了日志数据缓存。
共享内存日志大小一般为90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。varnish提供了多个不同的工具如varnishlog、varnishncsa或varnishstat等来分析共享内存日志中的信息并能够以指定的方式进行显示。
varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。后端存储的类型包括:
(1)file: 使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);
(2)malloc: 使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象,类似于C语言中的malloc动态申请函数;
file和malloc存储方法类似,重启缓存服务时,先前缓存的数据不复存在。
说道varnish的状态引擎,不得不说vcl(Varnish Configuration Language:varnish配置缓存策略的工具)。它是基于域的一种简单的编程语言,支持算数运算、允许使用正则表达式、支持if语句等。使用vcl语言编写的缓存策略通常保存于.vcl文件中,其需要编译成二进制的格式后才能由varnish调用。
VCL用于让管理员定义缓存策略,而定义好的策略将由varnish的management进程分析、转换成C代码、编译成二进制程序并连接至child进程。varnish内部有几个所谓的状态(state),在这些状态上可以附加通过VCL定义的策略以完成相应的缓存处理机制,因此VCL也经常被称作“域专用”语言或状态引擎,“域专用”指的是有些数据仅出现于特定的状态中。
具体的状态的是通过定义内置函数来实现的,具体过程如下图:
vcl各状态引擎的功用: vcl_recv:是在Varnish完成对请求报文的解码为基本数据结构后第一个要执行的子例程 vcl_fetch:根据服务器端的响应作出缓存决策 vcl_pipe:用于将请求直接发往后端主机; vcl_hash:自定义hash生成时的数据来源 vcl_pass:用于将请求直接传递至后端主机; vcl_hit:从缓存中查找到缓存对象时要执行的操作; vcl_miss:从缓存中款查找到缓存对象时要执行的操作; vcl_deliver:将用户请求的内容响应给客户端时用到的方法; vcl_error:在varnish端合成错误响应而时;
缓存相关的HTTP首部:
HTTP协议提供了多个首部用以实现页面缓存及缓存失效的相关功能,这其中最常用的有: (1)Expires:用于指定某web对象的过期日期/时间,通常为GMT格式;一般不应该将此设定的未来过 长的时间,一年的长度对大多场景来说足矣;其常用于为纯静态内容如JavaScripts样式表或图片 指定缓存周期; (2)Cache-Control:用于定义所有的缓存机制都必须遵循的缓存指示,这些指示是一些特定的指令, 包括public、private、no-cache(表示可以存储,但在重新验正其有效性之前不能用于响应客户端 请求)、no-store、max-age、s-maxage以及must-revalidate等;Cache-Control中设定的时间会覆 盖Expires中指定的时间; (3)Etag:响应首部,用于在响应报文中为某web资源定义版本标识符; (4)Last-Mofified:响应首部,用于回应客户端关于Last-Modified-Since或If-None-Match首部的请 求,以通知客户端其请求的web对象最近的修改时间; (5)If-Modified-Since:条件式请求首部,如果在此首部指定的时间后其请求的web内容发生了更改, 则服务器响应更改后的内容,否则,则响应304(not modified); (6)If-None-Match:条件式请求首部;web服务器为某web内容定义了Etag首部,客户端请求时能获取 并保存这个首部的值(即标签);而后在后续的请求中会通过If-None-Match首部附加其认可的标签列 表并让服务器端检验其原始内容是否有可以与此列表中的某标签匹配的标签;如果有,则响应304, 否则,则返回原始内容; (7)Vary:响应首部,原始服务器根据请求来源的不同响应的可能会有所不同的首部,最常用的是 Vary: Accept-Encoding,用于通知缓存机制其内容看起来可能不同于用户请求时 Accept-Encoding-header首部标识的编码格式; (8)Age:缓存服务器可以发送的一个额外的响应首部,用于指定响应的有效期限;浏览器通常根据此 首部决定内容的缓存时长;如果响应报文首部还使用了max-age指令,那么缓存的有效时长为 “max-age减去Age”的结果;
# rpm包下载地址:https://repo.varnish-cache.org/ rpm -ivh varnish-3.0.5-1.el6.x86_64.rpm varnish-libs-3.0.5-1.el6.x86_64.rpm \ varnish-docs-3.0.5-1.el6.x86_64.rpm
修改varnish的相关参数:
## /etc/sysconfig/varnish 的有效参数如下: NFILES=131072 MEMLOCK=82000 NPROCS="unlimited" RELOAD_VCL=1 VARNISH_VCL_CONF=/etc/varnish/default.vcl VARNISH_LISTEN_PORT=80 # 一般修改varnish的监听端口 VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 VARNISH_ADMIN_LISTEN_PORT=6082 VARNISH_SECRET_FILE=/etc/varnish/secret VARNISH_MIN_THREADS=50 VARNISH_MAX_THREADS=1000 VARNISH_THREAD_TIMEOUT=120 VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin VARNISH_STORAGE_SIZE=1G VARNISH_STORAGE_MEM_SIZE=128M # 使用malloc存储方法时的内存大小 VARNISH_STORAGE="malloc,${VARNISH_STORAGE_MEM_SIZE}" # 配置后端存储方法为malloc 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}"
配置vcl的相关参数:
# 修改 /etc/varnish/default.vcl 中以下内容: backend default { .host = "172.16.10.11"; .port = "80"; }
此时我们访问:http://172.16.10.77时,会发现第一次响应速度比较慢,随后的响应是很快的。
此时,可以通过varnishstat查看缓存命中情况。
也可以自行定义缓存策略:
Varnish内置变量: 请求到达时可用的内置变量: req.url req.request req.http.HEADER req.restarts: 请求被重启的次数; server.ip server.port server.hostname client.ip req.backend 向后后端主机请求时可用的内置变量 bereq.url bereq.request bereq.http.HEADER bereq.connect_timeout bereq.proto 从后端主机获取到响应的object时可用的内置变量 beresp.status beresp.response beresp.http.HEADER beresp.ttl beresp.backend.name beresp.backend.ip beresp.backend.port 缓存对象进入缓存时可用的内置变量(只能用于vcl_hit或vcl_error,且大多为只读) obj.status obj.response obj.ttl obj.hits obj.http.HEADER 响应给客户端时可用的内置变量 resp.proto resp.status resp.response resp.http.HEADER
自定义web.vcl,内容如下:
[root@varnish ~]# cat /etc/varnish/web.vcl # 定义后端主机,并提供健康状态检测 backend web1 { .host = "172.16.10.11"; .probe = { .url="/index.html"; .interval=2s; .window=8; .threshold=2; } } backend web2 { .host = "172.16.10.16"; .probe = { .url="/index.html"; .interval=2s; .window=8; .threshold=2; } } director websrv round-robin { { .backend = web1; } { .backend = web2; } } # 定义一个acl 目的是进行缓存裁剪 acl purgers { "127.0.0.1"; "172.16.0.0"/16; } sub vcl_recv { set req.backend = websrv; 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.url ~ "^/test.html$") { return(pass); } if (req.request == "PURGE") { if (!client.ip ~ purgers) { error 405 "Method not allowed"; } return (lookup); } 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 || req.http.Cookie) { /* Not cacheable by default */ return (pass); } return (lookup); } sub vcl_pipe { return (pipe); } # sub vcl_hash { # hash_data(req.url); # if (req.http.host) { # hash_data(req.http.host); # } else { # hash_data(server.ip); # } # return (hash); # } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged"; } return (deliver); } sub vcl_miss { if (req.request == "PURGE") { purge; error 404 "Not in cache"; } return (fetch); } sub vcl_pass { if (req.request == "PURGE") { error 502 "PURGE on a passed object"; } return (pass); } # # sub vcl_fetch { # if (beresp.ttl <= 0s || # beresp.http.Set-Cookie || # beresp.http.Vary == "*") { # /* # * Mark as "Hit-For-Pass" for the next 2 minutes # */ # set beresp.ttl = 120 s; # return (hit_for_pass); # } # return (deliver); # } sub vcl_deliver { set resp.http.X-Age = resp.http.Age; unset resp.http.Age; if (obj.hits > 0) { set resp.http.X-Cache = "HIT Via" + " " + server.hostname; } else { set resp.http.X-Cache = "MISS Via" + " " + server.hostname; } return (deliver); } # sub vcl_deliver { # # # return (deliver); # } # # sub vcl_error { # set obj.http.Content-Type = "text/html; charset=utf-8"; # set obj.http.Retry-After = "5"; # synthetic {" # <?xml version="1.0" encoding="utf-8"?> # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" # "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> # <html> # <head> # <title>"} + obj.status + " " + obj.response + {"</title> # </head> # <body> # <h1>Error "} + obj.status + " " + obj.response + {"</h1> #"} + obj.response + {"# Guru Meditation: #XID: "} + req.xid + {"# <hr> #Varnish cache server# </body> # </html> # "}; # return (deliver); # } # sub vcl_init { return (ok); } sub vcl_fini { return (ok); }
vcl自动编译的方法,编辑 /etc/sysconfig/varnish 文件中的:
VARNISH_VCL_CONF=/etc/varnish/web.vcl