Web项目开发中用到的缓存技术
在WEB开发中用来应付高流量最有效的办法就是用缓存技术,能有效的提高服务器负载性能,用空间换取时间。
缓存一般用来
- 存储频繁访问的数据
- 临时存储耗时的计算结果
- 内存缓存减少磁盘IO
使用缓存的2个主要原因:
- 降低延迟:缓存离客户端更近,因此,从缓存请求内容比从源服务器所用时间更少,呈现速度更快,网站就显得更灵敏。
- 降低网络传输:副本被重复使用,大大降低了用户的带宽使用,其实也是一种变相的省钱(如果流量要付费的话),同时保证了带宽请求在一个低水平上,更容易维护了。
在WEB开发中,缓存可以分为:
一、数据库端缓存
- 数据库的缓存一般由数据库提供,可以对表建立高速缓存。数据库中,用户可能多次执行相同的查询语句,为了提高查询效率,数据库会在内存划分一个专门的区域,用来存放用户最近执行的查询,这块区域就是缓存。(参考MYSQL缓存)
- “空间换时间”,比如建一个表来存储另外一个表某个类型的数据的总条数,在每次更新数据的时候同事更新 数据表和统计条数的表。在需要获取某个类型的数据的条数的时候,就不需要select count去查询,直接查询统计表就可以了,这样可以提高查询的速度和数据库的性能。
- 数据库查询缓存,每次访问页面的时候,都会先检测相应的缓存数据是否存在,如果不存在,就连接数据库,得到数据,并把查询结果序列化后保存到文件中,以后同样的查询结果就直接从缓存表或文件中获得。用的最广的例子看Discuz的搜索功能,把结果ID缓存到一个表中,下次搜索相同关键字时先搜索缓存表。举个常用的方法,多表关联的时候,把附表中的内容生成数组保存到主表的一个字段中,需要的时候数组分解一下,这样的好处是只读一个表,坏处就是两个数据同步会多不少步骤,数据库永远是瓶颈,用硬盘换速度,是这个的关键点。
二、应用层缓存
应用层缓存这块跟开发人员关系最大,也是平时经常接触的。
- 缓存数据库的查询结果,减少数据的压力。这个在大型网站是必须做的。
- 缓存磁盘文件的数据。比如常用的数据可以放到内存,不用每次都去读取磁盘,特别是密集计算的程序,比如中文分词的词库。
- 缓存某个耗时的计算操作,比如数据统计。
应用层缓存的架构也可以分几种:
- 嵌入式,也就是缓存和应用在同一个机器。比如单机的文件缓存,java中用hashMap来缓存数据等等。这种缓存速度快,没有网络消耗。
- 分布式缓存,把缓存的数据独立到不同的机器,通过网络来请求数据,比如常用的memcache就是这一类。
比较常见的应用层分布式缓存容器,Memcache、共享文件服务器、MemcacheDb、Tokyo Tyrant。 php里面也有比如x-cache,apc等的基于进程的缓存,这种缓存比分布式缓存速度快,但是限于跟应用的一个机器。 java实现的缓存也比较多,比如oscache,jcache ,ehcached等等。
三、前端缓存(服务端缓存)
我们这里说的前端缓存可以理解为一般使用的cdn技术,利用squid等做前端缓冲技术,主要还是针对静态文件类型,比如图片,css,js,html等静态文件。
一般针对静态资源如CSS,JS,图片等使用缓存,原因如下:
- 请求更快:通过将内容缓存在本地浏览器或距离最近的缓存服务器(如CDN),在不影响网站交互的前提下可以大大加快网站加载速度。
- 节省带宽:对于已缓存的文件,可以减少请求带宽甚至无需请求网络。
- 降低服务器压力:在大量用户并发请求的情况下,服务器的性能受到限制,此时将一些静态资源放置在网络的多个节点,可以起到均衡负载的作用,降低服务器的压力。
前端比较常用的就是squid,Varnish Cache,ncache等等。Varnish是一款高性能的开源HTTP加速器,挪威最大的在线报纸 Verdens Gang (vg.no) 使用3台Varnish代替了原来的12台squid,性能比以前更好。
四、客户端缓存(浏览器缓存)
客户端缓存依赖于浏览器的实现,目前一般的浏览器都实现了基于http都信息来缓存相应的文件。浏览器端的缓存,可以让用户请求一次之后,下一次不在从服务器端请求数据,直接从本地缓存读取,可以减轻服务器负担也可以加快用户的访问速度。
浏览器缓存分为强缓存和协商缓存:
- 强缓存:浏览器在加载资源时,先根据这个资源的一些http header判断它是否命中强缓存,强缓存如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器。比如某个css文件,如果浏览器在加载它所在的网页时,这个css文件的缓存配置命中了强缓存,浏览器就直接从缓存中加载这个css,连请求都不会发送到网页所在服务器;
- 协商缓存:当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回(304),但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;若未命中请求,则将资源返回客户端,并更新本地缓存数据(200)。
强缓存与协商缓存区别:强缓存不发请求到服务器,协商缓存会发请求到服务器。
如何设置缓存
1. HTML Meta标签控制缓存(非HTTP协议定义)
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。这种方法使用上很简单,但只有部分浏览器可以支持,而且所有缓存代理服务器都不支持,因为代理不解析HTML内容本身。
2. HTTP头信息控制缓存
HTTP头信息发送在HTML代码之前,只能被浏览器和一些中间缓存能看到,一个典型的HTTP 1.1协议返回的头信息看上去像这样:
HTTP/1.1 200 OK Date: Fri, 30 Oct 1998 13:19:41 GMT Server: Apache/1.3.3 (Unix) Cache-Control: max-age=3600, must-revalidate Expires: Fri, 30 Oct 1998 14:19:41 GMT Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT ETag: "3e86-410-3596fbbc" Content-Length: 1040 Content-Type: text/html
头信息空一行后是HTML代码的输出。
HTTP头信息控制缓存是通过Expires(强缓存)、Cache-control(强缓存)、Last-Modified/If-Modified-Since(协商缓存)、Etag/If-None-Match(协商缓存)实现,下面详细介绍。
1)Expires是http1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间,由服务器返回,用GMT格式的字符串表示,如:Expires:Thu, 31 Dec 2016 23:55:55 GMT,读取缓存数据条件:缓存过期时间(服务器的)< 当前时间(客户端的)。
尽管Expires头很有用,但它有一定的局限性。首先,因为牵扯到时间,Web服务器端的时钟必须和缓存的同步,否则很可能实现不了预期的结果——缓存把前女友当初现女友,把现女友当作过去式——那就悲剧了。另外一个问题是,你很容易忘记给某内容设置了一个特定时间,如果返回内容的时候没有更新这个过期时间,则每个请求都是上访到服务器,反而增加了负载和响应时间。
缺点:Expires是较老的强缓存管理header,由于它是服务器返回的一个绝对时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。
2)Cache-Control(缓存控制)HTTP头信息,是HTTP 1.1引入了新的头信息,描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较Expires,Cache-Control的缓存管理更有效,安全一些。读取缓存数据条件:上次缓存时间(客户端的)+max-age < 当前时间(客户端的)。Cache-Control值可以是public、private、no-cache、no- store、no-transform、must-revalidate、proxy-revalidate、max-age
各个消息中的指令含义如下:
- max-age=[秒]:表示在这个时间范围内缓存是新鲜的无需更新。类似Expires时间,不过这个时间是相对的,而不是绝对的。也就是某次请求成功后多少秒内缓存是新鲜的。(指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。上次缓存时间(客户端的)+max-age(64200s)<客户端当前时间)
- s-maxage=[秒]:类似max-age, 除了仅应用于共享缓存(如代理)。
- min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
- max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
- public:指示响应可被任何缓存区缓存。一般而言,需要认证HTTP请求内容会自动私有化(不会被缓存Add)。
- private:指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当前用户的部分响应消息,此响应消息对于其他用户的请求无效。(允许缓存专门为某一个用户存储响应,比方说在浏览器中;共享缓存一般不会,例如在代理中。)
- no-cache:指示请求或响应消息不能缓存,该选项并不是说可以设置”不缓存“,而是需要和服务器确认。(每次在释放缓存副本之前都强制发送请求给源服务器进行验证,这在确保认证有效性上很管用(和public结合使用)或者保证内容必须是即时的,不得无视缓存的所有优点,如国内的微博、twitter等的刷新显示Add。)
- no-store:在请求消息中发送将使得请求和响应消息都不使用缓存,完全不存下来。(强制缓存在任何情况下都不要保留任何副本。)
- must-revalidate:告诉缓存,我给你准备了一些关于新鲜度的信息,在表现的时候要严格遵循之。HTTP允许缓存在某些特定情况下返回过期数据,指定了这个属性,相对于告诉缓存,你丫必须严格遵循我的规则。
- proxy-revalidate:类似must-revalidate,除了只能应用于代理缓存。
注意:这两个header可以只启用一个,也可以同时启用,当response header中,Expires和Cache-Control同时存在时,Cache-Control优先级高于Expires。
3)Last-Modified/If-Modified-Since:Last-Modified/If-Modified-Since要配合Cache-Control使用。
Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。
If-Modified-Since:当资源过期时(强缓存失效),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。
缺点:Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间(无法及时更新文件)
如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存,有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形(无法使用缓存)。
HTTP1.1中Etag解决了上述问题。
4)Etag/If-None-Match:Etag/If-None-Match也要配合Cache-Control使用。
Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。
If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。
Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag一起使用时,服务器会优先验证ETag。
yahoo的Yslow法则中则提示谨慎设置Etag:需要注意的是分布式系统里多台机器间文件的last-modified必须保持一致,以免负载均衡到不同机器导致比对失败,Yahoo建议分布式系统尽量关闭掉Etag(每台机器生成的etag都会不一样,因为除了 last-modified、inode 也很难保持一致)。
缓存的更新和过期
我们这几只讲应用层的缓存。在应用层的缓存由于应经有新的数据加入,数据的修改,数据的删除等等操作,而在某些时间,我们需要这些操作及时的生效(由于用了缓存,可能会导致修改后缓存没有更新,而页面也没有变化),所以出现缓存的更新和过期的概念。
缓存的过期包含
- 时间过期:我们在缓存数据的时候我们可以指定数据缓存的最大时间,如果超过这个时间,我们就认为缓存是失效的。
- 基于规则的过期:我们在缓存中存储了某些数据来标明数据的版本。比如存取的时间,更新的时间,数据的版本信息等等,然后比较这些信息是否有变化来判断是否过期。
缓存的更新
- 被动:当缓存失效的时候我们的应用程序重新从主存储器中取数据,然后重新放回缓存中。
- 主动:当数据一更新的时候,我们的应用主动的去更新我们的缓存内容。
- 被动和主动结合(基于版本):当数据以更新的时候,我们更新一个数据被更新的标志。然后根据上面讲到的“基于规则的过期”来更新数据。
如何评价缓存的好坏
速度:当然,我们使用缓存的目的之一就是要提高我们应用的速度。如果使用缓存后速度更慢那就没有缓存的必要了。取缓存的速度应 当是非常快的,因为缓存的工作仅仅是取出一个曾经存储好的数据。所以影响缓存速度的因素可能跟缓存所才采用的存储方式有关系,还有可能印象速度的就是分布 式缓存的网络消耗,同样,缓存服务器在并发很大的时候也有可能不堪重负出现瓶颈,变得缓慢。
比如目前凤凰论坛使用的帖子缓存的响应时间在亚毫秒级,也就是还不足1ms的响应时间,这个速度是相当快的。
及时性:我们使用缓存的重要一点就是对我们的数据的更新需要缓存也及时的更新。这点非常重要,关系到页面显示的正确性,用户体验等。
命中率:命中率直接关系到缓存所起到的作用的大小。如果命中率太低就相当于没有使用缓存,或者我们的缓存的规则有问题,重视命中不到缓存。
如何有效的使用缓存
哪些地方需要缓存
- 页面的打开速度非常慢,每一次都要进行大量的计算,从数据库取一个比较耗时操作的数据。
- 频繁的读取操作,每次请求都要从数据库读取数据,而这些数据的更新并不是很频繁,导致数据库的压力非常大。
- 频繁的写入操作(需要写到数据库),比如页面点击量,投票数的操作。这些操作可以暂时利用缓存,然后定期的写到数据库。
选择什么缓存
缓存如此有用,那么这么多的缓存产品,我们如何选择呢?下面我们主要就常用的php方面来说。
- 访问量不是特别大,web前端机器只有一台,对数据库端的内容缓存可以使用文件缓存,这种方式非常简单也比较有效,不需要对服务器做特别的配置。也可以使用apc,x-cached等提供的内存缓存,也非常有效,它比磁盘缓存速度快,但是因为是内存缓存,所以缓存的容量有限。
- 访问量比较大,web前端机器一般超过一台甚至更多,我们一般采用分布式的缓存。我们一般选择memcache作为我们的缓存,memcache是一个基于内存的分布式缓存,它的速度和效率非常高。
其实这里也可以使用共享的磁盘缓存,但是基于我们目前所处的环境和对于可靠性,高并发的要求,我们不建议或者说不考虑使用共享磁盘作为缓存。
================PHP中9大缓存技术总结===================
1、全页面静态化缓存
也就是将页面全部生成html静态页面,用户访问时直接访问的静态页面,而不会去走php服务器解析的流程。此种方式,在CMS系统中比较常见,比如dedecms;
2、页面部分缓存
该种方式,是将一个页面中不经常变的部分进行静态缓存,而经常变化的块不缓存,最后组装在一起显示;可以使用类似于ob_get_contents的方式实现,也可以利用类似ESI之类的页面片段缓存策略,使其用来做动态页面中相对静态的片段部分的缓存(ESI技术,请baidu,此处不详讲)。该种方式可以用于如商城中的商品页。
3、数据缓存
顾名思义,就是缓存数据的一种方式;比如,商城中的某个商品信息,当用商品id去请求时,就会得出包括店铺信息、商品信息等数据,此时就可以将这些数据缓存到一个php文件中,文件名包含商品id来建一个唯一标示;下一次有人想查看这个商品时,首先就直接调这个文件里面的信息,而不用再去数据库查询;其实缓存文件中缓存的就是一个php数组之类。Ecmall商城系统里面就用了这种方式。
4、查询缓存
其实这跟数据缓存是一个思路,就是根据查询语句来缓存;将查询得到的数据缓存在一个文件中,下次遇到相同的查询时,就直接先从这个文件里面调数据,不会再去查数据库;但此处的缓存文件名可能就需要以查询语句为基点来建立唯一标示;
按时间变更进行缓存:其实,这一条不是真正的缓存方式;上面的2、3、4的缓存技术一般都用到了时间变更判断;就是对于缓存文件您需要设一个有效时间,在这个有效时间内,相同的访问才会先取缓存文件的内容,但是超过设定的缓存时间,就需要重新从数据库中获取数据,并生产最新的缓存文件;比如,我将我们商城的首页就是设置2个小时更新一次。
5、按内容变更进行缓存
这个也并非独立的缓存技术,需结合着用;就是当数据库内容被修改时,即刻更新缓存文件。比如,一个人流量很大的商城,商品很多,商品表必然比较大,这表的压力也比较重;我们就可以对商品显示页进行页面缓存;当商家在后台修改这个商品的信息时,点击保存,我们同时就更新缓存文件;那么,买家访问这个商品信息时,实际上访问的是一个静态页面,而不需要再去访问数据库。
试想,如果对商品页不缓存,那么每次访问一个商品就要去数据库查一次,如果有10万人在线浏览商品,那服务器压力就大了;
6、内存式缓存
提到这个,可能大家想到的首先就是Memcached;memcached是高性能的分布式内存缓存服务器。 一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、 提高可扩展性。它就是将需要缓存的信息,缓存到系统内存中,需要获取信息时,直接到内存中取;比较常用的方式就是 key–>value方式。
7、apache缓存模块
apache安装完以后,是不允许被cache的。如果外接了cache或squid服务器要求进行web加速的话,就需要在htttpd.conf里进行设置,当然前提是在安装apache的时候要激活mod_cache的模块。
安装apache时:./configure –enable-cache –enable-disk-cache –enable-mem-cache
8、php APC缓存扩展
Php有一个APC缓存扩展,windows下面为php_apc.dll,需要先加载这个模块,然后是在php.ini里面进行配置:
[apc] extension=php_apc.dll apc.rfc1867 = on upload_max_filesize = 100M post_max_size = 100M apc.max_file_size = 200M upload_max_filesize = 1000M post_max_size = 1000M max_execution_time = 600 ; 每个PHP页面运行的最大时间值(秒),默认30秒 max_input_time = 600 ; 每个PHP页面接收数据所需的最大时间,默认60 memory_limit = 128M ; 每个PHP页面所吃掉的最大内存,默认8M
9、Opcode缓存
PHP的缓冲器、加速器,有eaccelerator, apc, phpa,xcache。
首先php代码被解析为Tokens,然后再编译为Opcode码,最后执行Opcode码,返回结果;所以,对于相同的php文件,第一次运行时可以缓存其Opcode码,下次再执行这个页面时,直接会去找到缓存下的opcode码,直接执行最后一步,而不再需要中间的步骤了。比较知名的是XCache、Turck MM Cache、PHP Accelerator等。
文章参考:
http://www.cnblogs.com/sunli/archive/2009/11/24/1609444.html
https://segmentfault.com/a/1190000006741200
https://my.oschina.net/leejun2005/blog/369148
http://www.oschina.net/news/41397/web-cache-knowledge
http://www.open-open.com/lib/view/open1479181086120.html
http://www.jb51.net/article/49714.htm
http://www.php100.com/html/php/lei/2015/0919/8969.html
版权声明:本文采用署名-非商业性使用-相同方式共享(CC BY-NC-SA 3.0 CN)国际许可协议进行许可,转载请注明作者及出处。 |