varnish高速缓存服务

一. CDN技术简介

CDN的全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。

CDN 诞生于二十多年前,随着骨干网压力的逐渐增大,以及长传需求的逐渐增多,使得骨干网的压力越来越大,长传效果越来越差。于是在 1995 年,MIT 的应用数学教授 Tom Leighton 带领着研究生 Danny Lewin 和其他几位顶级研究人员一起尝试用数学问题解决网络拥堵问题。

他们使用数学算法,处理内容的动态路由安排,并最终解决了困扰 Internet 使用者的难题。后来,史隆管理学院的 MBA 学生 Jonathan Seelig 加入了 Leighton 的队伍中,从那以后他们开始实施自己的商业计划,最终于 1998 年 8 月 20 日正式成立公司,命名为 Akamai。

同年 1998 年,中国第一家 CDN 公司 蓝汛(ChinaCache)成立。

在接下来的20年中,CDN行业历经变革和持续发展,行业也涌现出很多云CDN厂商。阿里云CDN是2008年从淘宝CDN起家,在2014年正式发展成为阿里云CDN的,它不仅为阿里巴巴集团所有子公司提供服务,同时也将自身的资源、技术以云计算的方式输出。

CDN的功能

  • 加速网站的访问,主要是加速静态内容
  • 为了实现跨运营商、跨地域的全网覆盖
  • 为了保障你的网站安全
  • 为了异地备援

1. varnish缓存代理 - 图1

  • 当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS 系统会最终将域名的解析权交给 CNAME 指向的 CDN 专用 DNS 服务器;
  • CDN 的 DNS 服务器将 CDN 的全局负载均衡设备 IP 地址返回用户;
  • 用户向 CDN 的全局负载均衡设备发起内容 URL 访问请求;
  • CDN 全局负载均衡设备根据用户 IP 地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求;
  • 基于以下这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址:
    • 根据用户 IP 地址,判断哪一台服务器距用户最近;
    • 根据用户所请求的 URL 中携带的内容名称,判断哪一台服务器上有用户所需内容;
    • 查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。
  • 全局负载均衡设备把服务器的 IP 地址返回给用户;
  • 用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。

DNS 服务器根据用户IP地址,将域名解析成相应节点的缓存服务器IP地址,实现用户就近访问。使用 CDN 服务的网站,只需将其域名解析权交给 CDN 的全局负载均衡(GSLB)设备,将需要分发的内容注入CDN,就可以实现内容加速了。

常用的CDN缓存软件

  • Varnish :可以认为是内存缓存,速度一流,但是内存缓存也限制了其容量,缓存页面和图片一般是挺好的;
  • squid:是功能最全面的比较传统的web cache server,有自己的存储引擎。,但是架构太老,性能不怎样;
  • nginx:本来是反向代理/web服务器,用了插件可以做,但是本身不支持的功能比较多;

二. Varnish 原理

Varnish Cache是Web应用程序加速器,也称为缓存HTTP反向代理。您可以将其安装在任何使用HTTP的服务器之前,并将其配置为缓存内容。Varnish Cache确实非常快。根据您的体系结构,通常可以将交付速度提高300到1000倍。

2.1 varnish简介

