Varnish简介与实践

Varnish简介与实践

Varnish与web架构实践

一、前言

​ 我国的互联网处于飞速发展时期,网民队伍飞速扩大,举个栗子,家里的小学生跟爷爷一代都已经成为了使用智能手机获取互联网资源的新兴势力。庞大的网民群体必然要求主流或者想成为主流的互联网企业具备承载海量并发访问的能力,但是单纯增加原始内容服务器数量去应对海量并发并不是有性价比的解决方案,因此各类缓存技术应运而生且诞生了一个口号“缓存为王”。

二、http协议缓存原理

​ http协议缓存机制是指通过HTTP协议头里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段来控制文件缓存的机制。缓存会根据请求保存输出内容的副本,例如html页面,图片,文件,当下一个请求来到的时候:如果是相同的URL,缓存直接使用副本响应访问请求,而不是向源服务器再次发送请求。

常用首部:

  • Expires:指示响应内容过期的时间,格林威治时间GMT。

  • Cache-Control:用于更精细地控制 本地缓存的相关设置。

    • Varnish简介与实践

    • 比如Cache-Control:max-age=600表示文件在本地缓存,且有效时长是600秒(从发出请求算起)。在接下来600秒内,如果有请求这个资源,浏览器不会发出HTTP请求,而是直接使用本地缓存的文件(强缓存)。

    • 所以判断缓存是否过期步骤是:

      • 查看是否有cache-control 的max-age / s-maxage , s-maxage(share cache即public缓存最大保留时间)如果有,则用服务器时间date值 + max-age/s-maxage 的秒数计算出新的过期时间,将当前时间与过期时间进行比较,判断是否过期。 

      • 如果没有cache-control 的max-age / s-maxage,则用expires 作为过期时间比较。

  • Last-Modified:标识文件在服务器上的最后一次更新时间。收到请求时,如果文件缓存过期,浏览器通过If-Modified-Since字段带上这个时间,发送给服务器,由服务器比较时间戳来判断文件是否有修改。如果没有修改,服务器返回304告诉浏览器继续使用缓存(协商缓存);如果有修改,则返回200,同时返回最新的文件。

  • Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。为什么要使用Etag主要是为了解决以下问题:

    • 如果某些文件会被定期生成,有时内容并没有任何变化,但Last-Modified却改变了,导致缓存未命中。

    • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间。

    • Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

  • no-cache :并不是说限定的资源不能缓存,而是要求浏览器必须发出条件式请求进行缓存有效性验证,所以不能结合expires使用。

  • no-store :所限定的资源不允许缓存

三、Varnish简介

​ Varnish是一款高性能的轻量级开源web cache加速器,常见的缓存技术有Varnish与Squid,两者的关系类似于Nginx与Httpd。与Squid 相比,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选项指定。后端存储的类型包括:

    • file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);

    • malloc:使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象;

    • persistent(experimental):与file的功能相同,但可以持久存储数据(即重启varnish数据时不会被清除);仍处于测试期

    varnish无法追踪某缓存对象是否存入了缓存文件,从而也就无从得知磁盘上的缓存文件是否可用,因此,file存储方法在varnish停止或重启时会清除数据。

    选择使用合适的存储方式有助于提升系统性,从经验的角度来看,建议在内存空间足以存储所有的缓存对象时使用malloc的方法,反之,file存储将有着更好的性能的表现。然而,需要注意的是,varnishd实际上使用的空间比使用-s选项指定的缓存空间更大,一般说来,其需要为每个缓存对象多使用差不多1K左右的存储空间,这意味着,对于100万个缓存对象的场景来说,其使用的缓存空间将超出指定大小1G左右。另外,为了保存数据结构等,varnish自身也会占去不小的内存空间。

    为varnishd指定使用的缓存类型时,-s选项可接受的参数格式如下:   

    • malloc[,size]

    • file[,path[,size[,granularity]]]  #granularity用于设定缓存空间分配单位,默认单位是字节,所有其它的大小都会被圆整。

    • persistent,path,size {experimental}   

四、Varnish状态引擎(state engine)

