Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1250302
  • 博文数量: 220
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1769
  • 用 户 组: 普通用户
  • 注册时间: 2015-03-13 16:19
个人简介

努力, 努力, 再努力

文章分类

全部博文(220)

文章存档

2018年(8)

2017年(46)

2016年(75)

2015年(92)

我的朋友

分类: 架构设计与优化

2015-06-15 08:55:36

4章动态内容缓存

 

本章结论:一定要评估局部动态数据的影响力,如果你将一个动态网页中占主要开销有数据计算置于无缓存状态,那么缓存对你来说已经失去了意义,你可以考虑选择其他缓存方式或者页面组织结构,比如使用数据层缓存。

=========================================================

目录

 

    4.1 重复开销

              执行一次动态网页需要花费的时间, 动态网面的名称: place_posts.php


吞吐率只有35.24reqs/s, 而响应时间为28.378ms, 吞吐率很低, 响应时间也较长, 我们可以通过数据访问的DEBUG日志来分析吞吐率如此之低的原因:


请看用粗体标记出来的部分,包括一次数据库连接操作和两次比较复杂的SQL查询, 3个操作均在7ms以上.

7ms意味着什么? 一个比特通过光纤从北京到西安,理论上只需要5ms. WEB服务器来说一个151字节的静态页面请求响应大概是0.5ms左右, 所以说,7ms对于web服务器的处理时间来说是太长了.

动态网页的多次请求可以通过缓存来解决,避免重复的动态网页计算

    4.2 缓存与速度

              缓存的目的就是把需要花费昂贵开销的计算结果保存起来,在以后需要

的时候直接取出,而避免重复计算,一切缓存的本质都是如此。

缓冲:buffer  原意出自物理学,那就是减缓冲击力,其目的在于改善各部件之间由于速度不同而引发的问题。

              磁盘缓冲区起到了将快速设备(内存)和慢速设备(磁盘)平滑衔接作用

缓冲与缓存有一些相似之处,比如它们都需要一块存储区,而且它们的本质都与速度不一致有关。缓存更加注重的是策略,也就是说缓存命中率,如果每次都能在缓存中找到需要的数据,那是最理想的结果。

由于缓存的管理逻辑增加了新的开销,所以凡是使用缓存,都一定要意识到命中率的重要性。

    4.3 页面缓存

对于动态网页来说,缓存的内容实际上就是动态网页输出的HTML,我们称为页面缓存(page Cache)

              名称术语: Smarty模板,Cakephp, Django, ZendMVC框架

                                   这些框架有一点是相同的,就是将视图与控制器进行分离

           Smarty缓存

       这个小节通过压力测试展示了使用Smarty缓存与不使用Smarty缓存在吞吐率上的差异. 在应用Smartty缓存后,吞吐率增加到原来的3倍以上,并用使用Strace工具分析了使用缓存后节省了哪部分时间

       案例分析参见附录-案例分析

           缓存持久化与查找

              缓存目录下的缓存文件:

              Cache/12882^%%E6^E6C^E6C210ED%%place_posts.htm

              一个动态网页根据URL参数的不同,会展现出多种不同的结果,而每一种结果都必须生成对应的缓存文件。缓存文件中包含一个数字,12882,我们在请求这个动态网页的时候,URL参数为“marker_id=12882”.

              Smarty根据不同参数生成了不同的缓存文件。

              一旦cache目录下的文件数量达到数以万计,CPU花在遍历目录上的时间便非同寻常,如果缓存文件的读写比较频繁的话,可能CPU使用率很容易达到100%

              幸运的是,我们可以通过缓存目录分级来解决这一问题Smarty支持分级

              $this->smarty-use_sub_dir = true;   这个选项默认是关闭的。

              Cache/12882/^%%E6^E6C^E6C210ED%%place_posts.htm

              这样,可以将每个目录下的子目录和文件数量控制在少量范围内,同时适当增加目录切换次数,但这部分开销微不足道

           过期检查

动态内容的目的在于提供变化的内容,所以它的缓存不可能长期有效,否则就失去了动态内容的意义,所以动态内容的缓存机制必须能够判断缓存何时过期,以及何时需要生成新的缓存

过期检查的两种方法:

每次检查时,根据缓存的创建时间,缓存有效期设置的长度,以及当前时间,来判断是否过期,也就是说,如果当前时间距离缓存创建时间的间隔超过有效期长度的话,认为缓存过期,这是一种相对比较。

