在看了百度百科后发现在学习varnish原理的过程中,我们需要了解一些在varnish运行过程中涉及的名词,在了解varnish之前我们先熟悉几个概念:
代理服务器**:代理服务器(Proxy Server)是一种重要的服务器安全功能,它的工作主要在开放系统互联(OSI)模型的会话层,从而起到防火墙的作用。代理服务器大多被用来连INTERNET(国际互联网)和Local Area Network(局域网)。代理(英语:Proxy),也称网络代理,是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击。
反向代理:(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。当一个代理服务器能够代理外部网络上的主机,访问内部网络时,这种代理服务的方式称为反向代理服务。此时代理服务器对外就表现为一个Web服务器,外部网络就可以简单把它当作一个标准的Web服务器而不需要特定的配置。不同之处在于,这个服务器没有保存任何网页的真实数据,所有的静态网页或者CGI程序,都保存在内部的Web服务器上。因此对反向代理服务器的攻击并不会使得网页信息遭到破坏,这样就增强了Web服务器的安全性。
cdn:即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置反向代理节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。
线程:有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
WebCache:web缓存,是一种缓存技术,用于临时存储(缓存)的网页文件,如HTML页面和图像等静态资源(此处不绝对,也可以缓存动态页面,但是存储到本地后也为静态文件),减少带宽以及后端服务器的压力,通常一个WebCache也是一个反向代理软件,既可以通过缓存响应用户的请求,当本地没有缓存时,可以代理用户请求至后端主机
WebCache的由来:
由于程序具有局部性,而局部性分为:时间局部性和空间局部性
(1)时间局部性是指:在单位时间内,大部分用户访问的数据只是热点数据(热点数据指经常被访问的数据)
(2)空间局部性是指:比如,某新闻网站突然出来一个重大新闻,此新闻会被被反复访问
varnish 工作机制及原理
Management进程:
Management进程主要实现应用新的配置、编译VCL、监控varnish、初始化varnish以及提供一个命令行接口等。Management进程会每隔几秒钟探测一下Child进程以判断其是否正常运行,如果在指定的时长内未得到Child进程的回应,Management将会重启此Child进程。
Child进程:
Child进程包含多种类型的线程,常见的如:Acceptor线程:接收新的连接请求并响应;Worker线程:child进程会为每个会话启动一个worker线程,因此,在高并发的场景中可能会出现数百个worker线程甚至更多;
Expiry线程:
从缓存中清理过期内容;
Varnish依赖“工作区(workspace)”以降低线程在申请或修改内存时出现竞争的可能性。在varnish内部有多种不同的工作区,其中最关键的当属用于管理会话数据的session工作区。
日志:
为了与系统的其它部分进行交互,Child进程使用了可以通过文件系统接口进行访问的共享内存日志(shared memory log),因此,如果某线程需要记录信息,其仅需要持有一个锁,而后向共享内存中的某内存区域写入数据,再释放持有的锁即可。而为了减少竞争,每个worker线程都使用了日志数据缓存。
共享内存日志大小一般为90M,其分为两部分,前一部分为计数器,后半部分为客户端请求的数据。varnish提供了多个不同的工具如varnishlog、varnishncsa或varnishstat等来分析共享内存日志中的信息并能够以指定的方式进行显示。
varnish的后端存储
varnish支持多种不同类型的后端存储,这可以在varnishd启动时使用-s选项指定。后端存储的类型包括:
(1)file: 使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);
(2)malloc: 使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象,类似于C语言中的malloc动态申请函数;
file和malloc存储方法类似,重启缓存服务时,先前缓存的数据不复存在。
varnish的状态引擎(state engine)
说道varnish的状态引擎,不得不说vcl(Varnish Configuration Language:varnish配置缓存策略的工具)。它是基于域的一种简单的编程语言,支持算数运算、允许使用正则表达式、支持if语句等。使用vcl语言编写的缓存策略通常保存于.vcl文件中,其需要编译成二进制的格式后才能由varnish调用。
VCL用于让管理员定义缓存策略,而定义好的策略将由varnish的management进程分析、转换成C代码、编译成二进制程序并连接至child进程。varnish内部有几个所谓的状态(state),在这些状态上可以附加通过VCL定义的策略以完成相应的缓存处理机制,因此VCL也经常被称作“域专用”语言或状态引擎,“域专用”指的是有些数据仅出现于特定的状态中。
varnish目标请求进入cache缓存的整个过程
Varnish与一般服务器软件类似,分为master进程和child(worker,主要做cache的工作)进程。master进程读入命令,进行一些初始化,然后fork并监控child进程。child进程分配若干线程进行工作,主要包括一些管理线程和很多woker线程。针对文件缓存部分,master读入存储配置,调用合适的存储类型,然后创建/读入相应大小的缓存大文件。接着,master初始化管理该存储空间的结构体。这些变量都是全局变量,在fork以后会被child进程所继承(包括文件描述符)。在child进程主线程初始化过程中,将前面打开的存储大文件整个mmap到内存中(如果超出系统的虚拟内存,mmap失败,进程会减少原来的配置mmap大小,然后继续mmap),此时创建并初始化空闲存储结构体,挂到存储管理结构体,以待分配。接着,真正的工作开始,Varnish的某个负责接受新HTTP连接的线程开始等待用户,如果有新的HTTP连接过来,它总负责接收,然后叫醒某个等待中的线程,并把具体的处理过程交给它。Worker线程读入HTTP请求的URI,查找已有的object,如果命中则直接返回并回复用户。如果没有命中,则需要将所请求的内容,从后端服务器中取过来,存到缓存中,然后再回复。分配缓存的过程是这样的:它根据所读到object的大小,创建相应大小的缓存文件。为了读写方便,程序会把每个object的大小变为最接近其大小的内存页面倍数。然后从现有的空闲存储结构体中查找,找到最合适的大小的空闲存储块,分配给它。如果空闲块没有用完,就把多余的内存另外组成一个空闲存储块,挂到管理结构体上。如果缓存已满,就根据LRU[4]机制,把最旧的object释放掉。释放缓存的过程是这样的:有一个超时线程,检测缓存中所有object的生存期,如果超初设定的TTL(Time To Live)没有被访问,就删除之,并且释放相应的结构体及存储内存。注意释放时会检查该存储内存块前面或后面的空闲内存块,如果前面或后面的空闲内存和该释放内存是连续的,就将它们合并成更大一块内存。整个文件缓存的管理,没有考虑文件与内存的关系,实际上是将所有的object都考虑是在内存中,如果系统内存不足,系统会自动将其换到swap空间,而不需要varnish程序去控制。
以下是大概画的varnish原理图
注:以上原理大多数来自百度百科;
画了一早上保存后才发现忘了将数据返回客户端,不过根据网上的一些提示,大概的varnish工作机制就是这样。。。。
配置virnish
系统:rhel6.5
varnish:server1 172.25.67.1
apache1:server2 172.25.67.2
apache2:server3 172.25.67.3
前期配置:
关闭火墙/域名解析等
一:安装软件
[root@server1 /]# yum install -y varnish-3.0.5-1.el6.x86_64.rpm varnish-libs-3.0.5-1.el6.x86_64.rpm
[root@server2 ~]# yum install -y httpd
[root@server2 ~]# echo "server2"> /var/www/html/index.html
[root@server3 ~]# yum install -y httpd
[root@server3 ~]# echo "server3"> /var/www/html/index.html
二:配置文件
Management配置文件地址(以.vcl结尾)
[root@server1 ~]# vim /etc/varnish/default.vcl
配置一个后端服务器
backend web1 {
.host = "172.25.67.2";
.port = "80";
}
查看缓存命中情况
sub vcl_deliver { ##配置目标是否击中
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT from zpy cache";
}
else {
set resp.http.X-Cache = "MISS from zpy cache";
}
return (deliver);
}
测试
[root@foundation67 Desktop]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
172.25.254.250 content.example.com
172.25.67.1 www.zpy.com
172.25.67.1 bbs.zpy.com
[root@foundation67 Desktop]# curl bbs.zpy.com -I
HTTP/1.1 200 OK
Server: Apache/2.2.15 (Red Hat)
Last-Modified: Wed, 27 Sep 2017 00:49:54 GMT
ETag: “5fcfe-8-55a212b8cab1a”
Content-Type: text/html; charset=UTF-8
Content-Length: 8
Accept-Ranges: bytes
Date: Wed, 27 Sep 2017 01:22:20 GMT
X-Varnish: 1545355921
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Cache: MISS from zpy cache
[root@foundation67 Desktop]# curl bbs.zpy.com -I
HTTP/1.1 200 OK
Server: Apache/2.2.15 (Red Hat)
Last-Modified: Wed, 27 Sep 2017 00:49:54 GMT
ETag: “5fcfe-8-55a212b8cab1a”
Content-Type: text/html; charset=UTF-8
Content-Length: 8
Accept-Ranges: bytes
Date: Wed, 27 Sep 2017 01:22:20 GMT
X-Varnish: 1545355922 1545355921
Age: 1
Via: 1.1 varnish
Connection: keep-alive
X-Cache: HIT from zpy cache
定义多个后端站点
backend web1 { ##定义后台web1 名字随便起
.host = "172.25.67.2"; ##web1ip
.port = "80"; ##apache端口
}
backend web2 { ##同上
.host = "172.25.67.3";
.port = "80";
}
当访问 www.zpy.com 域名时从 web1 上取数据,访问 bbs.zpy.com域名时到 web2 取数据,
sub vcl_recv {
if (req.http.host ~ "^(www.)?zpy.com") {
set req.http.host = "www.zpy.com";
set req.backend = web1;
return (pass);}
elsif (req.http.host ~ "^bbs.zpy.com") {
set req.backend = web2;
} else {error 404 "zpy cache";
}
}
测试:
[root@foundation67 Desktop]# curl bbs.zpy.com
server3
[root@foundation67 Desktop]# curl www.zpy.com
server2
定义健康检查
robe healthcheck {
.url = "/index.html"; # 哪个 url 需要 varnish 请求
.interval = 5s; #检查的间隔时间
.timeout = 1s; #等待多长时间探针超时
.window = 5; #维持 5 个 sliding window 的结果
.threshold = 3; #至少有三次 window 是成功的,就宣告 bachend 健康
backend web1 {
.host = "172.25.67.2";
.port = "80";
}
backend web2 {
.host = "172.25.67.3";
.port = "80";
}
director lb round-robin {
{.backend = web1;}
{.backend = web2;}
}
sub vcl_recv {
if (req.http.host ~ "^(www.)?zpy.com") {
set req.http.host = "www.zpy.com";
set req.backend = lb; #把多个后端聚合为一个组,并检测后端健康状况
return (pass;)} #在测试过程中,为了方便不计入缓存
elsif (req.http.host ~ "^bbs.zpy.com") {
set req.backend = web2;
} else {error 404 "zpy cache";
}
}
总结:varnish客户端在取数据的时候有以下几点:
(1):大的数据直接通过pipe管道取
(2):正常情况下,客户端发起请求,varnish接受请求,然后去查自己的缓存日志,如果hit,则直接发送给client,如果没有,则去后台web站点内去取数据,返回给用户,同时存入自己的缓存中。
(3):当缓存超过TTL所设置的时长时,超时线程自动清理缓存,释放内存