首页 > 代码库 > Varnish的负载均衡、动静分离

Varnish的负载均衡、动静分离

一、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状态引擎以及工作原理说明

wKioL1Rot_TCI59vAAH002Hq3FI410.jpg

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

Varnish的负载均衡、动静分离