每次检查时,根据缓存的过期时间和当前时间来判断是否过期,这个方法比较简单,直接检查当前时间是否超过缓存过期时间即可,这是一种绝对比较。

两种方法的区别: 如果应用第二种方法, 那么在缓存过期前,如果你修改了程序中缓存有效期的长度,是不会影响上一次缓存的过期时间. 而对于第一种方法,修改缓存有效期长度会影响每一次过期检查,这有利于缓存过期的调试.

此处两个概念不要混淆,缓存过期时间和缓存有效长度,前者是一个具体的时间点,后者是一个时间长度,如果缓存有效期长度不会变化的话,两种方法没有太大的差别,根据您的需要选择其中一种。

Smarty缓存过期检查在在着一定的管理开销.因为每次请求动态内容时,即使命中缓存,Smarty仍然要通过is_cached()display()对缓存文件的内容进行再次分析, 每次分析都要调用Smarty_core_read_cache_file核心函数,包括提取缓存标志信息和HTML页面,这涉及大量字符串操作

           是否放弃Smarty缓存

作者自己设计了一个简单的缓存方法:设计思路是在缓存文件中只存储HTML,而将文件修改时间作为判断依据,通过PHPstat()方法可以获得文件的最后一次修改时间。

此处,作者使用自己设计的缓存方法进行压力测试,与smarty缓存方法对比(此处的安全暂时放弃)

我们必须清楚一个原则,在动态网页加载HTML缓存方法并终止程序之前,我们要让它尽可能只加载必要的其他文件,这些文件越少越好,比如引用某些PHP库文件,因为这些PHP库文件可能在直接输出缓存时根本不需要。

如果你使用缓存的目的是跳过数据库的访问,那么请做得彻底一些,在加载缓存之前,将与数据库访问的库文件引用关闭掉。

           把缓存放到内存中

动态网页的HTML缓存还可以放在其他地方,如本机内存,借助PHPAPC模块,我们可以轻松地将任何PHP运行时的数据或对象缓存在内存中,这样一来,缓存的过程将没有任何磁盘I/O操作。

APC缓存的代码()

APC缓存方法的压力测试:内容略,参见附录安全分析.

只给结论,因为这个结论可以帮助分析问题。

APC压力测试吞吐率是: 473req/s.也许你非常失望,相比于前面的461.08req/s,为什么性能的提高如此微不足道?我们这次可是在内存中读写缓存,避免了之前文件缓存加载时的磁盘I/O开销,为什么效果不明显呢?

原因很简单,其实用strace跟踪PHP进程,你会发现,在动态网页处理过程中,需要加载的磁盘文件非常多,而缓存文件只是其中一个,仅仅把它放在内存里,对整体的影响当然微不足道。

           缓存服务器

我们还可以将HTML缓存存储在一台独立的缓存服务器中,利用memcached,我们可以很容易地通过TCP将缓存存储在其他服务器中,而且memcached同样也是使用内存空间来保存缓存数据,减少了不必要磁盘I/O.

代码和压力测试参见附录案例分析

 

如何选择
       几种缓存方法压力测试的结果对比:

  

整页缓存方法

吞吐率(reqs/s)

不使用缓存

51.59

Smarty Cache

173.95

File cache

461.08

APC cache

473.38

xCache cache

462.05

Memcached cache

388.62

 

在实际情况中,一个站点包含了大量的动态网页,如果你为缓存分配的内存空间不足以容纳所有的缓存数据,便会使得缓存命中率大幅度下降,吞吐率也会随之降低。

一旦本地内存达到上限,你的站点规模已经让你不得不考虑比性能更加重要的问题那就是如何扩展缓存存储区,显然,使用memcache来实现分布式缓存使得扩展成为可能。

WEB服务器特别是应用服务器本身的内存是相当宝贵的,它要满足HTTP进程和脚本解释器的大量开销,无法拿出大量的空间来存放HTML缓存;其次,使用本机内存不具备良好的扩展性

一旦缓存数据和站点负载大幅度增加,为了保证较高的缓存命中率,必须加大缓存空间,本机内存显然成为瓶颈,而使用独立的缓存服务器可以便于扩展,构成多台服务器组成的分布式缓存系统。