根据官网 (https://www.varnish-cache.org/) 的介绍,Varnish 的主要特性如下:

  1. 缓存位置:可以使用内存也可以使用磁盘。如果要使用磁盘的话推荐 SSD 做 RAID1
  2. 日志存储:日志也存储在内存中。存储策略:固定大小,循环使用
  3. 支持虚拟内存的使用。
  4. 有精确的时间管理机制,即缓存的时间属性控制。
  5. 状态引擎架构:在不同的引擎上完成对不同的缓存和代理数据进行处理。可以通过特定的配置语言设计不同的控制语句,以决定数据在不同位置以不同方式缓存,在特定的地方对经过的报文进行特定规则的处理。
  6. 缓存管理:以二叉堆格式管理缓存数据,做到数据的及时清理。

2.2 Varnish与Squid的对比

相同点:

  • 都是一个反向代理服务器;
  • 都是开源软件;

Varnish的优势:

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

Varnish 的劣势:

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

Varnish 劣势的解决方案:

  • 针对劣势一:在访问量很大的情况下推荐使用varnish的内存缓存方式启动,而且后面需要跟多台 squid/nginx 服务器。主要为了防止前面的varnish 服务crash、服务器被重启的情况下,大量请求穿透 varnish,这样 squid/nginx 可以就担当第二层CACHE,而且也弥补了varnish缓存在内存中重启都会释放的问题;
  • 针对劣势二:可以在负载均衡上做url哈希,让单个url请求固定请求到一台 varnish服务器上;

2.3 使用varnish作为web代理缓存的原理

varnish是一个HTTP反向代理的缓存。它从客户端接收请求然后尝试从缓存中获取数据来响应客户端的请求,如果varnish不能从缓存中获得数据来响应客户端,它将转发请求到后端(backend servers),获取响应同时存储,最后交付给客户端。

如果varnish已经缓存了某个响应,它比你传统的后端服务器的响应要快很多,所以你需要尽可能是更多的请求直接从 varnish 的缓存中获取响应。

varnish决定是缓存内容还是从后端服务器获取响应。后端服务器能通过 HTTP响应头中的Cache-Control 来同步 varnish 缓存内容。在某些条件下 varnish将不缓存内容,最常见的是使用Cookie。当一个被标记有Cookie的客户端web请求,varnish默认是不缓存。这些众多的varnish功能特点都是可以通过写VCL来改变的。

2.4 简单架构:

Varnish 分为 management 进程和 child 进程;

  • Management 进程:对子进程进行管理,同时对VCL配置进行编译,并应用到不同的状态引擎;
  • Child 进程:生成线程池,负责对用户请求进行处理,并通过hash查找返回用户结果。

2.5 varnish 主要配置部分:

varnish 配置主要分为:后端配置,ACL 配置,probes 配置,directors 配置,核心子程序配置几大块。其中后端配置是必要的,在多台服务器中还会用到 directors 配置,核心子程序配置。

  • 后端配置:即给 varnish 添加反代服务器节点,最少配置一个。
  • ACL 配置:即给 varnish 添加访问控制列表,可以指定这些列表访问或禁止访问。
  • probes 配置:即给 varnish 添加探测后端服务器是否正常的规则,方便切换或禁止对应后端
    服务器。
  • directors 配置:即给 varnish 添加负载均衡模式管理多个后端服务器。
  • 核心子程序配置:即给 varnish 添加后端服务器切换,请求缓存,访问控制,错误处理等规则。

2.6 VCL - Varnish Configuration Language

VCL从C继承了很多东西,它读起来很像简单的C或Perl。

块用大括号分隔,语句以分号结尾,并且注释可以根据您自己的喜好以C,C ++或Perl编写。

请注意,VCL不包含任何循环或跳转语句。

字符串(Strings)

  • 基本字符串包含在“…”中,并且不能包含换行符。
  • 长字符串包含在{“…”}中。它们可以包含除NUL(0x00)字符以外的任何字符,包括“,换行符和其他控制字符。

Access control lists (ACLs)

ACL声明创建并初始化一个命名的访问控制列表,该列表可用于匹配客户端地址:

  1. acl local {
  2. "localhost"; // myself
  3. "192.0.2.0"/24; // and everyone on the local network
  4. ! "192.0.2.23"; // except for the dialin router
  5. }
  6. ##应用acl,简单的使用匹配操作符
  7. if (client.ip ~ local) {
  8. return (pipe);
  9. }

操作符(Operators)

符号 解释
= 赋值运算符
== 比较运算符
~ 匹配,可以与正则表达式或ACL一起使用
! 取反
&& 逻辑and
|| 逻辑or

内置子程序(Built in subroutines)

Varnish具有许多内置子例程,这些子例程在每次事务流经Varnish时都会被调用。这些内置子例程均已命名vcl_*

内置子程序的处理以return (<action>)结尾。

自定义子程序(Custom subroutines)

可以自己写自定义子程序,名称不能以vcl_开头。

子例程通常用于对代码进行分组以提高可读性或可重用性。

  1. sub pipe_if_local {
  2. if (client.ip ~ local) {
  3. return (pipe);
  4. }
  5. }

使用call关键字进行调用

  1. call pipe_if_local;

VCL中的自定义子程序不接受参数,也不返回值。

2.7 请求和响应的VCL objects

1. varnish缓存代理 - 图2

  • req:请求对象。Varnish收到请求后,将创建并填充req对象。您在vcl_recv中执行的大部分工作都是在req对象上或与req对象一起执行的。
  • bereq:后端请求对象。Varnish在将其发送到后端之前先构造它。它基于req对象。
  • beresp:后端响应对象。它包含来自后端的对象的标头。如果要修改来自服务器的响应,请在vcl_backend_response中修改此对象。
  • resp:将HTTP响应传递给客户端之前。通常在vcl_deliver中对其进行修改。
  • obj:存储在内存中对象属性相关的可用的变量,只读。

预设变量

是系统固定的,请求进入对应的vcl子程序后便生成,这些变量可以方便子程序提取,当然也可以自定义一些全局变量。

  1. ##当前时间:
  2. now :作用:返回当前时间戳。
  3. ##客户端:(客户端基本信息)
  4. client.ip:返回客户端 IP 地址。
  5. ##注:原 client.port 已经弃用,如果要取客户端请求端口号使用 std.port(client.ip),需要 import std;才可以使用 std
  6. client.identity:用于装载客户端标识码。
  7. ##服务器:(服务器基本信息)
  8. ##注:原 server.port 已经弃用,如果要取服务器端口号使用 std.port(server.ip),需要 import std;才可以使用 std
  9. server.hostname:服务器主机名。
  10. server.identity:服务器身份标识。
  11. server.ip:返回服务器端 IP 地址。

req :(客户端发送的请求对象)

  1. req:整个 HTTP 请求数据结构
  2. req.backend_hint:指定请求后端节点,设置后bereq.backend才能获取后端节点配置数据
  3. req.can_gzip:客户端是否接受 GZIP 传输编码。
  4. req.hash_always_miss:是否强制不命中高速缓存,如果设置为 true,则高速缓存不会命中,一直从后端获取新数据。
  5. req.hash_ignore_busy:忽略缓存中忙碌的对象,多台缓存时可以避免死锁。
  6. req.http:对应请求 HTTP header
  7. req.method:请求类型(如 GET , POST)。
  8. req.proto:客户端使用的 HTTP 协议版本。
  9. req.restarts:重新启动次数。默认最大值是 4
  10. req.ttl:缓存有剩余时间。
  11. req.url:请求的 URL
  12. req.xid:唯一 ID

bereq:(发送到后端的请求对象,基于 req 对象)

  1. bereq 整个后端请求后数据结构。
  2. bereq.backend 所请求后端节点配置。
  3. bereq.between_bytes_timeout 从后端每接收一个字节之间的等待时间(秒)。
  4. bereq.connect_timeout 连接后端等待时间(秒),最大等待时间。
  5. bereq.first_byte_timeout 等待后端第一个字节时间(秒),最大等待时间。
  6. bereq.http 对应发送到后端 HTTP header 信息。
  7. bereq.method 发送到后端的请求类型(如:GET , POST)。
  8. bereq.proto 发送到后端的请求的 HTTP 版本。
  9. bereq.retries 相同请求重试计数。
  10. bereq.uncacheable 无缓存这个请求。
  11. bereq.url 发送到后端请求的 URL
  12. bereq.xid 请求唯一 ID

beresp:(后端响应请求对象)

  1. beresp 整个后端响应 HTTP 数据结构。
  2. beresp.backend.ip 后端响应的 IP
  3. beresp.backend.name 响应后端配置节点的 name
  4. beresp.do_gunzip 默认为 false 。缓存前解压该对象
  5. beresp.do_gzip 默认为 false 。缓存前压缩该对象
  6. beresp.grace 设置当前对象缓存过期后可额外宽限时间,用于特殊请求加大缓存时间,
  7. 当并发量巨大时,不易设置过大否则会堵塞缓存,一般可设置 1m 左右,
  8. beresp.ttl=0s 时该值无效。
  9. beresp.http 对应的 HTTP 请求 header
  10. beresp.keep 对象缓存后带保持时间
  11. beresp.proto 响应的 HTTP 版本
  12. beresp.reason 由服务器返回的 HTTP 状态信息
  13. beresp.status 由服务器返回的状态码
  14. beresp.storage_hint 指定保存的特定存储器
  15. beresp.ttl 该对象缓存的剩余时间,指定统一缓存剩余时间。
  16. beresp.uncacheable 继承 bereq.uncacheable,是否不缓存

resp :(返回给客户端的响应对象)

  1. resp 整个响应 HTTP 数据结构。
  2. resp.http 对应 HTTP header
  3. resp.proto 编辑响应的 HTTP 协议版本。
  4. resp.reason 将要返回的 HTTP 状态信息。
  5. resq.status 将要返回的 HTTP 状态码。

OBJ :(高速缓存对象,缓存后端响应请求内容)

  1. obj.grace:该对象额外宽限时间
  2. obj.hits:缓存命中次数,计数器从1开始,当对象缓存该值为1
  3. 一般可以用于判断是否有缓存,当前该值大于 0 时则为有缓存。
  4. obj.http 对应 HTTP header
  5. obj.proto HTTP 版本
  6. obj.reason 服务器返回的 HTTP 状态信息
  7. obj.status 服务器返回的状态码
  8. obj.ttl 该对象缓存剩余时间(秒)
  9. obj.uncacheable 不缓存对象

存储

  1. storage.<name>.free_space 存储可用空间(字节数)。
  2. storage.<name>.used_space 存储已经使用空间(字节数)。
  3. storage.<name>.happy 存储健康状

2.8 特定功能性语句

  • ban(expression): 清除指定对象缓存;
  • call(subroutine): 调用子程序,如:call(name);
  • hash_data(input)
    :生成hash键,用于制定hash键值生成结构,只能在vcl_hash子程序中使用。调用hash_data(input)后,即这个hash为当前页面的缓存hash键值,无需其它获取或操作,如:

    1. sub vcl_hash {
    2. hash_data(client.ip);
    3. return(lookup);
    4. }
    5. ##注意:return(lookup); 是默认返回值,所以可以不写。
  • new():创建一个 vcl 对象,只能在 vcl_init 子程序中使用;

  • return():结束当前子程序,并指定继续下一步动作,如:return (ok); 每个子程序可指定的动作均有不同;
  • rollback():恢复HTTP头到原来状态,已经弃用,使用 std.rollback() 代替;
  • synthetic(STRING)
    :合成器,用于自定义一个响应内容,比如当请求出错时,可以返回自定义404内容,而不只是默认头信息,只能在vcl_synth 与vcl_backend_error子程序中使用,如:

    1. sub vcl_synth {
    2. //自定义内容
    3. synthetic({"
    4. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    5. <html lang="zh-cn">
    6. <head>
    7. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    8. <title>error</title>
    9. </head>
    10. <body>
    11. <h1>Error</h1>
    12. <h3>这只是一个测试自定义响应异常内容</h3>
    13. </body>
    14. </html>
    15. "});
    16. //只交付自定义内容
    17. return(deliver);
    18. }
  • regsub(str,regex,sub):使用正则替换第一次出现的字符串,第一个参数为待处理字符串,第二个参数为正则表达式,第三个为替换为字符串。

  • regsuball(str, regex, sub):使用正则替换所有匹配字符串。参数与 regsuball 相同。

具体变量详见:
https://varnish-cache.org/docs/6.4/reference/vcl.html#varnish-configuration-language

2.9 return 语句

return 语句是终止子程序并返回动作,所有动作都根据不同的 vcl 子程序限定来选用。

  1. 语法:return(action);
  2. ##常用的动作:
  3. abandon:放弃处理,并生成一个错误。
  4. deliver:交付处理
  5. fetch:从后端取出响应对象
  6. hash:哈希缓存处理
  7. lookup:查找缓存对象
  8. ok:继续执行
  9. pass:进入 pass 非缓存模式
  10. pipe:进入 pipe 非缓存模式
  11. purge:清除缓存对象,构建响应
  12. restart:重新开始
  13. retry:重试后端处理
  14. synth(status code,reason):合成返回客户端状态信息

2.10 内置子程序(Built in subroutines)

子程序是一种类似C的函数,但是程序没有调用参数,子程序以sub关键字定义。在VCL里子程序是用于管理程序。

注意:所有VCL内置的程序都是以vcl_开头,并已经预置好,在VCL文件中只要声明对应的内置子程序,都会在对应的流程中调用。

varnish 内置子程序均有自己限定的返回动作return(action);不同的动作将调用对应下一个子程序。

2.10.1 客户端

vcl_recv

在请求开始时调用,在接收和分析完完整的请求后调用,在重新启动后调用,或作为ESI include的结果调用。

它的目的是决定是否服务于请求,可能修改请求并决定如何进一步处理请求。后端提示可以设置为后端处理端的默认提示。

调用return()的动作可以是:

  • fail
  • synth(status code, reason)
  • restart
  • pass
  • pipe
  • hash
  • purge
  • vcl(label)

vcl_pipe

进入管道模式时调用。在这种模式下,请求将传递到后端,而来自客户端和后端的任何其他数据都将保持不变,直到任一端关闭连接为止。基本上,Varnish会降级为简单的TCP代理,来回移动字节。对于管道模式的连接,在vcl_pipe之后将再没有其他VCL子例程被调用。

调用return()的动作可以是:

  • fail
  • synth(status code, reason)
  • pipe

vcl_pass

在这种模式下,请求将传递到后端,后端的响应将传递到客户端,但不会输入到缓存中。通过同一客户端连接提交的后续请求将得到正常处理。

调用return()的动作可以是:

  • fail
  • synth(status code, reason)
  • restart
  • fetch

vcl_hash

在vcl_recv之后调用以创建请求的哈希值,在varnish中用于查找对象的key值。

调用return()的动作可以是:

  • fail
  • lookup
    • lookup 查找缓存对象,存在缓存进入 vcl_hit 子程序,不存在缓存进入 vcl_miss 子程序,当使用了purge 清理模式时会进入 vcl_purge 子程序,默认返回值。

vcl_purge

在清除已执行且其所有变体已被逐出后调用。

调用return()的动作可以是:

  • fail
  • synth(status code, reason)
  • restart

vcl_miss

如果在缓存中找不到请求的文档,或者vcl_hit返回fetch,则在缓存查找后调用。

其目的是决定是否尝试从后端检索文档。后端提示可以设置为后端处理端的默认提示。

调用return()的动作可以是:

  • fail
  • synth(status code, reason)
  • restart
  • pass
  • fetch
    • 从后端检索请求的对象。控件最终将传递给vcl_backend_fetch。

vcl_hit

高速缓存查找成功时调用。被命中的对象可能是陈旧的。

调用return()的动作可以是:

  • fail
  • synth(status code, reason)
  • restart
  • pass
  • deliver
    • 交付对象。如果它已过时,将触发后台提取以刷新它。

vcl_deliver

在将除了vcl_synth结果外的任何对象交付给客户端之前调用。

调用return()的动作可以是:

  • fail
  • synth(status code, reason)
  • restart
  • deliver
    • 将对象交付给客户端

vcl_synth

调用以传递合成对象。在VCL中生成一个合成对象,而不是从后端获取。可以使用synthetic()函数构造其主体。

vcl_synth定义的对象永远不会进入缓存,这与vcl_backend_error定义的对象相反,后者可能最终进入缓存。

调用return()的动作可以是:

  • fail
  • restart
  • deliver
    • 直接将vcl_synth定义的对象交付给客户端,而无需调用vcl_deliver。

2.10.2 后端

vcl_backend_fetch

在发送后端请求之前调用。在此子例程中,通常会在请求到达后端之前对其进行更改,可用于改变请求地址或其它信息,或放弃请求。

调用return()的动作可以是:

  • fail
  • abandon
  • fetch
  • error(status code, reason)

vcl_backend_response

从后端成功检索到响应头后调用,可用于修改缓存时间及缓存相关信息。

调用return()的动作可以是:

  • fail
  • abandon
  • deliver
  • retry
  • pass(duration)
  • error(status code, reason)

vcl_backend_error

如果后端获取失败或已超过最大重试次数,则调用此子例程。

调用return()的动作可以是:

  • fail
  • abandon
  • deliver
  • retry

2.10.3 vcl.load / vcl.discard期间

vcl_init

在加载VCL时调用,在任何请求通过之前调用。通常用于初始化VMOD。

调用return()的动作可以是:

  • ok
  • fail

vcl_fini

只有在所有请求都退出VCL后要放弃VCL时调用。通常用于清理VMOD。

调用return()的动作可以是:

  • ok

子程序关系图

1. varnish缓存代理 - 图3

三. 安装 Varnish

3.1 配置YUM源
  1. [root@varnish-cache ~]# cat /etc/yum.repos.d/varnishcache_varnish64.repo
  2. [varnishcache_varnish64]
  3. name=varnishcache_varnish64
  4. baseurl=https://packagecloud.io/varnishcache/varnish64/el/7/$basearch
  5. repo_gpgcheck=1
  6. gpgcheck=0
  7. enabled=1
  8. gpgkey=https://packagecloud.io/varnishcache/varnish64/gpgkey
  9. sslverify=1
  10. sslcacert=/etc/pki/tls/certs/ca-bundle.crt
  11. metadata_expire=300
  12. [varnishcache_varnish64-source]
  13. name=varnishcache_varnish64-source
  14. baseurl=https://packagecloud.io/varnishcache/varnish64/el/7/SRPMS
  15. repo_gpgcheck=1
  16. gpgcheck=0
  17. enabled=1
  18. gpgkey=https://packagecloud.io/varnishcache/varnish64/gpgkey
  19. sslverify=1
  20. sslcacert=/etc/pki/tls/certs/ca-bundle.crt
  21. metadata_expire=300

3.2 安装
  1. [root@varnish-cache ~]# yum install pygpgme yum-utils
  2. [root@varnish-cache ~]# yum install varnish -y
  3. [root@varnish-cache ~]# systemctl enable varnish.service
  4. [root@varnish-cache ~]# systemctl start varnish.service
  5. [root@varnish-cache ~]# systemctl status varnish.service

四. Varnish配置解析

varnish 配置基本上是编辑 VCL(Varnish Configuration Language) 文件,varnish 有一套自定义VCL语法,启动时,会将配置文件编译为C语言,再执行。

  • varnish 4.0 开始,每个VCL文件必须在开始行声明它的版本,如:vcl 4.1;
  • 块(子程序)由大括号分隔,语句用分号结束。所有的关键字及预设子程序名都是全小写。
  • 注释:支持 // 或 # ,多行时还可以使用 //

4.1 后端服务器地址池配置及后端服务器健康检查

varnish 有”后端”或者”源”服务器的概念。backend server 提供给 varnish 加速的内容。实际上就是给 varnish添加可供访问的web服务器,如果有多台 web 服务器时,可添加多个backend块。

4.1.1 定义后端服务器

命令:backend。这个定义为最基本的反向入口定义,用于 varnish 连接对应的服务器,如果没有定义或定义错误则用户无法访问正常页面。

语法格式

  1. backend name {
  2. .attribute = "value";
  3. }
  • backend 是定义后端关键字;
  • name 是当前后端节点的别名,多个后端节点时,name名不能重复,否则覆盖;
  • 花括号里面定义当前节点相关的属性(键=值);
  • 除默认节点外其它节点定义后必需有调用,否则 varnish 无法启动;
  • 后端是否正常可以通过std.healthy(backend)判断。

属性列表

  1. .host="xxx.xxx.xxx.xxx"; //要转向主机(即后端主机)的 IP 或域名,必填键/值对。
  2. .port="8080"; //主机连接端口号或协议名(HTTP 等),默认 80
  3. .host_header=''; //请示主机头追加内容
  4. .connect_timeout=1s; //连接后端的超时时间
  5. .first_byte_timeout=5s; //等待从后端返回的第一个字节时间
  6. .between_bytes_timeout=2s; //每接收一个字节之间等待时间
  7. .probe=probe_name; //监控后端主机的状态,指定外部监控 name 或者内部直接添加
  8. .max_connections=200; //设置最大并发连接数,超过这个数后连接就会失败

配置样例:(下面两个例子结果是一样的,但第二个例子中更适用于集群,可以方便批量修改)

  1. backend web {
  2. .host="192.168.31.83";
  3. .port="80";
  4. .probe={ //直接追加监控块.probe 是一个的参数
  5. .url="/";
  6. .timeout=2s;
  7. }
  8. }
  9. ##或者
  10. probe web_probe { //监控必需定义在前面,否则后端调用找不到监控块。
  11. .url="/";
  12. .timeout=2s;
  13. }
  14. backend web {
  15. .host="192.168.31.83";
  16. .port="80";
  17. .probe=web_probe; //调用外部共用监控块
  18. }

4.1.2 定义监视器

命令:probe 。监控可以循环访问指定的地址,通过响应时间判定服务器是否空闲或正常。这类命令非常适用于集群中某些节点服务器崩溃或负载过重,而禁止访问这台节点服务器。

语法格式

  1. probe name {
  2. .attribute = "value";
  3. }
  • probe 是定义监控关键字;
  • name 是当前监控点的别名,多个监控节点时,name名不能重复,否则覆盖;
  • 花括号里面定义当前节点相关的属性(键=值)。没有必填属性,因为默认值就可以正常执行操作。

属性列表

  1. .url="/"; //指定监控入口 URL 地址,默认为"/"
  2. .request=""; //指定监控请求入口地址,比 .url 优先级高。
  3. .expected_response="200"; //请求响应代码,默认是 200
  4. .timeout=2s; //请求超时时间。
  5. .interval=5s; //每次轮询请求间隔时间,默认为 5s 。
  6. .initial=-1; //初始启动时以.window 轮询次数中几次良好后续才能使用这个后端服务器节
  7. 点,默认为 -1 ,则轮询完 .window 所有次数良好判定为正常。
  8. .window=8; //指定多少轮询次数,用于判定服务器正常,默认是 8。
  9. .threshold=3; //必须多少次轮询正常才算该后端节点服务器正常,默认是 3。

配置样例:创建健康监测,定义健康检查名称为 backend_healthcheck

  1. probe backend_healthcheck {
  2. .url = "/";
  3. .timeout = 1s;
  4. .interval = 5s;
  5. .window = 5;
  6. .threshold = 3;
  7. }
  8. ##在上面的例子中 varnish将每5s检测后端,超时设为1s。每个检测将会发送 get /的请求。
  9. ##如果 5 个检测中大于3个是成功,varnish就认为后端是健康的,反之,后端就有问题了。

4.2 集群负载均衡 directors

varnish 可以定义多个后端,支持虚拟主机,也可以将几个后端组成一组后端。这个组被叫做 Directors。可以提高性能和弹性。

directors 是 varnish负载均衡模块,使用前必需引入directors模块,directors 模块主要包含:round_robin,random,hash,fallback负载均衡模式。

  • round_robin : 循环依次逐个选择后端服务器;
  • random : 随机选择后端服务器,可设置每个后端权重增加随机率;
  • hash : 通过散列随机选择对应的后端服务器且保持选择对应关系,下次则直接找对应的后端服务器;
  • fallback:后备。

注意:random,hash有权重值设置,用于提高随机率。每个后端最好都配置监控器(后端服务器正常监测)以便directors自动屏蔽不正常后端而不进入均衡列队中。这些操作需要你载入 VMOD(varnish module),然后在vcl_init 中调用这个 VMOD。

  1. import directors; ## load the directors
  2. backend web1 {
  3. .host = "192.168.0.10";
  4. .port = "80";
  5. .probe = backend_healthcheck;
  6. }
  7. backend web2 {
  8. .host = "192.168.0.11";
  9. .port = "80";
  10. .probe = backend_healthcheck;
  11. }
  12. ##初始化处理
  13. sub vcl_init { ##调用vcl_init初始化子程序创建后端主机组,即directors
  14. new web_cluster = directors.round_robin(); ##使用 new 关键字创建 drector 对象,使用 round_robin 算法
  15. web_cluster.add_backend(web1); ##添加后端服务器节点
  16. web_cluster.add_backend(web2);
  17. }
  18. ##开始处理请求
  19. sub vcl_recv { ##调用 vcl_recv 子程序,用于接收和处理请求
  20. set req.backend_hint = web_cluster.backend(); ##选取后端
  21. }

说明:

  • set 命令是设置变量;
  • unset 命令是删除变量;
  • web_cluster.add_backend( backend , real );添加后端服务器节点,backend为后端配置别名,real为权重值,随机率计算公式:100 * (当前权重 / 总权重);
  • req.backend_hint 是 varnish 的预定义变量,作用是指定请求后端节点;
  • vcl 对象需要使用new关键字创建,所有可创建对象都是内定的,使用前必需 import,所有new 操作只能在 vcl_init 子程序中。

扩展:varnish 将不同的URL发送到不同的后端server
  1. import directors; ##load the directors
  2. backend web1 {
  3. .host = "192.168.0.10";
  4. .port = "80";
  5. .probe = backend_healthcheck;
  6. }
  7. backend web2 {
  8. .host = "192.168.0.11";
  9. .port = "80";
  10. .probe = backend_healthcheck;
  11. }
  12. backend img1 {
  13. .host = "img1.lnmmp.com";
  14. .port = "80";
  15. .probe = backend_healthcheck;
  16. }
  17. backend img2 {
  18. .host = "img2.lnmmp.com";
  19. .port = "80";
  20. .probe = backend_healthcheck;
  21. }
  22. ##初始化处理
  23. sub vcl_init { ##调用vcl_init初始化子程序创建后端主机组,即directors
  24. new web_cluster = directors.round_robin(); ##使用new关键字创建 drector对象,使用round_robin算法
  25. web_cluster.add_backend(web1); ##添加后端服务器节点
  26. web_cluster.add_backend(web2);
  27. new img_cluster = directors.random();
  28. img_cluster.add_backend(img1,2); ##添加后端服务器节点,并且设置权重值
  29. img_cluster.add_backend(img2,5);
  30. }
  31. ##根据不同的访问域名,分发至不同的后端主机组
  32. sub vcl_recv {
  33. if (req.http.host ~ "(?i)^(www.)?benet.com$") {
  34. set req.http.host = "www.benet.com";
  35. set req.backend_hint = web_cluster.backend(); ##选取后端
  36. } elsif (req.http.host ~ "(?i)^images.benet.com$") {
  37. set req.backend_hint = img_cluster.backend();
  38. }
  39. }
  40. ##说明:中的i就是忽略大小写的意思。(?i)表示开启忽略大小写,而(?-i)表示关闭忽略大小写

4.3 访问控制列表(ACL):

创建一个地址列表,用于后面的判断,可以是域名或IP集合。这个可以用于指定某些地址请求入口,防止恶意请求等。

语法格式

  1. acl purgers {
  2. "127.0.0.1";
  3. "localhost";
  4. "192.168.134.0/24";
  5. ! "192.168.134.1";
  6. }
  7. ##说明:acl是访问列表关键字(必需小写),
  8. ##name:是该列表的别名用于调用,花括号内部是地址集。
  9. ##注意:如果列表中包含了无法解析的主机名,acl会匹配任何地址与之比较。
  10. ##可以在前添加一个“!”符号,acl则会拒绝任何地址与之比较
  11. ##如果其被括在括号内,则会简单的被忽略
  12. ##使用ACL只需要用匹配运算符 ~ 或 !~ 如:
  13. sub vcl_recv {
  14. if (req.method == "PURGE") { //PURGE 请求的处理
  15. if (client.ip ~ purgers) {
  16. return(purge);
  17. } else {
  18. return(synth(403, "Access denied."));
  19. }
  20. }
  21. }

4.4 缓存规则配置:

  1. sub vcl_recv {
  2. // PURGE 请求的处理
  3. if (req.method == "PURGE") {
  4. if (!client.ip ~ purgers) {
  5. return (synth(405, "Not Allowed."));
  6. }
  7. return (purge);
  8. }
  9. set req.backend_hint = web.backend();
  10. // 将php、asp等动态内容访问请求直接发给后端服务器,不缓存。
  11. if (req.url ~ "\.(php|asp|aspx|jsp|do|ashx|shtml)($|\?)") {
  12. return (pass);
  13. }
  14. // 将非GET和HEAD访问请求直接发给后端服务器,不缓存。例如 POST 请求。
  15. if (req.method != "GET" && req.method != "HEAD") {
  16. return (pass);
  17. }
  18. //如果varnish看到header中有'Authorization'头,它将pass请求。
  19. if (req.http.Authorization) {
  20. return (pass);
  21. }
  22. //带 cookie首部的GET请求也缓存
  23. if (req.url ~ "\.(css|js|html|htm|bmp|png|gif|jpg|jpeg|ico|gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)($|\?)") {
  24. unset req.http.cookie;
  25. return (hash);
  26. }
  27. //说明:默认情况,varnish不缓存从后端响应的http头中带有Set-Cookie的对象。
  28. //如果客户端发送的请求带有Cookie header,varnish将忽略缓存,直接将请求传递到后端。
  29. //为发往后端主机的请求添加X-Forward-For首部,首次访问增加 X-Forwarded-For 头信息,
  30. //方便后端程序获取客户端IP,而不是varnish地址
  31. if (req.restarts == 0) {
  32. if (req.http.x-forwarded-for) { //如果设置过此 header 则要再次附加上用逗号隔开
  33. set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
  34. } else { //如果只有一层代理的话,就无需设置了
  35. set req.http.X-Forwarded-For = client.ip;
  36. }
  37. }
  38. }
  39. //说明:X-Forwarded-For是用来识别通过HTTP代理或负载均衡方式连接到
  40. //Web服务器的客户端最原始的IP地址的HTTP请求头字段。

五. Varnish 配置案例

案例环境

主机名 IP地址 操作系统 软件版本
varnish-server 192.168.154.160/24 Centos 7.7 varnishi 6.4.0
web01 192.168.154.181/24 Centos 7.7 nginx
web02 192.168.154.182/24 Centos 7.7 nginx

5.1基本环境配置

  • IP地址配置
  • 主机名设置
  • 关闭Selinux

5.2 安装varnish及后端服务(略)

5.3 配置varnish

先备份一下配置文件,然后再修改

  1. [root@varnish-server ~]# cp /etc/varnish/default.vcl /etc/varnish/default.vcl.bak
  2. [root@varnish-server ~]# vim /etc/varnish/default.vcl

配置文件修改如下:

  1. ##使用varnish版本4的格式
  2. vcl 4.1;
  3. ##加载后端负载均衡模块
  4. import directors;
  5. ##加载 std 模块
  6. import std;
  7. ##创建名为 backend_healthcheck 的健康检查策略
  8. probe backend_healthcheck {
  9. .url = "/";
  10. .interval = 5s;
  11. .timeout = 1s;
  12. .window = 5;
  13. .threshold = 3;
  14. }
  15. ## Default backend definition. Set this to point to your content server.
  16. ## 定义后端服务器
  17. backend web_app_01 {
  18. .host = "192.168.154.181";
  19. .port = "80";
  20. .first_byte_timeout = 9s;
  21. .connect_timeout = 3s;
  22. .between_bytes_timeout = 1s;
  23. .probe = backend_healthcheck;
  24. }
  25. backend web_app_02 {
  26. .host = "192.168.154.182";
  27. .port = "80";
  28. .first_byte_timeout = 9s;
  29. .connect_timeout = 3s;
  30. .between_bytes_timeout = 1s;
  31. .probe = backend_healthcheck;
  32. }
  33. ## vcl_init 初始化子程序创建后端主机组
  34. sub vcl_init {
  35. new web = directors.round_robin();
  36. web.add_backend(web_app_01);
  37. web.add_backend(web_app_02);
  38. }
  39. ## 定义允许清理缓存的IP
  40. acl purgers {
  41. "127.0.0.1";
  42. "localhost";
  43. "192.168.154.0/24";
  44. }
  45. ##请求入口,用于接收和处理请求。这里一般用作路由处理,
  46. ##判断是否读取缓存和指定该请求使用哪个后端
  47. sub vcl_recv {
  48. ##将请求指定使用 web 后端集群 .在集群名后加上 .backend()
  49. set req.backend_hint = web.backend();
  50. ##匹配清理缓存的请求
  51. if (req.method == "PURGE") {
  52. if (!client.ip ~ purgers) {
  53. return(synth(405,"Not Allowed"));
  54. }
  55. ##是的话就执行清理
  56. return (purge);
  57. }
  58. ##如果不是正常请求 就直接穿透没商量
  59. if (req.method != "GET" &&
  60. req.method != "HEAD" &&
  61. req.method != "PUT" &&
  62. req.method != "POST" &&
  63. req.method != "TRACE" &&
  64. req.method != "OPTIONS" &&
  65. req.method != "PATCH" &&
  66. req.method != "DELETE") {
  67. return (pipe);
  68. }
  69. ##如果不是 GET 和 HEAD 就跳到 pass
  70. if (req.method != "GET" && req.method != "HEAD") {
  71. return (pass);
  72. }
  73. ##如果匹配动态内容访问请求就跳到 pass
  74. if (req.url ~ "\.(php|asp|aspx|jsp|do|ashx|shtml)($|\?)") {
  75. return (pass);
  76. }
  77. ##具有身份验证的请求跳到 pass
  78. if (req.http.Authorization) {
  79. return (pass);
  80. }
  81. if (req.http.Accept-Encoding) {
  82. if (req.url ~ "\.(bmp|png|gif|jpg|jpeg|ico|gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)$") {
  83. unset req.http.Accept-Encoding;
  84. }
  85. elseif (req.http.Accept-Encoding ~ "gzip") {
  86. set req.http.Accept-Encoding = "gzip";
  87. }
  88. elseif (req.http.Accept-Encoding ~ "deflate") {
  89. set req.http.Accept-Encoding = "deflate";
  90. }
  91. else {
  92. unset req.http.Accept-Encoding;
  93. }
  94. }
  95. if (req.url ~ "\.(css|js|html|htm|bmp|png|gif|jpg|ico|gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)($|\?)") {
  96. unset req.http.cookie;
  97. return (hash);
  98. }
  99. ##把真实客户端 IP 传递给后端服务器 后端服务器日志使用 X-Forwarded-For 来接收
  100. if (req.restarts == 0) {
  101. if (req.http.X-Forwarded-For) {
  102. set req.http.X-Forwarded-For = req.http.X-Forwarded-For + "," + client.ip;
  103. }
  104. else {
  105. set req.http.X-Forwarded-For = client.ip;
  106. }
  107. }
  108. return (hash);
  109. }
  110. ##hash 事件(缓存事件)
  111. sub vcl_hash {
  112. hash_data(req.url);
  113. if (req.http.host) {
  114. hash_data(req.http.host);
  115. } else {
  116. hash_data(server.ip);
  117. }
  118. return (lookup);
  119. }
  120. ##缓存命中事件
  121. sub vcl_hit {
  122. if (req.method == "PURGE") {
  123. return (synth (200,"Purged."));
  124. }
  125. return (deliver);
  126. }
  127. ##缓存不命中事件
  128. sub vcl_miss {
  129. if (req.method == "PURGE") {
  130. return (synth (404,"Purged."));
  131. }
  132. return (fetch);
  133. }
  134. ##处理对后端返回结果的事件(设置缓存、移除 cookie 信息、设置 header 头等)
  135. ##在 fetch 事件后自动调用
  136. sub vcl_backend_response {
  137. ##开启 grace 模式 表示当后端全挂掉后 即使缓存资源已过期(超过缓存时间)
  138. ##也会把该资源返回给用户 资源最大有效时间为 5 分钟
  139. set beresp.grace = 5m;
  140. ##后端返回如下错误状态码,则不缓存
  141. if (beresp.status == 499 || beresp.status == 404 || beresp.status == 502) {
  142. set beresp.uncacheable = true;
  143. }
  144. ##如请求 php 或 jsp 则不缓存
  145. if (bereq.url ~ "\.(php|jsp)(\?|$)") {
  146. set beresp.uncacheable = true;
  147. }
  148. else { ##自定义缓存文件的缓存时长,即 TTL 值
  149. if (bereq.url ~ "\.(css|js|html|htm|bmp|png|gif|jpg|jpeg|ico)($|\?)") {
  150. set beresp.ttl = 15m;
  151. unset beresp.http.Set-Cookie;
  152. }
  153. elseif (bereq.url ~ "\.(gz|tgz|bz2|tbz|zip|rar|mp3|mp4|ogg|swf|flv)($|\?)") {
  154. set beresp.ttl = 30m;
  155. unset beresp.http.Set-Cookie;
  156. }
  157. else {
  158. set beresp.ttl = 10m;
  159. unset beresp.http.Set-Cookie;
  160. }
  161. }
  162. ##返回给用户
  163. return (deliver);
  164. }
  165. ##返回给用户的前一个事件 通常用于添加或删除 header 头
  166. sub vcl_deliver {
  167. if (obj.hits > 0) {
  168. set resp.http.X-Cache = "HIT";
  169. set resp.http.X-Cache-Hits = obj.hits;
  170. } else {
  171. set resp.http.X-Cache = "MISS";
  172. }
  173. ##取消显示 php 框架版本的 header 头
  174. unset resp.http.X-Powered-By;
  175. ##取消显示 web 软件版本、Via(来自 varnish)等 header 头,为了安全
  176. unset resp.http.Server;
  177. unset resp.http.X-Drupal-Cache;
  178. unset resp.http.Via;
  179. unset resp.http.Link;
  180. unset resp.http.X-Varnish;
  181. ##显示请求经历 restarts 事件的次数
  182. set resp.http.xx_restarts_count = req.restarts;
  183. ##显示该资源缓存的时间单位秒
  184. set resp.http.xx_Age = resp.http.Age;
  185. ##显示该资源命中的次数
  186. set resp.http.hit_count = obj.hits;
  187. ##取消显示Age,为了不和CDN冲突
  188. unset resp.http.Age;
  189. ##返回给用户
  190. return (deliver);
  191. }
  192. ## pass事件
  193. sub vcl_pass {
  194. return (fetch);
  195. }
  196. sub vcl_purge {
  197. return (synth (200,"success"));
  198. }
  199. sub vcl_backend_error {
  200. if (beresp.status == 500 ||
  201. beresp.status == 501 ||
  202. beresp.status == 502 ||
  203. beresp.status == 503 ||
  204. beresp.status == 504) {
  205. return (retry);
  206. }
  207. }
  208. sub vcl_fini {
  209. return (ok);
  210. }

5.4 启动varnish

当启动 varnish 时有两个重要的参数你必须设置
  • 一个是处理HTTP请求的TCP监听端口;
  • 另一个是处理真实请求的后端Server;

使用的命令为:varnishd
  • -a:定义了varnish监听在哪个地址,并用该地址处理http请求,应该将其设置为众所周知的80端口

    • 例如:
      1. -a :80
      2. -a localhost:80
      3. -a 192.168.1.100:8080
      4. -a '[fe80::1]:80'
      5. -a '0.0.0.0:8080,[::]:8081'
  • -f:指定vcl文件

  • -b:定义后端Server,一般是使用配置文件定义
  • -s:定义分配的缓存内存大小
    • 例如:
      1. -s malloc,256m

因为我这个是YUM安装的,需要更改systemd unit文件

  1. [root@varnish-server ~]# vim /usr/lib/systemd/system/varnish.service
  2. ExecStart=/usr/sbin/varnishd -a :80 -f /etc/varnish/default.vcl -s malloc,4096m
  3. [root@varnish-server ~]# systemctl daemon-reload
  4. [root@varnish-server ~]# systemctl restart varnish.service
  5. [root@varnish-server ~]# netstat -antp | grep :80
  6. tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 6329/varnishd
  7. tcp6 0 0 :::80 :::* LISTEN 6329/varnishd

5.5 客户端访问

第一次访问

第一次访问应该是MISS

1. varnish缓存代理 - 图4

第二次访问

第二次访问应该是HIT

1. varnish缓存代理 - 图5