基于资源的HTTP Cache的实现介绍

我们都知道浏览器会缓存访问过网站的网页,浏览器通过URL地址访问一个网页,显示网页内容的同时会在电脑上面缓存网页内容。如果网页没有更新的话,浏览器再次访问这个URL地址的时候,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。

一、什么是HTTP Cache

对于浏览器的这种网页缓存机制大家已经耳熟能详了,举个例子来说,JavaEye的新闻订阅地址:http://www.iteye.com/rss/news , 当浏览器或者订阅程序访问这个URL地址的时候,JavaEye的服务器在response的header里面会发送给浏览器如下状态标识:

C代码  

  1. Etag    "427fe7b6442f2096dff4f92339305444"
  2. Last-Modified   Fri, 04 Sep 2009 05:55:43 GMT

这就是告诉浏览器,新闻订阅这个网络资源的最后修改时间和Etag。于是浏览器把这两个状态信息连同网页内容在本地进行缓存,当浏览器再次访问JavaEye新闻订阅地址的时候,浏览器会发送如下两个状态标识给JavaEye服务器:

C代码  

  1. If-None-Match   "427fe7b6442f2096dff4f92339305444"
  2. If-Modified-Since   Fri, 04 Sep 2009 05:55:43 GMT

就是告诉服务器,我本地缓存的网页最后修改时间和Etag是什么,请问你服务器的资源有没有在我上次访问之后有更新啊?于是JavaEye服务器会核对一下,如果该用户上次访问之后没有更新过新闻,那么根本就不必生成这个RSS了,直接告诉浏览器:“没什么新东西,你还是看自己缓存的网页吧”,于是服务器就发送一个304 Not Modified的消息,其他什么都不用干了。

这就是HTTP层的Cache,使用这种基于资源的缓存机制,不但大大节省服务器程序资源,而且还减少了网页下载次数,节约了很多网络带宽。

二、HTTP Cache究竟有什么作用?

我们通常的动态网站编程,服务器端程序根本就不去处理浏览器发送过来的If-None-Match和If-Modified-Since状态标识,只要有请求就生成网页发送给浏览器。对于一般情况来说,用户不会总是没完没了刷新一个页面,所以大家并不认为这种基于资源的缓存有什么太大的作用,但实际情况并非如此:

1、像Google这种比较智能的网络爬虫可以有效识别资源的状态信息,如果使用这种缓存机制,可以大大减少爬虫的爬取次数。

比方说Google每天爬JavaEye网站大概15万次左右,但实际上JavaEye每天有更新的内容不会超过1万个网页。因为很多内容更新比较快,因此Google就会反复不停的爬取,这样本身就造成了很多资源的浪费。如果我们使用HTTP Cache,那么只有当网页内容发生改变的时候,才会真正进行爬取,其他时候我们直接告诉Google的爬虫304 Not Modified就可以了。这样不但降低了服务器本身的负载和爬虫造成的网络带宽消耗,实际上也大大提高了Google爬虫的工作效率,岂不是皆大欢喜?

2、很多内容更新不频繁的网页,尽管用户不会频繁的刷新,但是从一个比较长的时间段来看使用HTTP Cache,仍然可以起到很大的缓存作用。

比方说一些历史讨论帖子,已经过去了几个月了,这些帖子内容很少更新。用户可能通过搜索,收藏链接,文章关联等方式时不时访问到这个页面。那么只要用户访问过一次以后,后续所有访问服务器直接发送304 Not Modified就可以了,不用真正生成页面。

3、对于历史帖子使用HTTP Cache可以避免爬虫反复的爬取。

比方说JavaEye的论坛帖子列表页面,分页到20页后面的帖子已经很少有人直接访问了,但是从服务器日志去看,每天仍然有大量爬虫反复爬取这些分页到很后面的页面。这些页面由于用户很少去点击,所以基本上没有被应用程序的memcached缓存住,每次访问都会造成很高的资源消耗,爬虫隔一段时间就爬一次,对服务器是很大的负担。如果使用了HTTP Cache,那么只要爬虫爬过一次以后,以后无论爬虫爬多少次,都可以直接返回304 Not Modified了,极大的节省了服务器的负载。

三、如何在应用程序里面使用HTTP Cache

如果我们要在自己的程序里面实现HTTP Cache,是件非常简单的事情,特别是对Rails来说只需要添加一点点代码,以上面的JavaEye新闻订阅来说,只要添加一行代码:

Ruby代码  

  1. def news
  2. fresh_when(:last_modified => News.last.created_at, :etag => News.last)
  3. end

用最新新闻文章作为Etag,该文章最后修改时间作为资源的最后修改时间,这样就OK了。如果浏览器发送过来的标识和服务器标识一致,说明内容没有更新,直接发送304 Not Modified;如果不一致,说明内容更新,浏览器本地的缓存太古老了,那么就需要服务器真正生成页面了。

以上只是一个最简单的例子,如果我们需要根据状态做一些更多的工作也是很容易的。比方说JavaEye博客的RSS订阅地址: http://robbin.iteye.com/rss