VCL用于让管理员定义缓存策略,而定义好的策略将由varnish的management进程分析、转换成C代码、编译成二进制程序并连接至child进程。varnish内部有几个所谓的状态(state),在这些状态上可以附加通过VCL定义的策略以完成相应的缓存处理机制,因此VCL也经常被称作“域专用”语言或状态引擎,“域专用”指的是有些数据仅出现于特定的状态中。

  1. VCL状态引擎

    在VCL状态引擎中,状态之间具有相关性,但彼此间互相隔离,每个引擎使用return(x)来退出当前状态并指示varnish进入下一个状态。

    varnish开始处理一个请求时,首先需要分析HTTP请求本身,比如从首部获取请求方法、验正其是否为一个合法的HTT请求等。当这些基本分析结束后就需要做出第一个决策,即varnish是否从缓存中查找请求的资源。这个决定的实现则需要由VCL来完成,简单来说,要由vcl_recv方法来完成。如果管理员没有自定义vcl_recv函数,varnish将会执行默认的vcl_recv函数。然而,即便管理员自定义了vcl_recv,但如果没有为自定义的vcl_recv函数指定其终止操作(terminating),其仍将执行默认的vcl_recv函数。事实上,varnish官方强烈建议让varnish执行默认的vcl_recv以便处理自定义vcl_recv函数中的可能出现的漏洞。

  2. VCL语法

    VCL的设计参考了C和Perl语言,因此,对有着C或Perl编程经验者来说,其非常易于理解。其基本语法说明如下:

    • //、#或/* comment */用于注释

    • sub $name 定义函数

    • 不支持循环,有内置变量

    • 使用终止语句,没有返回值

    • 域专用

    • 操作符:=(赋值)、==(等值比较)、~(模式匹配)、!(取反)、&&(逻辑与)、||(逻辑或) 

    VCL的函数不接受参数并且没有返回值,因此,其并非真正意义上的函数,这也限定了VCL内部的数据传递只能隐藏在HTTP首部内部进行。VCL的return语句用于将控制权从VCL状态引擎返回给Varnish,而非默认函数,这就是为什么VCL只有终止语句而没有返回值的原因。同时,对于每个“域”来说,可以定义一个或多个终止语句,以告诉Varnish下一步采取何种操作,如查询缓存或不查询缓存等。

  3. VCL的内置函数

    VCL提供了几个函数来实现字符串的修改,添加bans,重启VCL状态引擎以及将控制权转回Varnish等。

    • regsub(str,regex,sub)|regsuball(str,regex,sub) :这两个用于基于正则表达式搜索指定的字符串并将其替换为指定的字符串;但regsuball()可以将str中能够被regex匹配到的字符串统统替换为sub,regsub()只替换一次;

    • ban(expression)|ban_url(regex): 禁用所有其URL能够由regex匹配的缓存对象;

    • purge:从缓存中挑选出某对象以及其相关变种一并删除,这可以通过HTTP协议的PURGE方法完成;

    • hash_data(str): 设定用于hash计算后匹配缓存项的字符串值;

    • return():当某VCL域运行结束时将控制权返回给Varnish,并指示Varnish如何进行后续的动作;其可以返回的指令包括:lookup、pass、pipe、hit_for_pass、fetch、deliver和hash等;但某特定域可能仅能返回某些特定的指令,而非前面列出的全部指令;

    • return(restart):重新运行整个VCL,即重新从vcl_recv开始进行处理;每一次重启都会增加req.restarts变量中的值,而max_restarts参数则用于限定最大重启次数。

  4. vcl_recv

    vcl_recv是在Varnish完成对请求报文的解码为基本数据结构后第一个要执行的子例程,它通常有四个主要用途:

    • 修改客户端数据以减少缓存对象差异性;比如删除URL中的www.等字符;

    • 基于客户端数据选用缓存策略;比如仅缓存特定的URL请求、不缓存POST请求等;

    • 为某web应用程序执行URL重写规则;

    • 挑选合适的后端Web服务器;

    可以使用下面的终止语句,即通过return()向Varnish返回的指示操作:

    • pass:绕过缓存,即不从缓存中查询内容或不将内容存储至缓存中;

    • pipe:不对客户端进行检查或做出任何操作,而是在客户端与后端服务器之间建立专用“管道”,并直接将数据在二者之间进行传送;此时,keep-alive连接中后续传送的数据也都将通过此管道进行直接传送,并不会出现在任何日志中;

    • lookup:在缓存中查找用户请求的对象,如果缓存中没有其请求的对象,后续操作很可能会将其请求的对象进行缓存;

    • error:(注意这是3.x版本的参数,4.x已经改为synth)由Varnish自己合成一个响应报文,一般是响应一个错误类信息、重定向类信息或负载均衡器返回的后端web服务器健康状态检查类信息;

    vcl_recv也可以通过精巧的策略完成一定意义上的安全功能,以将某些特定的攻击扼杀于摇篮中。同时,它也可以检查出一些拼写类的错误并将其进行修正等。 Varnish默认的vcl_recv专门设计用来实现安全的缓存策略,它主要完成两种功能:

    • 仅处理可以识别的HTTP方法,并且只缓存GET和HEAD方法;

    • 不缓存任何用户特有的数据;

    安全起见,一般在自定义的vcl_recv中不要使用return()终止语句,而是再由默认vcl_recv进行处理,并由其做出相应的处理决策。

  5. vcl_fetch

    如前面所述,相对于vcl_recv是根据客户端的请求作出缓存决策来说,vcl_fetch则是根据服务器端的响应作出缓存决策。在任何VCL状态引擎中返回的pass操作都将由vcl_fetch进行后续处理。vcl_fetch中有许多可用的内置变量,比如最常用的用于定义某对象缓存时长的beresp.ttl变量。默认的vcl_fetch放弃了缓存任何使用了Set-Cookie首部的响应。

    通过return()返回给varnish的操作指示有:

    • deliver:缓存此对象,并将其发送给客户端(经由vcl_deliver);

    • hit_for_pass:不缓存此对象,但可以导致后续对此对象的请求直接送达到vcl_pass进行处理;

    • restart:重启整个VCL,并增加重启计数;超出max_restarts限定的最大重启次数后将会返回错误信息;

    • error code [reason]:返回指定的错误代码给客户端并丢弃此请求;

五、修剪缓存对象

  1. 缓存内容修剪

    提高缓存命中率的最有效途径之一是增加缓存对象的生存时间(TTL),但是这也可能会带来副作用,比如缓存的内容在到达为其指定的有效期之间已经失效。因此,手动检验缓存对象的有效性或者刷新缓存很有可能成为管理员的日常工作之一,相应地,Varnish为完成这类的任务提供了三种途径:HTTP 修剪(HTTP purging)、禁用某类缓存对象(banning)和强制缓存未命令(forced cache misses)。

  2. 移除单个缓存对象

    purge用于清理缓存中的某特定对象及其变种(variants),因此,在有着明确要修剪的缓存对象时可以使用此种方式。HTTP协议的PURGE方法可以实现purge功能,不过,其仅能用于vcl_hit和vcl_miss中,它会释放内存工作并移除指定缓存对象的所有Vary:-变种,并等待下一个针对此内容的客户端请求到达时刷新此内容。另外,其一般要与return(restart)一起使用。下面是个在VCL中配置的示例。

  3. 强制缓存未命中

    在vcl_recv中使用return(pass)能够强制到上游服务器取得请求的内容,但这也会导致无法将其缓存。使用purge会移除旧的缓存对象,但如果上游服务器宕机而无法取得新版本的内容时,此内容将无法再响应给客户端。使用req.has_always_miss=ture,可以让Varnish在缓存中搜寻相应的内容但却总是回应“未命中”,于是vcl_miss将后续地负责启动vcl_fetch从上游服务器取得新内容,并以新内容缓存覆盖旧内容。此时,如果上游服务器宕机或未响应,旧的内容将保持原状,并能够继续服务于那些未使用req.has_always_miss=true的客户端,直到其过期失效或由其它方法移除。

  4. Banning

    • varnishadm:  适合让客户端重新缓存更新的内容,只拦截一次。   

      ban <field> <operator> <arg>                                       

    • 示例:假设JSP更新了,让客户端都重新从服务器获取一次内容,临时按需清理                       

      • ban req.url ~ ^/javascripts                       

      • ban  req.url ~ .js$                       

      • ban  req.url == / && req.http.host ~ “ilinux.io”#清空指定域缓存,慎用!              

    • 在配置文件中定义,使用ban()函数,示例:                 

      if (req.method == "BAN") {                        
          ban("req.http.host == " + req.http.host + " && req.url == " + req.url);  
          #相当于在命令行执行 ban req.http.host == host && req.url == /index.html 
                         
        return(synth(200, "Ban added"));                   
      }              

六、Varnish检测后端主机的健康状态

Varnish可以检测后端主机的健康状态,在判定后端主机失效时能自动将其从可用后端主机列表中移除,而一旦其重新变得可用还可以自动将其设定为可用。为了避免误判,Varnish在探测后端主机的健康状态发生转变时(比如某次探测时某后端主机突然成为不可用状态),通常需要连续执行几次探测均为新状态才将其标记为转换后的状态。

每个后端服务器当前探测的健康状态探测方法通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的Backend_health查看或varnishadm的debug.health查看。

