架构高性能网站秘笈(四)——反向代理缓存

什么是反向代理?

在介绍“反向代理”之前,首先要介绍一下“正向代理”的概念。

1.什么是正向代理?

在NAT技术(Network Address Translation)出现之前,所有主机无法直接与外网相连,要想上网,需要连接到一台能够访问外网的Web服务器,再通过这台服务器访问外网。而这台Web服务器就叫做“正向代理服务器”。

现在的“翻墙”技术也是如何,我们把请求发给一台可以连接外面世界的Web服务器,由它转发我们的请求,再将结果返回给我们。这台Web服务器就是“正向代理服务器”。

综上所述:正向代理服务器是客户端和目的服务器之间的一个中介,客户端通过正向代理服务器访问客户端原本无法访问的目标服务器。 

2.什么是反向代理?

客户端向一个服务器A提交请求后,服务器A偷偷地去服务器B上获取资源,并返回给客户端。客户端天真地以为数据是服务器A给他的。在这过程中,服务器A称为“反向代理服务器”,服务器B称为反向代理服务器的“后端服务器”。 

3.“正向代理”与“反向代理”的区别?

两者最直观的区别是在用户的角度。“正向代理”是用户使用的技术。用户首先是知道自己要访问的目标服务器是谁,但由于某种原因无法直接访问该目标服务器,因此选择使用正向代理服务器帮忙转发请求。

而“反向代理”是服务器使用的技术。用户向服务器发送请求后,服务器在用户不知情的情况下去其他服务器上获取资源并返回给用户。 

什么是反向代理服务器?

反向代理服务器用于存储静态数据和缓存数据,它处于Web服务器之前。当用户发起请求时,请求首先被反向代理服务器截获,若请求的是静态数据或缓存数据,则反向代理服务器直接将数据返回;若请求的是动态数据,且缓存中不存在,则反向代理服务器将请求转发给后端的Web服务器,在获取后端服务器的数据后再返回给用户。 

反向代理服务器有何作用?

反向代理服务器能够分担后端服务器的压力。在请求数很高的情况下,即使服务器使用了缓存,但仍然无法应对巨大的并发数,因此需要反向代理服务器的帮忙。反向代理服务器收到请求后,如果请求的是缓存数据或静态数据,则直接返回给用户,而无需再劳驾后端服务器了,从而缓解后端服务器的压力。 

如何使用反向代理缓存?

1.如何选择反向代理服务器?

反向代理服务器有多种选择,可以使用Nginx的反向代理模块,但它毕竟是Nginx的一个插件,功能不够全面。 
Squid是一个缓存服务器,除提供反向代理外还拥有其他功能,但过于重量级,历史也比较悠久,性能不咋地。 
Varnish是一款专门用于反向代理的服务器,相对于Squid较为轻量,由于使用内存缓存,因此性能较好,但也收到了内存的存储容量的限制。 
究竟哪一个反向代理服务器适合你,可以参考: 
varnish / squid / nginx cache 有什么不同? 
这里我们以Varnish为例。 

2.搭建Varnish服务器

  1. 下载安装Varnish 
    首先你需要去Varnish官网下载并安装Varnish,这里不做详细介绍了。

    扫描二维码关注公众号,回复: 11080218 查看本文章
  2. 配置Varnish的后端服务器的IP和端口号

<code class="language-shell hljs haskell has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-title" style="margin: 0px; padding: 0px; box-sizing: border-box;">backend</span> <span class="hljs-default" style="margin: 0px; padding: 0px; box-sizing: border-box;"><span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">default</span>{</span>
    .host = <span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"127.0.0.1"</span>;
    .port = <span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"80"</span>;
}</code><ul class="pre-numbering" style="margin: 0px; padding: 6px 0px 40px; box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">1</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">2</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">3</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">4</li></ul>
  1. 启动Varnish 
    在启动时,你需要指定如下参数: 
    • Varnish对外的端口
    • Varnish命令行操作的端口
    • 缓存空间的大小 
      PS:Varnish会采用一种类似于Mysql Innodb的存储引擎来存储缓存数据。 

3.自定义Varnish的缓存规则