Ruby代码  

  1. @blogs  = @blog_owner.last_blogs
  2. @hash  = @blogs .collect{|b| {b.id => b.post.modified_at.to_i + b.posts_count}}.hash
  3. if stale?(:last_modified => (@blog_owner.last_blog.post.modified_at || @blog_owner.last_blog.post.created_at), :etag => @hash)
  4. render :template => "rss/blog"
  5. end

这个实现稍微复杂一些。我们需要判断博客订阅所有的输出文章是否有更新,所以我们用博客文章内容最后修改时间和博客的评论数量做一个hash,然后用这个hash值作为资源的Etag,那么只要这些博客文章当中任何文章内容被修改,或者有新评论,都会改变Etag值,从而通知浏览器内容有更新了。

除了RSS订阅之外,JavaEye网站还有很多地方适合使用HTTP Cache,比方说JavaEye论坛的版面列表页面,一些经常喜欢泡论坛的用户,可能时不时会上来刷新一下版面, 看看有没有新的帖子,那么我们就不必每次用户请求的时候都去执行程序,生成页面给他。我们判断一下如果没有新帖子的话,直接告诉他304 Not Modified就可以了,在没有使用HTTP Cache之前的版面Action代码:

Ruby代码  

  1. def board
  2. @topics = @forum.topics.paginate...
  3. @announcements = (params[:page] || 1).to_i == 1 ? Topic.find :all, :conditions => ...
  4. render :action => ‘show‘
  5. end

添加HTTP Cache以后,代码如下:

Ruby代码  

  1. def board
  2. @topics = @forum.topics.paginate...
  3. if logged_in? || stale?(:last_modified => @topics[0].last_post.created_at, :etag => @topics.collect{|t| {t.id => t.posts_count}}.hash)
  4. @announcements = (params[:page] || 1).to_i == 1 ? Topic.find :all, :conditions...
  5. render :action => ‘show‘
  6. end
  7. end

对于登录用户,不使用HTTP Cache,这是因为登录用户需要实时接收站内短信通知和订阅通知,因此我们只能对匿名用户使用HTTP Cache,然后我们使用当前所有帖子id和回帖数构造hash作Etag,这样只要当前分页列表页面有任何帖子发生改变或者有了新回帖,就更新页面,否则就不必重新生成页面。

论坛帖子页面实际上也可以使用HTTP Cache,只不过Etag的hash算法稍微复杂一些,需要保证帖子的任何改动都要引起hash值的改变,示例代码如下:

Ruby代码  

  1. def show
  2. @topic = Topic.find params[:id]
  3. user_session.update_.......  if logged_in?
  4. Topic.increment_counter(...) if ......
  5. @posts = @topic.post_by_page params[:page]
  6. posts_hash = @posts.collect{|p| {p.id => p.modified_at}}.hash
  7. topic_hash = @topic.forum_id + @topic.sys_tag_id.to_i + @topic.title.hash + @topic.status_flag.hash
  8. ad_hash = ...  (广告的hash算法,略)
  9. if logged_in? || stale?(:etag => [posts_hash, topic_hash, ad_hash])
  10. render
  11. end
  12. end

要分别根据主题贴,该分页的所有回帖和帖子页面的广告内容进行hash,计算出来一个唯一的Etag值,保证任何改动都会生成新的Etag,这样就搞定了,是不是很简单!这种帖子的缓存非常有效,可以避免Rails去render页面和下载页面,极大的减轻了服务器负载和带宽。

再举一个需求比较特殊的例子:对于知识库搜索相关文章的推荐页面,比方说:http://www.iteye.com/wiki/topic/462476,也就是本文的相关文章推荐内容,我们并不希望用户和爬虫每次访问这个页面都实际执行一遍全文检索,然后构造页面内容,在一个相对不长的时间范围内,这篇文章的相关推荐文章改变的概率不大,因此我们希望比方说5天之内,用户重复访问该页面,就直接返回304 Not Modified,那么Rails没有直接的设施给我们使用,需要我们稍微了解一些Rails的机制,自己编写,代码示例如下:

Ruby代码  

  1. def topic
  2. @topic = Topic.find(params[:id])
  3. unless logged_in?
  4. if request.not_modified?(5.days.ago)
  5. head :not_modified
  6. else
  7. response.last_modified = Time.now
  8. end
  9. end
  10. end

每次用户请求,我们判断用户是否5天之内访问过该页面,如果访问过,直接返回304 Not Modified,如果没有访问过,或者上次访问已经超过了5天,那么设置最近修改时间为当前时间,然后生成页面给用户。是不是很简单?

在给JavaEye网站所有的RSS订阅输出添加了HTTP Cache以后,通过一天的观察发现,超过一半的RSS订阅请求已经被缓存了,直接返回304 Not Modified,所以效果非常明显,由于JavaEye网站每天RSS订阅的动态请求就超过了10万次,因此添加HTTP Cache可以减轻不少服务器的负担和带宽消耗。除此之外,新闻文章页面,整个论坛频道,知识库相关推荐文章页面都可以添加HTTP Cache,粗粗计算下来,JavaEye这些页面统统使用HTTP Cache以后,网站整体性能至少可以提高10%。

时间: 2024-08-03 19:41:39