对于小规模或者初创时期的WEB站点,如果需要缓存的动态网页比较少,这时候使用APC内存缓存仍然不失为一个快速有效的方案。

           有效期取值

有效期如果太长,虽然缓存命中率提高了,但是动态网页的内容更新总是不能及时展现;而有效期如果太短,动态网页的内容虽然实时变化,但是频繁地创建缓存比没有缓存好不到哪儿去。

这两种情况正是“过”与“不及”的两种表现,而成功往往就在“过”与“不及”之间。

缓存机制还提供了一个有效缓存控制的途径,那就是可以在任何时候强行清除缓存,这在动态内容更新频率较低的时候适合使用。

比如通常在一个Blog中,阅读的人总是远远多于撰写内容的人,这样就可以将缓存有效期设置得相对长一些,而每次内容更新时都主动清除缓存,促使动态内容创建新的缓存

Smarty内置缓存方法:

$this-smarty-clear_cache(‘place_posts.htm’, ‘12882’);

Smarty会找到缓存文件,然后删除它

    4.4 局部无缓存(针对动态网页)

对于有些特殊的动态网页,需要页面中某一块区域内容及时更新,比如一个新闻正文页面的阅读量统计区域和评论区域,如果为了这一块区域的及时更新,就将整个页面重新创建缓存的话,的确有点不值得,在流行的模板框架中,在整页缓存的基础上,都提供了局部无缓存的支持,它允许在页面中指定一块包含动态数据的HTML代码段,每次这些动态数据都需要实时计算,然后和其余的缓存合成最终的网页。

   Smarty模板中要实现局域无缓存,可以增加一个模板扩展标记,并且注册到Smarty对象中,只需要将不希望缓存的部分用标签包围起来即可。

cakePHP框架中,需要用标签将视图中无缓存区域包围起来,也可以实现相同的效果

引入局部无缓存机制后,动态网页控制器也要进行相应的修改,其目的就是把无缓存区域对应的动态数据计算提到缓存检查之前,这样才可以保证每次都将实时计算后的结果输出到模板中。但是这样对于性能是有影响的。

需要注意的是:一定要评估局部动态数据的影响力,如果你将一个动态网页中占主要开销有数据计算置于无缓存状态,那么缓存对你来说已经失去了意义,你可以考虑选择其他缓存方式或者页面组织结构,比如使用数据层缓存。

    4.5 静态化内容

动态程序看起来就像是缓存的代理人,每次用户的请求都要首先被送到动态程序,然后动态程序根据缓存的有效期来决定是否输出缓存。这种方法使得动态内容对于缓存数据有着较强的控制权,但是这种控制权的代价是比较昂贵的,它带来的性能上的不足。

           直接访问缓存

我们将前面动态网页place_posts.php在缓存目录中的静态页面拿出来放到与place_posts.php同目录下,重命名为place_posts.htm,然后进行压力测试:


吞吐率为11769reqs/s, 它比动态缓存的吞吐率高出百倍.如此高吞吐率的前提条件是必须拥有150 x 8Mbit/s的出口带宽,1.2G的带宽

实际情况中,吞吐率往往受限于WEB服务器的出口带宽,比如我们购买了100M独享带宽,那么按照这个13KB的网页来计算,如果不考虑IP的其他信息长度,理论上最大的吞吐率为:

((100Mbit/8) x 1000)/13KB = 961.53reqs/s

也就是说,在使用100M独享带宽的服务器上,这个13KB网页的吞吐率最高只能达到961.53reqs/s(本机上测试), 而实际经过测试(非本机上)的吞吐率也达到了859.01reqs/s.

当我们一旦使用静态化网页的缓存方案后,你会发现原来的一切都将被颠覆。缓存更新,过期检查,以及缓存待久化,都不能按照原来的方式来设计,我们必须重新来审视静态化缓存方案的具体实现。

我们一般会使用CMS(内容管理系统)来管理静态化内容,同时CMS也可以必要的时候帮助我们更新静态化内容。

           更新策略

对于静态化内容的更新策略,一般有以下两种:

l  数据更新时重新生成静态化内容。

l  定时重新生成静态化内容。

通常这两种更新策略可以相互弥补,共同应用在站点的静态化方案中.

 

策略一案例:

案例描述:

解决方案:

案例描述,解决方案参见附录案例分析5.

 

