一、Varish的简介
Varnish是一款高性能的开源HTTP加速器,挪威最大的在线报纸 Verdens Gang 使用3台Varnish代替了原来的12台Squid,性能比以前更好。
在当前主流的Web架构中,Cache担任着越来越重要的作用。常见的基于浏览器的C/S架构,Web Cache更是节约服务器资源的关键。而最近几年由FreeBSD创始人之一Kamp开发的varnish更是一个不可多得的Web Cache Server。严格意义上说,Varnish是一个高性能的反向代理软件,只不过与其出色的缓存功能相比,企业更愿意使用其搭建缓存服务器。同时,由于其工作在Web Server的前端,有一部分企业已经在生产环境中使用其作为旧版本的squid的替代方案,以在相同的服务器成本下提供更好的缓存效果,Varnish更是作为CDN缓存服务器的可选服务之一。
二、关于Varnish
1、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工作区。
2、varnish日志
为了与系统的其它部分进行交互,Child进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个worker线程都使用了日志数据缓存。
共享内存日志大小一般为90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。varnish提供了多个不同的工具如varnishlog、varnishncsa或varnishstat等来分析共享内存日志中的信息并能够以指定的方式进行显示。
3、VCL
Varnish Configuration Language (VCL)是varnish配置缓存策略的工具,它是一种基于“域”(domain specific)的简单编程语言,它支持有限的算术运算和逻辑运算操作、允许使用正则表达式进行字符串匹配、允许用户使用set自定义变量、支持if判断语句,也有内置的函数和变量等。使用VCL编写的缓存策略通常保存至.vcl文件中,其需要编译成二进制的格式后才能由varnish调用。事实上,整个缓存策略就是由几个特定的子例程如vcl_recv、vcl_fetch等组成,它们分别在不同的位置(或时间)执行,如果没有事先为某个位置自定义子例程,varnish将会执行默认的定义。
VCL策略在启用前,会由management进程将其转换为C代码,而后再由gcc编译器将C代码编译成二进制程序。编译完成后,management负责将其连接至varnish实例,即child进程。正是由于编译工作在child进程之外完成,它避免了装载错误格式VCL的风险。因此,varnish修改配置的开销非常小,其可以同时保有几份尚在引用的旧版本配置,也能够让新的配置即刻生效。编译后的旧版本配置通常在varnish重启时才会被丢弃,如果需要手动清理,则可以使用varnishadm的vcl.discard命令完成。
4、varnish的后端存储
varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。后端存储的类型包括:
(1)file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);
(2)malloc:使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象;
(3)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用于设定缓存空间分配单位,默认单位是字节,所有其它的大小都会被圆整。
三、安装varnish
Varnish的官方站点:[url]https://www.varnish-cache.org/[url]
官方最新版本:Varnish Cache 4.0.1
Varnish学习需要注意的要素:
1、Varnish要安装在64的系统上
2、Varnish使用内存做缓存大小为2G,基本就可以了;无论是memcache还是varnish基于内存做缓存都不宜太大,太大没有好处,它有一个临界值,如果缓存设置太小,那么数据缓存会频繁被清除掉;如果缓存设置太大的,那么缓存会永久存放,有些过期的也不会被清除掉,并且数据过多的查找起来也慢。建议Varnish缓存设置大小为1G-2G之间就可以了。
1、yum安装varnish
[root@Varnish ~]# rpm --nosignature -i https://repo.varnish-cache.org/redhat/varnish-3.0.el6.rpm [root@Varnish ~]# yum -y install varnish
2、Varnish的配置文件说明
[root@Varnish ~]# grep -v "^#" /etc/sysconfig/varnish |sed ‘/^$/d‘ NFILES=131072 # 最大打开文件数65536*2{ulimit -n} MEMLOCK=82000 # 锁定使用多大的共享内存来保存日志信息,默认为82M NPROCS="unlimited" # 最大的线程数,默认无限制 # DAEMON_COREFILE_LIMIT="unlimited" # 进程核心转储所使用的内存空间,unlimited表示无上限 RELOAD_VCL=1 # 设置为1可以使用restart自动加载vcl配置文件 VARNISH_VCL_CONF=/etc/varnish/default.vcl # 定义vcl配置文件 VARNISH_LISTEN_PORT=6081 # 定义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启动时最小线程数 VARNISH_MAX_THREADS=1000 # 定义varnish启动时最大线程数 VARNISH_THREAD_TIMEOUT=120 # 定义varnish线程响应超时时间 VARNISH_STORAGE_FILE=/var/lib/varnish/varnish_storage.bin # 定义varnish的缓存文件 VARNISH_STORAGE_SIZE=1G # 定义缓存使用文件大小,单位:k/M/G/T VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}" # 使用文件缓存 #VARNISH_STORAGE="malloc,${VARNISH_STORAGE_SIZE}" # 使用内存缓存;malloc VARNISH_TTL=120 # 如果后端服务器没有设置数据缓存多长时间,则默认为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}"
3、Varnish的VCL配置说明
[root@Varnish ~]# cat /etc/varnish/default.vcl #定义后端默认的服务器 backend default { .host = "127.0.0.1"; .port = "80"; } #定义一个vcl_recv函数 sub vcl_recv { #req.restarts可以解释为如果客户端第一次开始请求时, #如果请求的http中含有X-Forwarded-For信息,不管如何都在该信息后面附加客户端的ip地址信息; #若不含有X-Forwarded-For信息,则直接附加client.ip地址信息。 #X-Forwarded-For通常表示代理服务器的地址 #为什么要补充client.ip呢?因为varnish做了反向代理,为了使后端服务器记录客户端的ip地址而非varnish的地址 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; } } #定义http请求的方法;如果客户端请求的http的方法不是GET/HEAD/PUT/POST/TRAGE/OPTIONS/DELETE, #那么我们的varnish就不会去把请求传递给varnish的vcl_hash,直接交给pipe,表示varnish无法理解或者认为是非法的请求。 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); } #如果请求的方法不是GET,也不是HEAD,那么有可能就是PUT,POST,这些都是上传数据的, #对于上传的数据,我们没有必要缓存,直接跟后端服务器交互。 if (req.request != "GET" && req.request != "HEAD") { /* We only deal with GET and HEAD by default */ return (pass); #不查找缓存,直接从后端服务器获取数据 } #如果请求的内容中包括Authorization授权的,包括Cookie认证的,这些都是用户的敏感数据,一定不能缓存的。 if (req.http.Authorization || req.http.Cookie) { /* Not cacheable by default */ return (pass); } return (lookup); #lookup表示从缓存中查找 } #从后端服务器fetch数据 sub vcl_pass { return (pass); } sub vcl_hash { #定义vcl_hash函数 hash_data(req.url); #默认是根据用户请求的url做hash if (req.http.host) { #如果用户的请求http的首部中有host,那么就对此做hash hash_data(req.http.host); } else { hash_data(server.ip); #否则根据服务器端的地址做hash } return (hash); #最终返回hash数据 } #如果命中的则直接从本地缓存中返回数据给用户 sub vcl_hit { return (deliver); } #如果没有命中,则从后端服务器取数据 sub vcl_miss { return (fetch); } #如果没有命中,那么从后端服务器取数据应该怎样取呢? #在响应客户端之前,如果ttl头部值小于等于0秒,表示缓存已经过期了, #并且其中包含有"Set-Cookie","Vary"这些字段,那么就直接设定这些过期的缓存信息的缓存期限为120秒 #如果其中没有这些字段的话就直接缓存下来了。 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 { return (deliver); } #客户端请求某个页面,如果服务器上不存在这个页面,就请求错误 #对于后端服务器没有这个文件的,直接交由varnish响应一个错误信息 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> <p>"} + obj.response + {"</p> <h3>Guru Meditation:</h3> <p>XID: "} + req.xid + {"</p> <hr> <p>Varnish cache server</p> </body> </html> "}; return (deliver); } sub vcl_init { return (ok); } sub vcl_fini { return (ok); }
四、Varnish状态引擎以及工作原理说明
vcl_recv:用于接收到用户的请求 在vcl_hit引擎中可以调用return(pipe)指令和调用return(lookup)指令和调用return(pass)指令。 如果不检查缓存; 调用的是return(pipe)指令,然后由vcl_pipe引擎直接交给后端服务器进行处理 如果是检查缓存; 调用return(lookup)指令,检查缓存,看缓存是否命中,需自行定义如何 检查缓存 调用return(pass)指令,则将请求送给vcl_pass进行处理 vcl_pipe:用于把用户的请求接进来,然后建立一个管道直接交给后端服务器 在vcl_pipe引擎中可以调用return(pipe)指令 调用return(pipe)指令则建立一个与后端服务器的管道 vcl_hash:用于自行定义其它缓存的机制 在vcl_hash引擎中可以调用return(hash)指令 调用return(hash)指令,则通过hash键值对进行判断,是否命中 vcl_hit:用于表示缓存命中 在vcl_hit引擎中可以调用return(pass)指令和调用return(delive)指令 如果是调用return(pass)指令,则将请求送给vcl_pass进行处理 {此情况发生在当自定义的缓存为1个小时,但未满一个小时,所设置的缓存已经发生变化则需要用vcl_pass} 如果是调用return(delive)指令,则从缓存中直接取出后由vcl_deliver返回给用户 vcl_miss:用于表示缓存未命中 在vcl_miss引擎中可以调用return(pass)指令和调用return(fetch)指令 如果是调用return(pass)指令,则将请求送给vcl_pass进行处理 如果是调用return(fetch)指令,则将请求送给vcl_fetch进行处理 vcl_pass:用于给命中引擎和未命中引擎提供处理机制 在vcl_pass引擎中可以调用return(fetch)指令 调用return(fetch)指令,则将请求送给vcl_fetch进行处理 vcl_fetch:用于到后端服务器去取数据 在vcl_fetch引擎中可以调用return(delive)指令和调用return(pass)指令 如果是调用return(delive)指令,则把后端取的数据保存在缓存中 如果是调用return(pass)指令,则不把后端取的数据保存在缓存中 vcl_deliver:用于从缓存中取数据返回给用户 vcl_error:用于由varnish直接构建错误响应报文
************************
vcl_recv:用户请求到达varnish之后,varnish就要vcl函数做一些验证和处理操作。 pipe:管道 送给其它的进程或其它的机制来处理,一般用的比较少。 pass:不查找本地缓存,直接从后端服务器获取数据。 lookup:查找,从本地缓存中查找, vcl_hash:定义对用户查找的链接做vcl运算并比较的,因此我们用用户请求的url链接根据自已定义的vcl方式做hash计算以后,就会得到hash码; 为了使缓存中的数据查找效率高,这些缓存中的数据都是以键值对的方式存储的,key(url)和value(object) 这里的key是用户请求的url,value是用户请求的文件内容,我们叫缓存对象。 如果直接用url来做key,那么查找起来是十分缓慢的,往往都是把url做hash编码,可以指定hash编码的算法。得到hash码。 Is the object in cache?:如果用得到的hash码给现存缓存列表中的key做比较,如果有一样的则就命中缓存了。否则缓存没有命中。 vcl_miss: vcl_pass: { (vcl_pass)pass:如果没有命中,多一步验证措施 (vcl_pass)fetch:到后端服务器取数据 } vcl_hit: { (vcl_hit)pass:尽管命中的也不在缓存中返回,而是从后端服务器取数据,因为这些数据可能是私有的,比如登陆账户、密码,认证加密文件 (vcl_hit)deliver:表示如果命中了并想数据缓存下来或者说把数据做缓存了,在或者说从缓存中取数据了。 } vcl_pass: deliver: Fetch object from backend:表示从后端服务器取数据 vcl_fetch Cache(deliver):先缓存在返回给用户 Dont‘t cache(pass):不缓存直接返回给用户 vcl_deliver:
五、Varnish的一些补充命令说明
1、启动varnish命令
# /etc/init.d/varnish start
2、查看varnish监听的端口
# netstat -tnlp |grep varnish tcp 0 0 0.0.0.0:6081 0.0.0.0:* LISTEN 1397/varnishd tcp 0 0 127.0.0.1:6082 0.0.0.0:* LISTEN 1396/varnishd tcp 0 0 :::6081 :::* LISTEN 1397/varnishd
3、varnishadm的管理工具命令
[root@Varnish ~]# varnishadm -h varnishadm: invalid option -- ‘h‘ usage: varnishadm [-n ident] [-t timeout] [-S secretfile] -T [address]:port command [...] -n is mutually exlusive with -S and -T [root@Varnish ~]# varnishadm -T 127.0.0.1:6082 -S /etc/varnish/secret # 登录管理命令行 200 ----------------------------- Varnish Cache CLI 1.0 ----------------------------- Linux,2.6.32-358.el6.x86_64,x86_64,-sfile,-smalloc,-hcritbit varnish-3.0.6 revision 1899836 Type ‘help‘ for command list. Type ‘quit‘ to close CLI session. varnish> help #查看帮助命令 200 help [command] ping [timestamp] auth response quit banner status start stop vcl.load <configname> <filename> vcl.inline <configname> <quoted_VCLstring> vcl.use <configname> vcl.discard <configname> vcl.list vcl.show <configname> param.show [-l] [<param>] param.set <param> <value> panic.show panic.clear storage.list backend.list backend.set_health matcher state ban.url <regexp> ban <field> <operator> <arg> [&& <field> <oper> <arg>]... ban.list varnish> ping # 测试ping命令 200 PONG 1416045552 1.0 varnish> vcl.load d1 /etc/varnish/default.vcl # 加载编译新配置, d1是配置名,default.vcl是配置文件 200 VCL compiled. varnish> vcl.list # 列出所有的配置 200 active 0 boot available 0 d1 varnish> vcl.use d1 # 使用配置,需指定配置名,当前使用的配置以最后一次vcl.use为准 200 varnish> backend.list #查看配置文件的配置信息 200 Backend name Refs Admin Probe default(127.0.0.1,,80) 2 probe Healthy (no probe) varnish> storage.list #查看储存列表信息 200 Storage devices: storage.Transient = malloc storage.s0 = file
六、Varnish检测后端主机的健康状态
Varnish可以检测后端主机的健康状态,在判定后端主机失效时能自动将其从可用后端主机列表中移除,而一旦其重新变得可用还可以自动将其设定为可用。为了避免误判,Varnish在探测后端主机的健康状态发生转变时(比如某次探测时某后端主机突然成为不可用状态),通常需要连续执行几次探测均为新状态才将其标记为转换后的状态。
每个后端服务器当前探测的健康状态探测方法通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的Backend_health查看或varnishadm的debug.health查看。
backend web1 {
.host = "www.allentuns.com";
.probe = {
.url = "/.healthtest.html";
.interval = 1s;
.window = 5;
.threshold = 2;
}
}
.probe中的探测指令常用的有:
(1) .url:探测后端主机健康状态时请求的URL,默认为“/”;
(2) .request: 探测后端主机健康状态时所请求内容的详细格式,定义后,它会替换.url指定的探测方式;比如:
.request =
"GET /.healthtest.html HTTP/1.1"
"Host: www.magedu.com"
"Connection: close";
(3) .window:设定在判定后端主机健康状态时基于最近多少次的探测进行,默认是8;
(4) .threshold:在.window中指定的次数中,至少有多少次是成功的才判定后端主机正健康运行;默认是3;
(5) .initial:Varnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold;
(6) .expected_response:期望后端主机响应的状态码,默认为200;
(7) .interval:探测请求的发送周期,默认为5秒;
(8) .timeout:每次探测请求的过期时长,默认为2秒;
因此,如上示例中表示每隔1秒对此后端主机www.magedu.com探测一次,请求的URL为http://www.allentuns.com/.healthtest.html,在最近5次的探测请求中至少有2次是成功的(响应码为200)就判定此后端主机为正常工作状态。
如果Varnish在某时刻没有任何可用的后端主机,它将尝试使用缓存对象的“宽容副本”(graced copy),当然,此时VCL中的各种规则依然有效。因此,更好的办法是在VCL规则中判断req.backend.healthy变量显示某后端主机不可用时,为此后端主机增大req.grace变量的值以设定适用的宽容期限长度。
七、Varnish的负载均衡和动静分离配置文件
在实际生产环境中对后端server进行健康状态检查的时候;
静态的在网页根目录创建一个test.html检测页面,动态的在网页根目录先创建一个test.jsp的检测页面
# Configure VCL probe static_chk { #静态网页的健康状态检查 .url = "/test.html"; .interval = 2s; .timeout = 2s; .expected_response = 200; } probe dynamic_chk { #动态网页的健康状态检查 .url = "/test.php"; .interval = 2s; .timeout = 2s; .expected_response = 200; } backend apache01 { #静态请求负载均衡1 .host = "192.168.0.108"; .port = "80"; .probe = static_chk; } backend apache02 { #静态请求负载均衡2 .host = "192.168.0.110"; .port = "80"; .probe = static_chk; } backend nginx01 { #动态请求分发地址 .host = "192.168.0.100"; .port = "80"; .probe = dynamic_chk; } director myload random { #分发器和调度算法 .retries = 2; { .backend = apache01; .weight = 1; } { .backend = apache02; .weight = 1; } } sub vcl_recv { set req.http.X-Forward-For = client.ip; #记录客户端的ip地址 if (req.url ~ "\.(html)$" ) { return(lookup); } if (req.url ~ "\.(php)$") { #如果请求的是php文件则将请求发送给nginx代理的服务器 set req.backend = nginx01; }else{ set req.backend = myload; #如果请求的是html文件则将请求发送给myload代理的服务器 } } sub vcl_fetch { if (req.request == "GET" && req.url ~ "\.(html|jpg|jpeg)$") { set beresp.ttl = 3600s; } } sub vcl_deliver { #记录命中和未命中的变量设定 if (obj.hits > 0) { set resp.http.X-Cache = "HIT from" + " " + server.ip; } else { set resp.http.X-Cache = "MISS"; } return(deliver); }
好了,就写到这里吧;感觉这篇文章写的很不详细,架构图也没有画,案例也没有一个个演示,直接上的配置文件。最近发现自已越来越懒的。前几天一直学习python,有点学上瘾了。好了,大家晚安。明天周一 都能够有个好心情。下篇博文会继续补充。
本文出自 “郑彦生” 博客,请务必保留此出处http://467754239.blog.51cto.com/4878013/1577216
原文地址:http://467754239.blog.51cto.com/4878013/1577216