基于资源的HTTP Cache的实现介绍的相关文章

Linux 内核的文件 Cache 管理机制介绍

Linux 内核的文件 Cache 管理机制介绍 文件 Cache 管理是 Linux 内核中一个很重要并且较难理解的组成部分.本文详细介绍了 Linux 内核中文件 Cache 管理的各个方面,希望能够对开发者理解相关代码有所帮助. http://www.ibm.com/developerworks/cn/linux/l-cache/ http://www.cnblogs.com/MYSQLZOUQI/p/4857437.html 1 前言 自从诞生以来,Linux 就被不断完善和普及,目前它

基于Socket的UDP和TCP编程介绍

一.概述 TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议. TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的字节流,TCP套接口是字节流套接口(streamsocket)的一种. UDP:用户数据报协议.UDP是一种无连接协议.UDP套接口是数据报套接口(datagram socket)的一种. 二.TCP和UDP介绍 1)基本TCP客户—服务器程序设计基本框架 说明:(三路握手)         1.客户端发

基于 Socket 的 UDP 和 TCP 编程介绍

基于 Socket 的 UDP 和 TCP 编程介绍 一.概述 TCP(传输控制协议)和UDP(用户数据报协议是网络体系结TCP/IP模型中传输层一层中的两个不同的通信协议. TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的字节流,TCP套接口是字节流套接口(stream socket)的一种. UDP:用户数据报协议.UDP是一种无连接协议.UDP套接口是数据报套接口(datagram socket)的一种. 二.TCP和UDP介绍 1)基本TCP客户-服务器程序设计基本

基于角色与基于资源的权限访问控制

基于角色的权限访问控制RBAC(role-based access control)是以角色为中心进行的访问控制,也就是判断主体subject是那个角色的方式进行权限访问控制,是粗粒度的 基于资源的权限访问控制RBAC(resource-based access control)是以资源为中心进行的访问控制,只需要为角色添加权限就可以 区别: 由于基于角色的权限访问控制的角色与权限往往是多对多的关系(比如admin角色可以所有CURD的权限,部门经理角色有Retrieve权限,这就是多对多关系了

理解DDoS防护本质:基于资源较量和规则过滤的智能化系统

欢迎访问网易云社区,了解更多网易技术产品运营经验. 随着互联网生态逐渐形成,DDoS防护已经成为互联网企业的刚需要求,网易云安全(易盾)工程师根据DDoS的方方面面,全面总结DDoS的攻防对抗. 1.什么是DDoS DDoS全称Distributed Denial of Service,中文意思为"分布式拒绝服务",就是利用大量合法的分布式服务器对目标发送请求,从而导致正常合法用户无法获得服务.通俗点讲就是利用网络节点资源如:IDC服务器.个人PC.手机.智能设备.打印机.摄像头等对目

Linux 内核的文件 Cache 管理机制介绍-ibm

https://www.ibm.com/developerworks/cn/linux/l-cache/ 1 前言 自从诞生以来,Linux 就被不断完善和普及,目前它已经成为主流通用操作系统之一,使用得非常广泛,它与 Windows.UNIX 一起占据了操作系统领域几乎所有的市场份额.特别是在高性能计算领域,Linux 已经成为一个占主导地位的操作系统,在2005年6月全球TOP500 计算机中,有 301 台部署的是 Linux 操作系统.因此,研究和使用 Linux 已经成为开发者的不可回

python基于函数替换的热更新原理介绍

热更新即在不重启进程或者不离开Python interpreter的情况下使得被编辑之后的python源码能够直接生效并按照预期被执行新代码.平常开发中,热更能极大提高程序开发和调试的效率,在修复线上bug中更是扮演重要的角色.但是要想实现一个理想可靠的热更模块又非常的困难. 1.基于reload reload作为python官方提供的module更新方式,有一定作用,但是很大程度上并不能满足热更的需求. 先来看一下下面的问题: >>> import sys, math >>

基于资源名的MVC权限控制

在程序复杂程度不断上升的过程中,无可避免需要触碰到权限控制,而权限控制又与业务逻辑紧紧相关,市场上出现了大量的权限控制产品,而程序的开发,讲究去繁化简的抽象,在我的开发过程中,逐渐发现程序的权限控制核心不外乎两个方面:1.资源定位:2.访问控制列表.本文主要针对资源定位进行分析,并解决一些我所遇见过的问题.而在MVC上,MVC提供给我们了非常好的访问控制扩展机制,我们能够通过这些机制更好地控制系统权限. 在我们之前的开发中,针对ASP.NET下WebForm进行开发,很多人都采用了继承Page基

基于xmpp openfire smack开发之openfire介绍和部署[1]

前言 http://blog.csdn.net/shimiso/article/details/8816558 Java领域的即时通信的解决方案可以考虑openfire+spark+smack.当然也有其他的选择. Openfire是基于Jabber协议(XMPP)实现的即时通信服务器端版本,目前建议使用3.8.1版本,这个版本是当前最新的版本,而且网上可以找到下载的源代码. 即时通信客户端可使用spark2.6.3,这个版本是目前最新的release版本,经过测试发现上一版本在视频支持,msn