.probe中的探测指令常用的有:

  • .url:探测后端主机健康状态时请求的URL,默认为“/”;

  • .request: 探测后端主机健康状态时所请求内容的详细格式,定义后,它会替换.url指定的探测方式;比如:    .request =        “GET /.healthtest.html HTTP/1.1”        “Host: www.magedu.com”        “Connection: close”;

  • .window:设定在判定后端主机健康状态时基于最近多少次的探测进行,默认是8;

  • .threshold:在.window中指定的次数中,至少有多少次是成功的才判定后端主机正健康运行;默认是3;

  • .initial:Varnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold;

  • .expected_response:期望后端主机响应的状态码,默认为200;

  • .interval:探测请求的发送周期,默认为5秒;

  • .timeout:每次探测请求的过期时长,默认为2秒;

如果Varnish在某时刻没有任何可用的后端主机,它将尝试使用缓存对象的“宽容副本”(graced copy),当然,此时VCL中的各种规则依然有效。因此,在VCL规则中判断req.backend.healthy变量显示某后端主机不可用时,可为此后端主机增大req.grace变量的值以设定适用的宽容期限长度。

七、 Varnish使用多台后端主机

Varnish中可以使用director指令将一个或多个近似的后端主机定义为一个逻辑组,并可以指定的调度方式(也叫挑选方法)来轮流将请求发送至这些主机上。不同的director可以使用同一个后端主机,而某director也可以使用“匿名”后端主机(在director中直接进行定义)。每个director都必须有其专用名,且在定义后必须在VCL中进行调用,VCL中任何可以指定后端主机的位置均可以按需将其替换为调用某已定义的director。

backend web1 {
    .host = "backweb1.magedu.com";
    .port = "80";
}

director webservers random {
  .retries = 5;
  {
    .backend = web1;
    .weight  = 2;
  }
  {
    .backend  = {
      .host = "backweb2.magedu.com";
      .port = "80";
    }
      .weight         = 3;
  }
}

如上示例中,web1为显式定义的后端主机,而webservers这个directors还包含了一个“匿名”后端主机(backweb2.magedu.com)。webservers从这两个后端主机中挑选一个主机的方法为random,即以随机方式挑选。

Varnish的director支持的挑选方法中比较简单的有round-robin和random两种。其中,round-robin类型没有任何参数,只需要为其指定各后端主机即可,挑选方式为“轮叫”,并在某后端主机故障时不再将其视作挑选对象;random方法随机从可用后端主机中进行挑选,每一个后端主机都需要一个.weight参数以指定其权重,同时还可以在director上下文中使用.retires参数来设定查找一个健康后端主机时的尝试次数。

Varnish 2.1.0后,random挑选方法又多了两种变化形式client和hash。client类型的director使用client.identity作为挑选因子,这意味着client.identity相同的请求都将被发送至同一个后端主机。client.identity默认为client.ip,但也可以在VCL中将其修改为所需要的标识符。类似地,hash类型的director使用hash数据作为挑选因子,这意味着对同一个URL的请求将被发往同一个后端主机,其常用于多级缓存的场景中。然而,无论是client还hash,当其倾向于使用后端主机不可用时将会重新挑选新的后端其机。 另外还有一种称作fallback的director,用于定义备用服务器。

八、varnish管理进阶

  1. 可调参数

    Varnish有许多参数,虽然大多数场景中这些参数的默认值都可以工作得很好,然而特定的工作场景中要想有着更好的性能的表现,则需要调整某些参数。可以在管理接口中使用param.show命令查看这些参数,而使用param.set则能修改这些参数的值。然而,在命令行接口中进行的修改不会保存至任何位置,因此,重启varnish后这些设定会消失。可以通过启动脚本多次使用-p选项设定参数,在varnishd启动时调用。然而,除非特别需要对其进行修改,保持这些参数为默认值可以有效降低管理复杂度。

  2. 共享日志

    共享内存日志(shared memory log)通常被简称为shm-log,它用于记录日志相关的数据,大小为80M。varnish以轮转(round-robin)的方式使用其存储空间。

  3. 线程模型

    varnish的child进程由多种不同的线程组成,分别用于完成不同的工作。例如:

    • cache-worker线程:每连接一个,用于处理请求;

    • cache-main线程:全局只有一个,用于启动cache;

    • ban lurker线程:一个,用于清理bans;

    • acceptor线程:一个,用于接收新的连接请求;

    • epoll/kqueue线程:数量可配置,默认为2,用于管理线程池;

    • expire线程:一个,用于移除老化的内容;

    • backend poll线程:每个后端服务器一个,用于检测后端服务器的健康状况;

    在配置varnish时,一般只需关注cache-worker线程,而且也只能配置其线程池的数量,而除此之外的其它均非可配置参数。线程池的数量也只能在流量较大的场景下才需要增加,一般不超过CPU的物理核心数

  4. 线程相关参数

    varnish为每个连接使用一个线程,因此,其worker线程的最大数决定了varnish的并发响应能力。下面是线程池相关的各参数及其配置:

    • thread_pool_add_delay      2 [milliseconds]

    • thread_pool_add_threshold  2 [requests]

    • thread_pool_fail_delay     200 [milliseconds]

    • thread_pool_max            500 [threads]

    • thread_pool_min            5 [threads]

    • thread_pool_purge_delay    1000 [milliseconds]

    • thread_pool_stack          65536 [bytes]

    • thread_pool_timeout        120 [seconds]

    • thread_pool_workspace      16384 [bytes]

    • thread_pools               2 [pools]

    • thread_stats_rate          10 [requests]

    其中最关键的当属thread_pool_max和thread_pool_min,它们分别用于定义每个线程池中的最大线程数和最少线程数(即允许的最大空闲线程数 )。因此,在某个时刻,至少有thread_pool_min乘thread_pools个worker线程在运行,但至多不能超出thread_pool_max乘thread_pools个。根据需要,这两个参数的数量可以进行调整,varnishstat命令的n_wrk_queued可以显示当前varnish的线程数量是否足够,如果队列中始终有不少的线程等待运行,则可以适当调大thread_pool_max参数的值。一般建议每个线程池上最多运行的worker线程数不要超过5000个。

九、Varnish的命令行工具

命令语法:varnishadm -t timeout -T address:port [command […]]

通过命令行的方式连接至varnishd进行管理操作的工具,指定要连接的varnish实例的方法有两种:

  • -n name —— 连接至名称为“name”的实例;

  • -T address:port —— 连接至指定套接字上的实例;

其运行模式有两种,当不在命令行中给出要执行的”command”时,其将进入交互式模式;否则,varnishadm将执行指定的”command”并退出。

例如,要查看本地启用的缓存,可使用如下命令进行。

varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 storage.list

十、配置实践

(一)网络拓补

Varnish简介与实践