策略二:

           局部静态化

            SSI技术

静态网页也可以不必整页更新,它可以通过SSI(服务器端包含)技术实现局部页面的更新,这便大大节省了重建整个网页时的计算开销和磁盘I/O开销,甚至包括分发时的网络I/O开销

SSI技术现在可以在任何一个主流的WEB服务器中找到相应的模块。比如Apachemod_includeLighttpdmod_ssi模块。

  SSI技术的原理:

一旦网页支持SSI,那么每次请求的时候服务器必须要通读网页内容,查找include标签,这需要大量的CPU开销。通读网页的解释:正常情况下,CPU不用通读网页,直接通过sendfile()在内存与网卡缓冲区传输网页即可,但是SSI技术迫使CPU读完全部网页,从中查找include标签,所以增加了CPU开销

         SSI技术的剖析,请参见附录案例分析6.

         SSI页面的压力测试

         合成SSI页面,但后缀仍然使用.shtml的页面压力测试, 吞吐率提升了一倍

         shtml后缀网页改名为html后缀网页, 再压力测试, 吞吐率接近8000reqs/s

   使用SSI/shtml/html的压力测试结果对比图


如何选择SSI ?

通过这一系列测试,我们可以深刻的理解SSI的本质,并且认识到它对性能的影响,只有认识了本质后,我们才可能在使用中根据需要来把握平衡。

站点负载不大,或者带宽限制的情况下,完全可以使用必要的include来更好的管理静态内容。

其次,这也是非常重要的一点

我们可以将SSI静态内容通过后缀与html静态页面区分开来,但实际上,往往站点中的一些静态网页已经开放很长时间,无法随意修改后缀,所以我们最后不得不直接对所有htm后缀的网页打开SSI模式,这让大部分拥有htm后缀但没有使用SSI的静态网页深受其害。我们正是要减少这部分开销---htm后缀的静态网页开启SSI模型,通读整个网页。

Apache提供了一个思路,它允许打开XbitHack模式,如下所示:

XBitHack  on

这样一来,Apache会对所有具有执行权限的静态网页开启SSI模式,比如要对index_with_ssi.htm网页开启SSI模式, 只需要修改它的权限即可:

Chmod +x index_with_ssi.htm

如果觉得每次都要手动修改权限不现实,我们完全可以通过CMS来自动完成。

 

附录:

. 本章的案例分析

      1. 使用Smart缓存与不使用的效果

我们先对动态网页进行一次未使用任何缓存情况下的压力测试,结果如下所示:


    注意: 在不使用任何缓存的时候吞吐率仅为51.59reqs/s

    接下来我们使用Smarty的默认缓存方法,并且打开缓存开关. 下面是php 代码片段:



do_some_db_query() 代表所有数据库访问操作

我们再来看看压力测试的结果:


 注意: 使用Smarty缓存后, 吞吐率提高了3, 变为173.95reqs/s

 使用Strace来跟踪系统调用, 来看看节省的时间有哪些?

 未使用缓存时,跟踪结果如下:


使用缓存后,跟踪结果如下:


可以很明显的看出, read()调用在使用Smarty缓存后大量减少, 而未使用缓存时read()的总时间达到92.52%之多,那么减少的这部分read()是什么呢? 与数据库通信时所接收的数据.

2. APC数据缓存方法

借助PHPAPC模块,我们可以轻松的将任何PHP运行时的数据和对象缓存在内存当中, 这样一来加载缓存的过程将没有任何磁盘IO操作. APC数据缓存提供了Key/Value的存储方式, 即使在保存大量key的时候,也能保证高效的查找性能.所以我们不用为缓存目录分级的事情操心,每个Key都可以设置有效期长度,一旦过期,便会被删除. 代码如下:


压力测试如下:


       


3. 缓存服务器

                    应用memcached的代码片段:

  

         对其进行压力测试:

        

看起来结果不是很好, 毕竟它在在TCP socket操作的开销, 比起Smarty缓存性能好很多

 

 

4. 对比分析几种缓存的吞吐率:




5. 更新策略案例分析: 频繁页面更新,引起服务响应大幅降低

静态化内容的更新策略:

数据更新时重新生成静态化内容