Varnish采用VCL(Varnish Configuration Language)来让我们自定义缓存规则。

Varnish将反向代理的处理过程分为多个阶段,每个阶段都会触发对应的函数,我们可以在这些函数中配置具体的缓存处理策略。 
下面对常用规则的配置进行介绍:

1.vcl_revc函数

<code class="language-shell hljs perl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-sub" style="margin: 0px; padding: 0px; box-sizing: border-box;"><span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">sub</span> vcl_revc{</span>
    <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"GET"</span> && 
        req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"POST"</span> &&
        req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"PUT"</span> &&
        req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"HEAD"</span> &&
        req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"OPTIONS"</span> &&
        req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"DELETE"</span> &&
        req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"TRACE"</span>){
            <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">return</span>(<span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">pipe</span>);
    }

    <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"GET"</span> && 
        req.request!=<span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">"HEAD"</span>){
            <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">return</span>(pass);
    }


    <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(req.http.Authorization || req.http.Cookie){
            <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">return</span>(pass);
    }

    <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (lookup);
}</code><ul class="pre-numbering" style="margin: 0px; padding: 6px 0px 40px; box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">1</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">2</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">3</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">4</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">5</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">6</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">7</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">8</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">9</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">10</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">11</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">12</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">13</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">14</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">15</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">16</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">17</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">18</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">19</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">20</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">21</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">22</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">23</li></ul>
  • pass:表示不检查缓存,直接将请求转发给后端服务器,从而触发vcl_pass函数;
  • lookup:表示从缓存中查找,若命中缓存则触发vcl_hit函数,若未命中缓存则触发vcl_miss函数;
  • req.request:表示HTTP请求的类型
  • req.http.Cookie:表示请求中是否携带Cookie;
  • client.ip:用户的IP
  • req.url:用户请求的URL
  • req.http.[header-key]:http请求头中的某个属性
  • obj.status:后端服务器返回的HTTP响应状态码
  • obj.response:后端服务器返回的HTTP响应头中的状态信息(状态码后面的文字说明)
  • obj-cacheable:表示是否可以缓存后端服务器返回的数据。当后端服务器返回的响应头状态码为200、203、300、301、404、410,并且包含Expires或Cache-Control标记时,Varnish就会根据过期时间将数据缓存起来。

2.vcl_fetch函数

<code class="language-shell hljs perl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-sub" style="margin: 0px; padding: 0px; box-sizing: border-box;"><span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">sub</span> vcl_fetch{</span>
    <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(!obj.cacheable){
        <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">return</span>(pass);
    }

    <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(obj.http.set-Cookie){
        <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">return</span>(pass);
    }

    set obj.prefetch = -<span class="hljs-number" style="margin: 0px; padding: 0px; color: rgb(0, 102, 102); box-sizing: border-box;">30</span><span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">s</span>;
    <span class="hljs-keyword" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (deliver);
}</code><ul class="pre-numbering" style="margin: 0px; padding: 6px 0px 40px; box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">1</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">2</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">3</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">4</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">5</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">6</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">7</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">8</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">9</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">10</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">11</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">12</li></ul>
  • pass:代表将后端服务器发来的数据直接返回给浏览器,而不需要缓存
  • deliver:代表将后端服务器发来的数据写入缓存
  • if(!obj.cacheable){return(pass);}:表示当前内存不允许缓存的话直接返回给客户端;
  • if(obj.http.set-Cookie){return(pass);}:表示如果后端服务器返回的数据中设置了Cookie,那么Varnish也不需要缓存,直接将数据返回给客户端。 

4.清除Varnish中的缓存

Varnish提供了两种清除缓存的方式:

  • 通过Varnish命令行
<code class="language-shell hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    varnishadm -T localhost:<span class="hljs-number" style="margin: 0px; padding: 0px; color: rgb(0, 102, 102); box-sizing: border-box;">8011</span> purge<span class="hljs-preprocessor" style="margin: 0px; padding: 0px; color: rgb(68, 68, 68); box-sizing: border-box;">.url</span> /xxx<span class="hljs-preprocessor" style="margin: 0px; padding: 0px; color: rgb(68, 68, 68); box-sizing: border-box;">.html</span></code><ul class="pre-numbering" style="margin: 0px; padding: 6px 0px 40px; box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">1</li></ul>
  • 通过HTTP远程清除缓存 
    通过Varnish的HTTP服务端口发送如下请求:
<code class="language-shell hljs http has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-request" style="margin: 0px; padding: 0px; color: rgb(0, 0, 136); box-sizing: border-box;">PURGE <span class="hljs-string" style="margin: 0px; padding: 0px; color: rgb(0, 136, 0); box-sizing: border-box;">/xxx.html</span> HTTP/1.0</span>
<span class="hljs-attribute" style="margin: 0px; padding: 0px; box-sizing: border-box;">Host:Varnish的地址</span></code><ul class="pre-numbering" style="margin: 0px; padding: 6px 0px 40px; box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">1</li><li style="margin: 0px; padding: 0px 5px; box-sizing: border-box;">2</li></ul>

后端服务器能够使用HTTP方式删除Varnish上的指定的缓存。 

5.监控Varnish中的缓存命中率

使用varnishstat命令即可查看当前Varnish的运行情况,下面是Varnish运行参数的说明:

  • Client requests recived: 
    Varnish接收到的所有请求的个数。

  • Cache hits: 
    缓存命中的个数。

  • Cache misses: 
    缓存未命中的个数。

  • N expired objects: 
    过期缓存的个数。

  • N LRU moved objects: 
    被淘汰的缓存的个数。

  • Total header bytes: 
    当前缓存中所有数据的HTTP Header长度。

  • Total body bytes: 
    当前缓存中所有数据的HTTP body长度。

PS:Varnish还提供了可视化的监控界面。 

分配多大的缓存空间较为合适?

首先,我们需要通过压力测试来估算网站的最大并发数。 
然后观察反向代理服务器中缓存空间的使用率与命中率。 
如果缓存空间长时间处于满载,并且缓存的命中率很低,并且后端服务器的实际并发数接近最大并发数,那么我们就需要考虑增加缓存的存储空间。增加缓存空间后,一方面能提高缓存命中率,一方面能减轻后端服务器的压力。 

设置多长的缓存有效期较为合适?

若缓存有效期过长,虽然能够大大减轻后端服务器的压力,但数据的实时性将大大降低;若缓存有效期过短,那么虽然数据实时性大大提高,但后端服务器的压力也增加。

所以缓存有效期的确定需要根据实际的业务情况,如果你的网站对实时性要求很高,那么就需要设置较短的缓存有效期,如果对实时性要求不高,那么可以将缓存设置长一些,从而减轻后端服务器压力。

当然,如果你网站平实的并发数达不到服务器所能承受的最大并发数,那完全可以不用考虑缓存,用户每次请求都让后端服务器直接处理,一切都不用那么麻烦了。 

ESI简介

如果有些页面中大部分地方都不需要实时更新,而局部对实时性要求较高,那么可以使用ESI技术,对页面不同的地方设置不同的缓存有效期。 

将 后端服务器缓存 和 反向代理缓存一起使用

讲到这里,也许有的人要问,反向代理服务器实现了缓存的功能,那么后端服务器还需要缓存吗?答案是:当然需要! 
原因主要有如下两点:

  1. 反向代理服务器有时会漏掉一些原本需要缓存的数据,后端服务器的缓存能避免用户请求这部分数据时消耗太多时间;
  2. 反向代理服务器的数据缓存是在用户第一次请求时进行的,而后端服务器的数据缓存是在服务器启动时完成的。因此,如果使用了后端服务器的缓存,当用户请求尚未被反向代理服务器缓存的数据时,后端服务器可以直接将这部分缓存数据返回给用户,从而节约了用户等待时间,也减少了后端服务器的开销。 

反向代理服务器与后端服务器的网络环境搭建

由于反向代理服务器和后端服务器是通过TCP进行数据通信,因此为了降低他们之间的通信时延,需要将这两台服务器放在内部网络下,并通过交换机相连。

发布了11 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/John_ToStr/article/details/51166275