(二)软件版本
  • 系统均为CentOS 7.4

  • keepalived.x86_64                         1.3.5-6.el7

  • nginx.x86_64                              1:1.12.2-2.el7       epel

  • wordpress-4.9.4-zh_CN

  • varnish.x86_64                            4.0.5-1.el7                  epel

  • nfs-utils.x86_64                          1:1.3.0-0.54.el7            

  • mariadb.x86_64                            1:5.5.56-2.el7

  • php-fpm.x86_64                            5.4.16-45.el7

  • php.x86_64                                 5.4.16-45.el7

(三)各server配置
  1. 调度器配置

    仅做单台调度器示例,192.168.7.121

    • keepalived

      global_defs {
                          notification_email {
                              root@localhost
                          }
                          notification_email_from keepalived@localhost
                          smtp_server 127.0.0.1
                          smtp_connect_timeout 30
                          router_id keepalivedR1
                          vrrp_mcast_group4 224.0.0.33
                      }
                      vrrp_script chk_nginx {
                      script "/etc/keepalived/chk_nginx.sh"
                      interval 1
                      weight -15
                      fall 2
                      rise 1
                      }
                     
      
                      vrrp_instance VI_1 {
                          state MASTER
                          interface ens39
                          virtual_router_id 3
                          priority 99
                          advert_int 1
                          authentication {
                              auth_type PASS
                              auth_pass 736w4ib2
                          }
                          virtual_ipaddress {
                              192.168.7.120/24 dev ens39
                          }
                         track_script {
                          chk_nginx
                          }
                          notify_master "/etc/keepalived/notify.sh master"
                          notify_backup "/etc/keepalived/notify.sh backup"
                          notify_fault "/etc/keepalived/notify.sh fault"
                      }
    • nginx

      server {
              listen 192.168.7.120:80;
              server_name wind.com;
              index index.php;
              location ~ \.(js|css|htm|html|gif|jpg|jpeg|png|bmp|txt|ico)$ {
                       proxy_set_header X-Real-IP $remote_addr;
                       proxy_pass http://192.168.7.120:8081;#静态资源代理到本机varnish监听端口
              }
      
              location ~ ^/$ {  #当仅输入主站ip时跳转到动态服务器的默认主页index.php
                      proxy_pass http://192.168.7.126:80;
                      proxy_set_header X-Real-IP $remote_addr;
              }
              location ~ \.php$ { #所有的动态资源交给动态资源服务器
                      proxy_pass http://192.168.7.126:80;
                      proxy_set_header X-Real-IP $remote_addr;
              }
      }
    • varnish监听本机8081端口

      import directors; #启用代理功能的时候需要声明
      
      probe check {  #健康检测机制定义
              .url = "/health.html"; #检测RealServer工作目录下的health.html
              .timeout = 2s;访问超时时间,超时即返回error
              .interval = 2s;#两次检测的间隔时间
              .window = 5; #检测5次
              .threshold = 2; #在window指定的总检测次数中至少有2次即认定状态变更
      }
      backend static {   #定义后端静态资源主机
          .host = "192.168.7.125";
          .port = "80";
          .probe = check; #调用check健康检测机制
      }
      
      backend image {  #定义后端图片服务器主机
          .host = "192.168.7.127";
          .port = "80";
          .probe = check;
      }
      acl purgers {  #限定可以进行缓存purge的客户端IP范围
          "127.0.0.0"/8; #本机
          "192.168.7.0"/24; #服务器本地网段
                      }
      sub vcl_recv { 
              if (req.method == "PURGE") { #如果客户端的请求报文首部中有PURGE method
                  if (!client.ip ~ purgers) {#如果客户端的IP不是前文purgers限定范围
                      return(synth(405,"Purging not allowed for " + client.ip));
                          }  #返回客户端IP不允许进行缓存修剪操作
                      return(purge);#如果客户端IP在purgers范围且首部包含PURGE method就执行缓存修剪操作
              }
              if (req.url ~ "(?i)\.(js|css|htm|html|txt)$") {
                      set req.backend_hint = static; #静态资源请求代理到static服务器
              }
              if (req.url ~ "(?i)\.(gif|jpg|jpeg|png|bmp|ico)$") {
                      set req.backend_hint = image; #图片资源请求代理到image服务器
              }
      }
      
      
      sub vcl_backend_response {  
              if (beresp.http.cache-control !~ "s-maxage") {
                      if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)$") {
                          unset beresp.http.Set-Cookie;  #取消静态资源cookie设置
                          set beresp.ttl = 3600s;  #修改静态资源缓存时长
                      }
                  }
      }
      
      sub vcl_deliver {
               if (obj.hits>0) {
                          set resp.http.X-Cache = "HIT via " + server.ip;
                      } else { #设置通过本地缓存提供资源时显示 HIT+服务器IP 即缓存命中
                          set resp.http.X-Cache = "MISS via " + server.ip;
                      }# 设置如果没有在本地缓存中找到资源即 MISS 没有命中缓存  
  2. 图片服务器设置

   image服务器,设置nfs共享文件夹存储图片资源,提供动态资源服务器所需数据库。

   /usr/share/nginx/wordpress/wp-content/uploads 192.168.7.*(rw,sync,root_squash)
   
   nginx:
   server {
           listen 80;
           root /usr/share/nginx/wordpress;
           }
   

   mariadb:

   安装软件包后运行初始化程序/usr/bin/mysql_secure_installation 设置root用户密码

   为wordpress创建数据库,并设置一个在动态资源服务器上远程访问的用户,为此用户设定对wordpress数据库具有所有权限。

  1. 动态资源服务器设置

    安装php、php-fpm、nginx

  • nginx设置

server {
        listen 80;
        root /usr/share/nginx/wordpress;
        index index.php; 
        location ~ \.php$ {
               fastcgi_pass 127.0.0.1:9000;#设置将php解析请求传送到php-fpm程序监听端口
               fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
               include fastcgi_params;
               }
        }
  • mariadb:

    在wordpress初始化设置时设定数据库访问192.168.7.127服务器所提供的mariadb

十一、缓存命中测试

(一)博客上传图片

​ <center> Varnish与web架构实践 </center>

一、前言

​ 我国的互联网处于飞速发展时期,网民队伍飞速扩大,举个栗子,家里的小学生跟爷爷一代都已经成为了使用智能手机获取互联网资源的新兴势力。庞大的网民群体必然要求主流或者想成为主流的互联网企业具备承载海量并发访问的能力,但是单纯增加原始内容服务器数量去应对海量并发并不是有性价比的解决方案,因此各类缓存技术应运而生且诞生了一个口号“缓存为王”。

二、http协议缓存原理

​ http协议缓存机制是指通过HTTP协议头里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段来控制文件缓存的机制。缓存会根据请求保存输出内容的副本,例如html页面,图片,文件,当下一个请求来到的时候:如果是相同的URL,缓存直接使用副本响应访问请求,而不是向源服务器再次发送请求。

常用首部:

  • Expires:指示响应内容过期的时间,格林威治时间GMT。

  • Cache-Control:用于更精细地控制 本地缓存的相关设置。

    • Varnish简介与实践

    • 比如Cache-Control:max-age=600表示文件在本地缓存,且有效时长是600秒(从发出请求算起)。在接下来600秒内,如果有请求这个资源,浏览器不会发出HTTP请求,而是直接使用本地缓存的文件(强缓存)。

    • 所以判断缓存是否过期步骤是:

      • 查看是否有cache-control 的max-age / s-maxage , s-maxage(share cache即public缓存最大保留时间)如果有,则用服务器时间date值 + max-age/s-maxage 的秒数计算出新的过期时间,将当前时间与过期时间进行比较,判断是否过期。 

      • 如果没有cache-control 的max-age / s-maxage,则用expires 作为过期时间比较。

  • Last-Modified:标识文件在服务器上的最后一次更新时间。收到请求时,如果文件缓存过期,浏览器通过If-Modified-Since字段带上这个时间,发送给服务器,由服务器比较时间戳来判断文件是否有修改。如果没有修改,服务器返回304告诉浏览器继续使用缓存(协商缓存);如果有修改,则返回200,同时返回最新的文件。

  • Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。为什么要使用Etag主要是为了解决以下问题:

    • 如果某些文件会被定期生成,有时内容并没有任何变化,但Last-Modified却改变了,导致缓存未命中。

    • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间。

    • Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

  • no-cache :并不是说限定的资源不能缓存,而是要求浏览器必须发出条件式请求进行缓存有效性验证,所以不能结合expires使用。

  • no-store :所限定的资源不允许缓存

三、Varnish简介

​ Varnish是一款高性能的轻量级开源web cache加速器,常见的缓存技术有Varnish与Squid,两者的关系类似于Nginx与Httpd。与Squid 相比,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选项指定。后端存储的类型包括:

    • file:使用特定的文件存储全部的缓存数据,并通过操作系统的mmap()系统调用将整个缓存文件映射至内存区域(如果条件允许);

    • malloc:使用malloc()库调用在varnish启动时向操作系统申请指定大小的内存空间以存储缓存对象;

    • persistent(experimental):与file的功能相同,但可以持久存储数据(即重启varnish数据时不会被清除);仍处于测试期

    varnish无法追踪某缓存对象是否存入了缓存文件,从而也就无从得知磁盘上的缓存文件是否可用,因此,file存储方法在varnish停止或重启时会清除数据。

    选择使用合适的存储方式有助于提升系统性,从经验的角度来看,建议在内存空间足以存储所有的缓存对象时使用malloc的方法,反之,file存储将有着更好的性能的表现。然而,需要注意的是,varnishd实际上使用的空间比使用-s选项指定的缓存空间更大,一般说来,其需要为每个缓存对象多使用差不多1K左右的存储空间,这意味着,对于100万个缓存对象的场景来说,其使用的缓存空间将超出指定大小1G左右。另外,为了保存数据结构等,varnish自身也会占去不小的内存空间。

    为varnishd指定使用的缓存类型时,-s选项可接受的参数格式如下:   

    • malloc[,size]

    • file[,path[,size[,granularity]]]  #granularity用于设定缓存空间分配单位,默认单位是字节,所有其它的大小都会被圆整。

    • persistent,path,size {experimental}   

四、Varnish状态引擎(state engine)

VCL用于让管理员定义缓存策略,而定义好的策略将由varnish的management进程分析、转换成C代码、编译成二进制程序并连接至child进程。varnish内部有几个所谓的状态(state),在这些状态上可以附加通过VCL定义的策略以完成相应的缓存处理机制,因此VCL也经常被称作“域专用”语言或状态引擎,“域专用”指的是有些数据仅出现于特定的状态中。

  1. VCL状态引擎

    在VCL状态引擎中,状态之间具有相关性,但彼此间互相隔离,每个引擎使用return(x)来退出当前状态并指示varnish进入下一个状态。

    varnish开始处理一个请求时,首先需要分析HTTP请求本身,比如从首部获取请求方法、验正其是否为一个合法的HTT请求等。当这些基本分析结束后就需要做出第一个决策,即varnish是否从缓存中查找请求的资源。这个决定的实现则需要由VCL来完成,简单来说,要由vcl_recv方法来完成。如果管理员没有自定义vcl_recv函数,varnish将会执行默认的vcl_recv函数。然而,即便管理员自定义了vcl_recv,但如果没有为自定义的vcl_recv函数指定其终止操作(terminating),其仍将执行默认的vcl_recv函数。事实上,varnish官方强烈建议让varnish执行默认的vcl_recv以便处理自定义vcl_recv函数中的可能出现的漏洞。

  2. VCL语法

    VCL的设计参考了C和Perl语言,因此,对有着C或Perl编程经验者来说,其非常易于理解。其基本语法说明如下:

    • //、#或/* comment */用于注释

    • sub $name 定义函数

    • 不支持循环,有内置变量

    • 使用终止语句,没有返回值

    • 域专用

    • 操作符:=(赋值)、==(等值比较)、~(模式匹配)、!(取反)、&&(逻辑与)、||(逻辑或) 

    VCL的函数不接受参数并且没有返回值,因此,其并非真正意义上的函数,这也限定了VCL内部的数据传递只能隐藏在HTTP首部内部进行。VCL的return语句用于将控制权从VCL状态引擎返回给Varnish,而非默认函数,这就是为什么VCL只有终止语句而没有返回值的原因。同时,对于每个“域”来说,可以定义一个或多个终止语句,以告诉Varnish下一步采取何种操作,如查询缓存或不查询缓存等。

  3. VCL的内置函数

    VCL提供了几个函数来实现字符串的修改,添加bans,重启VCL状态引擎以及将控制权转回Varnish等。

    • regsub(str,regex,sub)|regsuball(str,regex,sub) :这两个用于基于正则表达式搜索指定的字符串并将其替换为指定的字符串;但regsuball()可以将str中能够被regex匹配到的字符串统统替换为sub,regsub()只替换一次;

    • ban(expression)|ban_url(regex): 禁用所有其URL能够由regex匹配的缓存对象;

    • purge:从缓存中挑选出某对象以及其相关变种一并删除,这可以通过HTTP协议的PURGE方法完成;

    • hash_data(str): 设定用于hash计算后匹配缓存项的字符串值;

    • return():当某VCL域运行结束时将控制权返回给Varnish,并指示Varnish如何进行后续的动作;其可以返回的指令包括:lookup、pass、pipe、hit_for_pass、fetch、deliver和hash等;但某特定域可能仅能返回某些特定的指令,而非前面列出的全部指令;

    • return(restart):重新运行整个VCL,即重新从vcl_recv开始进行处理;每一次重启都会增加req.restarts变量中的值,而max_restarts参数则用于限定最大重启次数。

  4. vcl_recv

    vcl_recv是在Varnish完成对请求报文的解码为基本数据结构后第一个要执行的子例程,它通常有四个主要用途:

    • 修改客户端数据以减少缓存对象差异性;比如删除URL中的www.等字符;

    • 基于客户端数据选用缓存策略;比如仅缓存特定的URL请求、不缓存POST请求等;

    • 为某web应用程序执行URL重写规则;

    • 挑选合适的后端Web服务器;

    可以使用下面的终止语句,即通过return()向Varnish返回的指示操作:

    • pass:绕过缓存,即不从缓存中查询内容或不将内容存储至缓存中;

    • pipe:不对客户端进行检查或做出任何操作,而是在客户端与后端服务器之间建立专用“管道”,并直接将数据在二者之间进行传送;此时,keep-alive连接中后续传送的数据也都将通过此管道进行直接传送,并不会出现在任何日志中;

    • lookup:在缓存中查找用户请求的对象,如果缓存中没有其请求的对象,后续操作很可能会将其请求的对象进行缓存;

    • error:(注意这是3.x版本的参数,4.x已经改为synth)由Varnish自己合成一个响应报文,一般是响应一个错误类信息、重定向类信息或负载均衡器返回的后端web服务器健康状态检查类信息;

    vcl_recv也可以通过精巧的策略完成一定意义上的安全功能,以将某些特定的攻击扼杀于摇篮中。同时,它也可以检查出一些拼写类的错误并将其进行修正等。 Varnish默认的vcl_recv专门设计用来实现安全的缓存策略,它主要完成两种功能:

    • 仅处理可以识别的HTTP方法,并且只缓存GET和HEAD方法;

    • 不缓存任何用户特有的数据;

    安全起见,一般在自定义的vcl_recv中不要使用return()终止语句,而是再由默认vcl_recv进行处理,并由其做出相应的处理决策。

  5. vcl_fetch

    如前面所述,相对于vcl_recv是根据客户端的请求作出缓存决策来说,vcl_fetch则是根据服务器端的响应作出缓存决策。在任何VCL状态引擎中返回的pass操作都将由vcl_fetch进行后续处理。vcl_fetch中有许多可用的内置变量,比如最常用的用于定义某对象缓存时长的beresp.ttl变量。默认的vcl_fetch放弃了缓存任何使用了Set-Cookie首部的响应。

    通过return()返回给varnish的操作指示有:

    • deliver:缓存此对象,并将其发送给客户端(经由vcl_deliver);

    • hit_for_pass:不缓存此对象,但可以导致后续对此对象的请求直接送达到vcl_pass进行处理;

    • restart:重启整个VCL,并增加重启计数;超出max_restarts限定的最大重启次数后将会返回错误信息;

    • error code [reason]:返回指定的错误代码给客户端并丢弃此请求;

五、修剪缓存对象

  1. 缓存内容修剪

    提高缓存命中率的最有效途径之一是增加缓存对象的生存时间(TTL),但是这也可能会带来副作用,比如缓存的内容在到达为其指定的有效期之间已经失效。因此,手动检验缓存对象的有效性或者刷新缓存很有可能成为管理员的日常工作之一,相应地,Varnish为完成这类的任务提供了三种途径:HTTP 修剪(HTTP purging)、禁用某类缓存对象(banning)和强制缓存未命令(forced cache misses)。

  2. 移除单个缓存对象

    purge用于清理缓存中的某特定对象及其变种(variants),因此,在有着明确要修剪的缓存对象时可以使用此种方式。HTTP协议的PURGE方法可以实现purge功能,不过,其仅能用于vcl_hit和vcl_miss中,它会释放内存工作并移除指定缓存对象的所有Vary:-变种,并等待下一个针对此内容的客户端请求到达时刷新此内容。另外,其一般要与return(restart)一起使用。下面是个在VCL中配置的示例。

  3. 强制缓存未命中

    在vcl_recv中使用return(pass)能够强制到上游服务器取得请求的内容,但这也会导致无法将其缓存。使用purge会移除旧的缓存对象,但如果上游服务器宕机而无法取得新版本的内容时,此内容将无法再响应给客户端。使用req.has_always_miss=ture,可以让Varnish在缓存中搜寻相应的内容但却总是回应“未命中”,于是vcl_miss将后续地负责启动vcl_fetch从上游服务器取得新内容,并以新内容缓存覆盖旧内容。此时,如果上游服务器宕机或未响应,旧的内容将保持原状,并能够继续服务于那些未使用req.has_always_miss=true的客户端,直到其过期失效或由其它方法移除。

  4. Banning

    • varnishadm:  适合让客户端重新缓存更新的内容,只拦截一次。   

      ban <field> <operator> <arg>                                       

    • 示例:假设JSP更新了,让客户端都重新从服务器获取一次内容,临时按需清理                       

      • ban req.url ~ ^/javascripts                       

      • ban  req.url ~ .js$                       

      • ban  req.url == / && req.http.host ~ “ilinux.io”#清空指定域缓存,慎用!              

    • 在配置文件中定义,使用ban()函数,示例:                 

      if (req.method == "BAN") {                        
          ban("req.http.host == " + req.http.host + " && req.url == " + req.url);  
          #相当于在命令行执行 ban req.http.host == host && req.url == /index.html 
                         
        return(synth(200, "Ban added"));                   
      }              

六、Varnish检测后端主机的健康状态

Varnish可以检测后端主机的健康状态,在判定后端主机失效时能自动将其从可用后端主机列表中移除,而一旦其重新变得可用还可以自动将其设定为可用。为了避免误判,Varnish在探测后端主机的健康状态发生转变时(比如某次探测时某后端主机突然成为不可用状态),通常需要连续执行几次探测均为新状态才将其标记为转换后的状态。

每个后端服务器当前探测的健康状态探测方法通过.probe进行设定,其结果可由req.backend.healthy变量获取,也可通过varnishlog中的Backend_health查看或varnishadm的debug.health查看。

.probe中的探测指令常用的有:

  • .url:探测后端主机健康状态时请求的URL,默认为“/”;

  • .request: 探测后端主机健康状态时所请求内容的详细格式,定义后,它会替换.url指定的探测方式;比如:    .request =        “GET /.healthtest.html HTTP/1.1”        “Host: www.magedu.com”        “Connection: close”;

  • .window:设定在判定后端主机健康状态时基于最近多少次的探测进行,默认是8;

  • .threshold:在.window中指定的次数中,至少有多少次是成功的才判定后端主机正健康运行;默认是3;

  • .initial:Varnish启动时对后端主机至少需要多少次的成功探测,默认同.threshold;

  • .expected_response:期望后端主机响应的状态码,默认为200;

  • .interval:探测请求的发送周期,默认为5秒;

  • .timeout:每次探测请求的过期时长,默认为2秒;

如果Varnish在某时刻没有任何可用的后端主机,它将尝试使用缓存对象的“宽容副本”(graced copy),当然,此时VCL中的各种规则依然有效。因此,在VCL规则中判断req.backend.healthy变量显示某后端主机不可用时,可为此后端主机增大req.grace变量的值以设定适用的宽容期限长度。

七、 Varnish使用多台后端主机

Varnish中可以使用director指令将一个或多个近似的后端主机定义为一个逻辑组,并可以指定的调度方式(也叫挑选方法)来轮流将请求发送至这些主机上。不同的director可以使用同一个后端主机,而某director也可以使用“匿名”后端主机(在director中直接进行定义)。每个director都必须有其专用名,且在定义后必须在VCL中进行调用,VCL中任何可以指定后端主机的位置均可以按需将其替换为调用某已定义的director。

backend web1 {
    .host = "backweb1.magedu.com";
    .port = "80";
}

director webservers random {
  .retries = 5;
  {
    .backend = web1;
    .weight  = 2;
  }
  {
    .backend  = {
      .host = "backweb2.magedu.com";
      .port = "80";
    }
      .weight         = 3;
  }
}

如上示例中,web1为显式定义的后端主机,而webservers这个directors还包含了一个“匿名”后端主机(backweb2.magedu.com)。webservers从这两个后端主机中挑选一个主机的方法为random,即以随机方式挑选。

Varnish的director支持的挑选方法中比较简单的有round-robin和random两种。其中,round-robin类型没有任何参数,只需要为其指定各后端主机即可,挑选方式为“轮叫”,并在某后端主机故障时不再将其视作挑选对象;random方法随机从可用后端主机中进行挑选,每一个后端主机都需要一个.weight参数以指定其权重,同时还可以在director上下文中使用.retires参数来设定查找一个健康后端主机时的尝试次数。

Varnish 2.1.0后,random挑选方法又多了两种变化形式client和hash。client类型的director使用client.identity作为挑选因子,这意味着client.identity相同的请求都将被发送至同一个后端主机。client.identity默认为client.ip,但也可以在VCL中将其修改为所需要的标识符。类似地,hash类型的director使用hash数据作为挑选因子,这意味着对同一个URL的请求将被发往同一个后端主机,其常用于多级缓存的场景中。然而,无论是client还hash,当其倾向于使用后端主机不可用时将会重新挑选新的后端其机。 另外还有一种称作fallback的director,用于定义备用服务器。

八、varnish管理进阶

  1. 可调参数

    Varnish有许多参数,虽然大多数场景中这些参数的默认值都可以工作得很好,然而特定的工作场景中要想有着更好的性能的表现,则需要调整某些参数。可以在管理接口中使用param.show命令查看这些参数,而使用param.set则能修改这些参数的值。然而,在命令行接口中进行的修改不会保存至任何位置,因此,重启varnish后这些设定会消失。可以通过启动脚本多次使用-p选项设定参数,在varnishd启动时调用。然而,除非特别需要对其进行修改,保持这些参数为默认值可以有效降低管理复杂度。

  2. 共享日志

    共享内存日志(shared memory log)通常被简称为shm-log,它用于记录日志相关的数据,大小为80M。varnish以轮转(round-robin)的方式使用其存储空间。

  3. 线程模型

    varnish的child进程由多种不同的线程组成,分别用于完成不同的工作。例如:

    • cache-worker线程:每连接一个,用于处理请求;

    • cache-main线程:全局只有一个,用于启动cache;

    • ban lurker线程:一个,用于清理bans;

    • acceptor线程:一个,用于接收新的连接请求;

    • epoll/kqueue线程:数量可配置,默认为2,用于管理线程池;

    • expire线程:一个,用于移除老化的内容;

    • backend poll线程:每个后端服务器一个,用于检测后端服务器的健康状况;

    在配置varnish时,一般只需关注cache-worker线程,而且也只能配置其线程池的数量,而除此之外的其它均非可配置参数。线程池的数量也只能在流量较大的场景下才需要增加,一般不超过CPU的物理核心数

  4. 线程相关参数

    varnish为每个连接使用一个线程,因此,其worker线程的最大数决定了varnish的并发响应能力。下面是线程池相关的各参数及其配置:

    • thread_pool_add_delay      2 [milliseconds]

    • thread_pool_add_threshold  2 [requests]

    • thread_pool_fail_delay     200 [milliseconds]

    • thread_pool_max            500 [threads]

    • thread_pool_min            5 [threads]

    • thread_pool_purge_delay    1000 [milliseconds]

    • thread_pool_stack          65536 [bytes]

    • thread_pool_timeout        120 [seconds]

    • thread_pool_workspace      16384 [bytes]

    • thread_pools               2 [pools]

    • thread_stats_rate          10 [requests]

    其中最关键的当属thread_pool_max和thread_pool_min,它们分别用于定义每个线程池中的最大线程数和最少线程数(即允许的最大空闲线程数 )。因此,在某个时刻,至少有thread_pool_min乘thread_pools个worker线程在运行,但至多不能超出thread_pool_max乘thread_pools个。根据需要,这两个参数的数量可以进行调整,varnishstat命令的n_wrk_queued可以显示当前varnish的线程数量是否足够,如果队列中始终有不少的线程等待运行,则可以适当调大thread_pool_max参数的值。一般建议每个线程池上最多运行的worker线程数不要超过5000个。

九、Varnish的命令行工具

命令语法:varnishadm -t timeout -T address:port [command […]]

通过命令行的方式连接至varnishd进行管理操作的工具,指定要连接的varnish实例的方法有两种:

  • -n name —— 连接至名称为“name”的实例;

  • -T address:port —— 连接至指定套接字上的实例;

其运行模式有两种,当不在命令行中给出要执行的”command”时,其将进入交互式模式;否则,varnishadm将执行指定的”command”并退出。

例如,要查看本地启用的缓存,可使用如下命令进行。

varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 storage.list

十、配置实践

(一)网络拓补

Varnish简介与实践

(二)软件版本
  • 系统均为CentOS 7.4

  • keepalived.x86_64                         1.3.5-6.el7

  • nginx.x86_64                              1:1.12.2-2.el7       epel

  • wordpress-4.9.4-zh_CN

  • varnish.x86_64                            4.0.5-1.el7                  epel

  • nfs-utils.x86_64                          1:1.3.0-0.54.el7            

  • mariadb.x86_64                            1:5.5.56-2.el7

  • php-fpm.x86_64                            5.4.16-45.el7

  • php.x86_64                                 5.4.16-45.el7

(三)各server配置
  1. 调度器配置

    仅做单台调度器示例,192.168.7.121

    • keepalived

      global_defs {
                          notification_email {
                              root@localhost
                          }
                          notification_email_from keepalived@localhost
                          smtp_server 127.0.0.1
                          smtp_connect_timeout 30
                          router_id keepalivedR1
                          vrrp_mcast_group4 224.0.0.33
                      }
                      vrrp_script chk_nginx {
                      script "/etc/keepalived/chk_nginx.sh"
                      interval 1
                      weight -15
                      fall 2
                      rise 1
                      }
                     
      
                      vrrp_instance VI_1 {
                          state MASTER
                          interface ens39
                          virtual_router_id 3
                          priority 99
                          advert_int 1
                          authentication {
                              auth_type PASS
                              auth_pass 736w4ib2
                          }
                          virtual_ipaddress {
                              192.168.7.120/24 dev ens39
                          }
                         track_script {
                          chk_nginx
                          }
                          notify_master "/etc/keepalived/notify.sh master"
                          notify_backup "/etc/keepalived/notify.sh backup"
                          notify_fault "/etc/keepalived/notify.sh fault"
                      }
    • nginx

      server {
              listen 192.168.7.120:80;
              server_name wind.com;
              index index.php;
              location ~ \.(js|css|htm|html|gif|jpg|jpeg|png|bmp|txt|ico)$ {
                       proxy_set_header X-Real-IP $remote_addr;
                       proxy_pass http://192.168.7.120:8081;#静态资源代理到本机varnish监听端口
              }
      
              location ~ ^/$ {  #当仅输入主站ip时跳转到动态服务器的默认主页index.php
                      proxy_pass http://192.168.7.126:80;
                      proxy_set_header X-Real-IP $remote_addr;
              }
              location ~ \.php$ { #所有的动态资源交给动态资源服务器
                      proxy_pass http://192.168.7.126:80;
                      proxy_set_header X-Real-IP $remote_addr;
              }
      }
    • varnish监听本机8081端口

      import directors; #启用代理功能的时候需要声明
      
      probe check {  #健康检测机制定义
              .url = "/health.html"; #检测RealServer工作目录下的health.html
              .timeout = 2s;访问超时时间,超时即返回error
              .interval = 2s;#两次检测的间隔时间
              .window = 5; #检测5次
              .threshold = 2; #在window指定的总检测次数中至少有2次即认定状态变更
      }
      backend static {   #定义后端静态资源主机
          .host = "192.168.7.125";
          .port = "80";
          .probe = check; #调用check健康检测机制
      }
      
      backend image {  #定义后端图片服务器主机
          .host = "192.168.7.127";
          .port = "80";
          .probe = check;
      }
      acl purgers {  #限定可以进行缓存purge的客户端IP范围
          "127.0.0.0"/8; #本机
          "192.168.7.0"/24; #服务器本地网段
                      }
      sub vcl_recv { 
              if (req.method == "PURGE") { #如果客户端的请求报文首部中有PURGE method
                  if (!client.ip ~ purgers) {#如果客户端的IP不是前文purgers限定范围
                      return(synth(405,"Purging not allowed for " + client.ip));
                          }  #返回客户端IP不允许进行缓存修剪操作
                      return(purge);#如果客户端IP在purgers范围且首部包含PURGE method就执行缓存修剪操作
              }
              if (req.url ~ "(?i)\.(js|css|htm|html|txt)$") {
                      set req.backend_hint = static; #静态资源请求代理到static服务器
              }
              if (req.url ~ "(?i)\.(gif|jpg|jpeg|png|bmp|ico)$") {
                      set req.backend_hint = image; #图片资源请求代理到image服务器
              }
      }
      
      
      sub vcl_backend_response {  
              if (beresp.http.cache-control !~ "s-maxage") {
                      if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|css|js)$") {
                          unset beresp.http.Set-Cookie;  #取消静态资源cookie设置
                          set beresp.ttl = 3600s;  #修改静态资源缓存时长
                      }
                  }
      }
      
      sub vcl_deliver {
               if (obj.hits>0) {
                          set resp.http.X-Cache = "HIT via " + server.ip;
                      } else { #设置通过本地缓存提供资源时显示 HIT+服务器IP 即缓存命中
                          set resp.http.X-Cache = "MISS via " + server.ip;
                      }# 设置如果没有在本地缓存中找到资源即 MISS 没有命中缓存  
  2. 图片服务器设置

    image服务器,设置nfs共享文件夹存储图片资源,提供动态资源服务器所需数据库。

   /usr/share/nginx/wordpress/wp-content/uploads 192.168.7.*(rw,sync,root_squash)
   
   nginx:
   server {
           listen 80;
           root /usr/share/nginx/wordpress;
           }
   

   mariadb:

   安装软件包后运行初始化程序/usr/bin/mysql_secure_installation 设置root用户密码

   为wordpress创建数据库,并设置一个在动态资源服务器上远程访问的用户,为此用户设定对wordpress数据库具有所有权限。

  1. 动态资源服务器设置

    安装php、php-fpm、nginx

  • nginx设置

server {
        listen 80;
        root /usr/share/nginx/wordpress;
        index index.php; 
        location ~ \.php$ {
               fastcgi_pass 127.0.0.1:9000;#设置将php解析请求传送到php-fpm程序监听端口
               fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
               include fastcgi_params;
               }
        }
  • mariadb:

    在wordpress初始化设置时设定数据库访问192.168.7.127服务器所提供的mariadb

十一、缓存命中测试

博客上传图片

(http://192.168.7.120/wp-content/uploads/2018/07/cluster.jpg)

在地址栏贴入图片url测试可以看到响应报文头部第一次是miss没有命中;刷新后就变成了hit:

   X-Cache: HIT via 192.168.7.120

本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/103828

(0)
上一篇 2018-07-23 21:02
下一篇 2018-07-24 14:29

相关推荐

  • 带你认识Linux中的通配符

    所谓的通配是指:显示以指定条件的文件,英文名为glob。通配符是一种特殊语句,用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符它使得文件管理更加快速,便捷,大大提升了工作效率。

    2018-04-01
  • MySQL之事务及并发控制

    事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务

    2018-06-12
  • boot

    把boot目录都删除了怎么办 文件的 关键 缺少 唯独缺少 grub.conf     ;创建这个文件 以上是boot下文件都删除了的补救法 下面是第二种方法 光盘启动,救援模式   ,用安装包的方式 不能安装在当前的跟下 grub-install安装grub相关信息 写 以上是boot是独立分区

    2018-05-13
  • 正则表达式

    正则表达式REGEXP 有一类特殊符号表是控制伙同裴的功能程序支持:grep,seb,。。。基本正则表达式:BRE扩展正则表达式;ere采用不同算法经检查处理正则表达式的软件模块PCRE元字符:字符匹配,匹配次数,位置锚定,分组 man7详细文档 字符匹配.表示任意的单一字符 放在[]里表示.本身不需要转义\.转义(回归字符本身的含义)表示.本身[]表示字符…

    Linux笔记 2018-04-08