数据在更新时重建静态化缓存,往往由用户某些动作触发. 比如新闻站点的网络编辑发表一篇新闻后,程序便创建一个新的静态化页面, 同时更新新闻列表页面.显然,这种方式在数据更新频繁时在在大量重建静态内容的开销.尤其是当一个动作引发大量静态内容需要更新时,比如大型新闻站点的CMS(内容管理系统)在工作高峰期间,所有编辑都修改新闻标题而引发大量的页面或局部页面频繁更新. 同时伴随着频繁的数据库操作,这将导致CMS系统的服务响应大幅度降低.当然,这可能并不直接影响现存的静态页面的访问,但是不要忘了, 这种更新机制也正是静态化缓存方案的一部分,所以它的性能至关重要.

一种常用的办法是引入延迟更新机制,将更新任务放入队列,一旦队列写满或达到超时时间,便将它们一次性更新到磁盘.

 

6. SSI技术的剖析

6.1 SSI页面的压力测试

我找来一个站点的首页index.shtml, 它自身大小为8472个字节,它包含了19个子页面,分别通过include方法进行加载,最终大小为50456个字节. WEB服务器使用Lighttpd对这个静态化的首页进行压力测试:


吞吐率为1445.72reqs/s, 我想这个结果对于50KB左右的静态网页来说确实不高,是否它在处理时加载了太多的其它文件? 导致处理时间延长, 答案是肯定的.

 

6.2 合成SSI页面的压力测试

接下来, 我们将最终合成的网页直接保存在服务器上, 取名为index_without_ssi.shtml, 然后对它进行同样的压力测试:


吞吐率提高了近一倍, 可见这19个子页面确实消耗了不少的开销. 我们用Strace来跟踪Lighttpd进程来看看磁盘I/O的变化, 在使用SSI的时候:


在不使用SSI的时候:


同样都是接受了1000个请求, 在使用SSI的时候, open()stat64()执行次数比较多, 等待时间比较长,所占时间比例较大,它们用于打开文件和查看文件状态, 是服务器加载子页面时执行的系统调用,同时用于传输sendfile64()系统调用所占比例较小. 而在不使用SSIsendfile64()占据了主要时间, 也就是说更多的时间花在了文件传输上,所以吞吐率要高一些.

       虽然刚才我们没有使用SSI, 但是我们网页面的后缀仍然是shtml,lighttpd进程仍然会通读整个网页的内容进行查找, 这显然不是我们希望的结果.

 

       6.3 shtml后缀网页改名为html后缀网页, 再压力测试

        我们修改了这个静态网页的后缀,将其改为index_without_ssi.html, 再进行压力测试:

      

这下你该可以接受现在的结果了,顺便看看数据传输速率, 接受每秒400MB,这相当于需要3.2G带宽, 这简直比我们前面测试的13KB1.2G带宽还要高.

回到刚才的话题, 我们将shtml后缀改为html, Lighttpd进程处理静态内容时便不会在通读网页内容, 这时候我们同样的Strace跟踪lighttpd进程:


       可以看出, 用于数据传输的sendfile64()系统调用占用了更多的时间,而其他的系统调用所占时间比例相对减少, 但你也许会发现, 从系统调用的总时间0.000448s来看,相比于使用shtml时的0.000836,减少的比例似乎不足以让它的吞吐率提升如此之多.

       我们多次使用Strace来跟踪进程,跟踪的目的是观察进程花在系统调用上的时间, 这些系统都发生在系统内核态, 大部分代表着系统对外设的操作,比如各种I/O操作,但是对于用户态的CPU操作,strace是无法跟踪的, 所以当我们使用html, 节省了原来大量扫描静态文件内容CPU计算, 这些时间在Strace中是没有反映的.

 

使用SSI/shtml/html压力测试结果对比图


. 本章的系统调用

       Open()

       Stat64()

. 本章的模块

Apache : mod_includeSSI技术)

Lithttpd: mod_ssiSSI技术)

. 本章的配置:

SSI技术相关

Apache相关配置:

AddType text/html  .shtml

AddOutputfilter INCLUDES .shtml

Lighttpd配置:

Ssi.extensiion = ( “.shtml” )

Apache XBitHack配置

XBitHack on

. 本章的工具

Apache的调试日志,可以分析WEB服务器反应慢的原因

Strace–p 进程号  -c 可以对进程所进行的系统调用进行统计,以发现哪些系统调用占用了大量时间比

阅读(1634) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~