`
ahuaxuan
  • 浏览: 640707 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

论缓存之第二<近与快>

阅读更多

/**
*作者:ahuaxuan
*日期:2009-03-13
*/

近水楼台先得月,向阳花木易为春--------苏麟

缓存的作用在第一论http://www.iteye.com/topic/345693中已有部分阐述,下面ahuaxuan和大家一起来学习一下缓存得另外一个重要的规则,近和快.

在我们打开浏览器,决定浏览某个网页之前(指人眼看到屏幕上的内容之前),一般来说浏览器有几个事情要做,首先根据url请求服务器端的html数据------,然后解析html,------下载css,和js,--------将html显示到屏幕上等等. ---------然后眼睛才能感受到,--------接着大脑才能感受到.

在这个流程中,那么怎么才能让大脑尽可能快的接受到这个信息呢,我想最快的方式是在大脑里放一份该屏幕的拷贝,下次想看这份内容的时候直接拿出大脑的拷贝就可以了.如果大脑容量有限,那我们可以考虑把这份拷贝放到眼睛里,如果眼睛也放不下,那我们可以考虑把这份拷贝放到浏览器里,从这个逻辑上看,越靠近大脑的数据越能快速的被我们接受到.


那么本文的目的其实就是为了研究如何使用大脑和眼睛来缓存数据------------------------吃惊吧,ahuaxuan瞎扯的,回到正题,上面这段调侃不是为了说明别的,而是为了说明越靠近用户的数据被用户感受到的速度就越快.也就是近与快的关系.

接着再让我们抛开缓存先不说,来说说CDN和镜像的问题,CDN的英文名字叫CDN,中文名字一般还是CDN(请换个调朗诵).呵呵,CDN中文名字是内容分布网络,简单来说就是把内容分布出去,比如放到全国几个地方,举例来说做一个图片服务,上海的用户请求某个图片服务器,那么只需要返回某个离上海最近的CDN节点上的图片,而不需要路由到北京或者云南的节点上去取数据,您要问为啥呢,因为快啊,上海的用户访问北京节点的数据显然在路由层次上,网络时间消耗上都要多出很多,这说明啥呀,还是那个理儿:近就会快啊

一般来说CDN都是放一些图片,视频,文档之类的数据,那么元数据呢,放一块儿,当然也不是,这时候可以用镜像来解决元数据的问题,于是变成了上海的用户访问上海的镜像,北京的用户访问北京的镜像.这还不是就地取材比较方便嘛.

嗯,说到这里,想必大家对近和快的关系有了一定的认识了,下面我们来看看如何把这种原理或者规则运用到缓存中去.

下面让ahuaxuan和大家先调查一下离眼睛最近的是什么,显示器(别跟我说是屏幕保护膜和键盘哈,鼠标也不行),不过这些是硬件呀,那软的呢,非浏览器莫数了.也就是说如果我们把一些可以缓存在浏览器上的数据缓存到浏览器上,那就能加快我们的页面响应速度了.也就是说我们现在找到一个地方,也许可以放一点可以缓存的数据.

下面我们要考察考察什么样的数据可以缓存在浏览器上,以及缓存在浏览器上的一些优缺点或者限制因素
什么样的数据可以缓存在浏览器上?
浏览器上无法就几种数据,html,css,js,image,等.那么接着我们来看看他们的变化特性,
html数据很多情况下是动态的,但是也有很多情况下是某个时间段内可以是静态的.
Css一般是静态的
Js一般也是静态的
Image一般也是静态的.

哟,看上去后几者基本都可以缓存在浏览器,而html是否缓存要看html中数据的特性了.那么问题来了,浏览器是依据什么设置来缓存html,或者css,或者js的呢.答曰,expires或者max-age.
Expires代表该份数据缓存到浏览器上,直到某个时间点后过期,而max-age表示缓存在浏览器上直到某个时间段之后过期.

对于静态内容:设置文件头过期时间Expires的值为“Never expire”(永不过期)
动态页面,在代码中添加cache-control,表示多少时间之后过期,如:
response.setHeader("Cache-Control", "max-age=3600");表示1个小时后过期,即在浏览器上缓存一个小时.

但是这样问题又来了,如果设置10天后过期,那我明天就要改变,css,js都变了,咋办呐,答曰,加版本号吧,这样浏览器就会重新加载新的css和js了.
但是如果是动态数据,那就没有折了,所以动态数据的max-age一般不推荐太大,否则啊,您呐,就得挨个通知您得用户按一下Ctril+F5了.

一般来说静态数据需要缓存,我们一般通过webserver(如apache,lighttpd之流),只需要配置一下即可,而动态数据是比较重要的,因为其改变的周期段,而且只能由servlet容器或者fastcgi之类的服务器返回,所以其应付大量并发的能力是有限的.那么这里理论上可能有一个瓶颈,就是如果访问的独立用户较多,那么这份动态数据还是会被请求1*用户数 = n次,那么我们可以想象,一样的请求对于我们的servlet容器或者fastcgi来说其实是多余的,我们可以想一个方法,把这些一样的请求挡在servlet容器或者fastcgi进程之前.

正如在第一说中说到的,在浏览器和servlet容器或者fastcgi进程之间,还有很大的空间可以发挥,在这一部分的缓存,ahuaxuan称之为webcache.

目前在webcache届,最流行的估计就属squid了,然后还有varnish等等.为了有一个比较直观的感受,我们来看看下面这张图呗:



从这张图上,我们可以看出,浏览器1在请求了一份数据之后,其实这份数据已经在webcache上了,浏览器再来请求2的时候,请求到了webcache这层就返回了,这样就降低了servlet container的压力了.虽然说我们在servlet容器上也是可以建page cache,但是毕竟servlet本身的并发能力有限.(如何在servlet container上使用page cache见:http://www.iteye.com/topic/128458)

而且更重要的是一般webcache的并发能力要比servlet container或者fastcgi process要高出很多 (没办法,谁叫它是专业的cache呢).所以使用webcache也能够提供更高访问量的服务.一举多得,何乐而不为呢.但是声明一下,您呐,别以为上面这种方式是标准方式,我们还有webserver,负载均衡器等等,上图只是为了便于说明本文的论点,而且互连网需求和解决方案层出不穷,切不可以胡搬乱套,还是要分析分析再分析,思考,思科再思考.

说到这里即使以前没有接触过得筒子大概也明白了web cache得作用了.下面我们再来看看如何使用web cache呢,呵呵,其实和浏览器上缓存数据得方式一样.也是通过在response header中指定expires或者max-age来实现的.(但是据ahuaxuan观察在使用squid的时候有一个要求,浏览器的请求必须满足http的语义,也就是说只有method=get的时候web cache才能缓存数据,如果是post,那么web cache认为这个是一个创建数据的请求,并不会缓存其返回结果.)

Squid,如果您要系统的学习squid,请看:
http://www.squid-cache.org/
http://blog.s135.com/book/squid/

varnish如果您想了解varnish,请看附件

补充,在有些情况下,web cache中的数据很有可能是有状态的.比如根据浏览器的locale返回不同的数据,那么虽然访问的url是一样的,但是返回的值却是不一样的,咋办呢,别担心,我们有vary,只要在response里指定vary参数为accept-language就ok.您也可以指定为cookie中的值,这就完全看您的需要了.如果您还是不明白vary的作用,请看: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44

总结:
说到这里,关于近和快的话题也基本可以结束了(这个话题再写下去就变成裹脚布了).所以一般情况下,我们可以认为有如下事实:”近==快”,但是近和快并不只是表现在人的体验上,如果换个角度,速度的感受者不是人,而是机器,那么我们也可以这么认为local cache比remote cache更靠近cpu,所以local cache的速度更快(当然他们的功能不是重叠的,各自适用的场景不一样而已,而且很多情况下他们可以配合使用,在后续的文章中将会讨论这个问题).

还是那句话:本文是ahuaxuan从自己的实践中总结出来的一些小小的心得,未参考任何文章,所以可能未必好,未必全面,未必令您满意,欢迎拍砖.


注:
按照看一送一的原则,本文还赠送以下内容:
如何在只使用tomcat的情况下,自动缓存js和css或者image等文件.
该方法分为以下3个步骤
第一步:写一个filter,可以根据路径的正则来判断该路径的请求是否需要设置max-age:
/**
 * 
 * @author ahuaxuan
 * @date 2008-12-4
 * @version $id$
 */
public class CacheFilter implements Filter{

	private static transient Log logger = LogFactory.getLog(CacheFilter.class);
	
	private Integer cacheTime = 3600 * 24;
	private List<Pattern> patternList = new ArrayList<Pattern>();
	
	private static ResourceBundle rb = ResourceBundle.getBundle("cache-pattern");
	public void destroy() {
		
	}

	public void doFilter(ServletRequest rq, ServletResponse rqs,
			FilterChain fc) throws IOException, ServletException {
		
		fc.doFilter(rq, rqs);
		if (rq instanceof HttpServletRequest && rqs instanceof HttpServletResponse) {
			HttpServletRequest request = (HttpServletRequest) rq;
			HttpServletResponse response = (HttpServletResponse) rqs;
			
			if (matchPattern(request.getRequestURI())) {
				response.setHeader("Cache-Control", "max-age=" + cacheTime);
				if (logger.isDebugEnabled()) {
					StringBuilder sb = new StringBuilder();
					sb.append(" set cache control for uri = ").append(request.getRequestURI());
					sb.append(" and the cache time is ").append(cacheTime).append(" second");
					logger.debug(sb.toString());
				}
			}
		
		} else {
			if (logger.isWarnEnabled()) {
				logger.warn("---- the request instance is not instanceof HttpServletRequest ---");
				logger.warn("---- the response instance is not instanceof HttpServletResponse ---");
			}
		}
		
	}

	public void init(FilterConfig arg0) throws ServletException {
		Enumeration<String> keys = rb.getKeys();
		while (keys.hasMoreElements()) {
			String p = keys.nextElement();
			String value = rb.getString(p);
			patternList.add(Pattern.compile(value, Pattern.CASE_INSENSITIVE));
			
			if (logger.isInfoEnabled()) {
				logger.info(">>>>>>>>>>> init the cache pattern " + value);
			}
		}
		
		if (arg0 != null) {
			String ct = arg0.getInitParameter("cache-time");
			if (!"".equals(ct) && null != ct) {
				cacheTime = new Integer(ct);
				if (logger.isInfoEnabled()) {
					logger.info(">>>>>>>>>> the cache time is " + cacheTime);
				}
			}
		}
	}
	
	private boolean matchPattern(String url) {
		for (Pattern pattern : patternList) {
			if (pattern.matcher(url).matches()) {
				return true;
			}
		}
		
		return false;
	}

	public static void main(String [] args) throws ServletException {
		CacheFilter cf = new CacheFilter();
		cf.init(null);
		System.out.println(cf.matchPattern("/css/prototype.CSS"));
	}
}

第二步:在classpath路径下创建一个cache-pattern.properties文件,内容如下:
1 = .*ext-all.js
2 = .*prototype.js
3 = .*/css/.*\\.css

在这个配置文件中,您可以根据js和css的路径来配置哪些目录,或者哪些文件需要设置max-age.

第三步:
在web.xml添加如下内容:
<filter>
		 <filter-name>cache-filter</filter-name>
		 <filter-class>com.filter.CacheFilter</filter-class>
		 <init-param>
            <param-name>cache-time</param-name>
            <param-value>86000</param-value>
        </init-param>
	</filter>

<filter-mapping>
        <filter-name>cache-filter</filter-name>
        <url-pattern>*.js</url-pattern>
    </filter-mapping>
    
    <filter-mapping>
        <filter-name>cache-filter</filter-name>
        <url-pattern>*.css</url-pattern>
</filter-mapping>


如此3步,就可以将js和css文件缓存于无形.快哉.

仓卒之间成文,再加上ahuaxuan水平有限,本文如有纰漏之处,还望各位看官您不吝指正,先谢过了.

  • 大小: 15.8 KB
分享到:
评论
10 楼 ls8023 2011-11-21  
大师 你的两篇讲述缓存已看完 ,之后的续集暂未发现 期待
9 楼 tianyazjq110 2009-12-11  
so goood
8 楼 tx_forever 2009-05-25  
ahuaxuan 写道
tx_forever 写道

LZ,
有个小小疑问:
按文中附录所述的Tomcat及Filter的设置,是否只Cache用户直接访问js,css文件的request?
比如 http://abcd/js/ext-all.js

不是,不管是谁访问,该filter都会起作用的,直接访问会起作用,页面引用也会起作用

谢谢。
7 楼 ahuaxuan 2009-05-18  
tx_forever 写道

LZ,
有个小小疑问:
按文中附录所述的Tomcat及Filter的设置,是否只Cache用户直接访问js,css文件的request?
比如 http://abcd/js/ext-all.js

不是,不管是谁访问,该filter都会起作用的,直接访问会起作用,页面引用也会起作用
6 楼 tx_forever 2009-05-18  
LZ,
有个小小疑问:
按文中附录所述的Tomcat及Filter的设置,是否只Cache用户直接访问js,css文件的request?
比如 http://abcd/js/ext-all.js
5 楼 ahuaxuan 2009-05-18  
diogin 写道

个人感受,缓存是“光谱原则”的一种表现。光谱往左,比较稠密;光谱往右,比较稀疏。如果稠密代表访问热度,那么这 20% 的宽度,可能占据了 80% 的性能开销。在程序功能领域,也同样存在类似的规律,就是 20% 的 API 在 80% 的情况下会被调用。于是针对“光谱原则”,可以采用“加速”法,比如对访问热度很大的实体,进行缓存;对使用频度很大的功能,简化成 API,等等。生活中也有很多“光谱原则”,值得挖掘和融会贯通 :-)

你说的就是20/80原则吧,其实我本来第三篇文章就是写“2/8原则和长尾理论在缓存中的应用”,但是最近换了公司,事情非常多,所以一直没有时间。抽空我一定写出来和大家讨论
4 楼 diogin 2009-05-14  
个人感受,缓存是“光谱原则”的一种表现。

光谱往左,比较稠密;光谱往右,比较稀疏。

如果稠密代表访问热度,那么这 20% 的宽度,可能占据了 80% 的性能开销。

在程序功能领域,也同样存在类似的规律,就是 20% 的 API 在 80% 的情况下会被调用。

于是针对“光谱原则”,可以采用“加速”法,比如对访问热度很大的实体,进行缓存;对使用频度很大的功能,简化成 API,等等。

生活中也有很多“光谱原则”,值得挖掘和融会贯通 :-)
3 楼 狂放不羁 2009-05-08  
呵呵,多谢ahuaxuan老哥的文章,写的不错。赞一个。
2 楼 ahuaxuan 2009-03-13  
xieye 写道


十分期待大师的续作,
焦虑。。

兄弟,别这样,我离大师还远着呢。

不过写完第二篇之后,我觉得第二篇在内容上已经是不如第一篇了,呵呵。
1 楼 xieye 2009-03-13  
ahuaxuan 写道

但是这样问题又来了,如果设置10天后过期,那我明天就要改变,css,js都变了,咋办呐,答曰,加版本号吧,这样浏览器就会重新加载新的css和js了.

替大师补充,
具体的说,就是改文件名,如filename_1.js -->   filename_2.js  --> ......

十分期待大师的续作,
焦虑。。

相关推荐

    discuz 2.2Fsp1插件加强版

    用户评分记录 版主管理记录 增加搜索功能&lt;br&gt; 用户可自选贴子排序方式&lt;br&gt; 用户级别与发贴数的关联&lt;br&gt; 用户访问记录&lt;br&gt; 选择顏色的补丁&lt;br&gt; 新主題後面會顯示New的圖片&lt;br&gt; 新短信提示&lt;br&gt; 限制游客浏览精华和置顶...

    论文《基于循环缓存和精确定时多线程数据采集》

    循环缓存仅需一个指定大小的二维缓冲区,其中第一维代表缓存队列的长度(记为B),第二维代表每次采集数据的采样点数(记为S)。通过这种方式,可以实现采集线程和处理线程之间的高效协作: - **采集线程先行**:...

    论文研究-基于预测缓存的低功耗TLB快速访问机制.pdf

    该机制采用单端口静态随机存储器(SRAM)代替传统的内容寻址存储器(CAM)结构,通过匹配搜索实现全相连TLB的快速访问,在两级TLB之间设计可配置的访问预测缓存,用于动态预测第二级TLB访问顺序,减少第二级TLB搜索...

    Android应用源码之图片异步缓存两层缓存-IT计算机-毕业设计.zip

    这个毕业设计项目——"Android应用源码之图片异步缓存两层缓存",旨在教你如何优化图片加载,提高用户体验,减少内存消耗,并且降低服务器压力。以下是这个项目中涉及的关键知识点: 1. **图片异步加载**:在...

    Android应用源码之下载网络图片 (整合多线程、内存缓存、本地文件缓存~)-IT计算机-毕业设计.zip

    2. **内存缓存**:内存缓存是一种快速但容量有限的存储方式。Android中,可以利用`LruCache`类来实现。该源码可能会根据内存大小动态调整缓存容量,并通过哈希表快速查找图片,提高加载速度。 3. **本地文件缓存**...

    毕业答辩-PHP论文格式化系统——前台的设计与实现(源代码论文).rar

    二、技术选型与实现 1. PHP技术:作为后台的主要开发语言,PHP负责处理用户的请求,包括文件上传、格式化逻辑的实现和数据存储。PHP的灵活性使得它可以与各种数据库系统无缝对接,如MySQL,用于存储用户的账号信息...

    《软件性能测试、分析与调优实践之路-第二版》ppt 课件总结

    ### 《软件性能测试、分析与调优实践之路-第二版》PPT课件总结 #### 一、软件性能测试的重要性及目标 1. **理解系统性能指标**: - **并发访问量**:评估系统在高并发环境下的承载能力。 - **平均响应时间**:...

    Android应用源码之Android 图片缓存、加载器-IT计算机-毕业设计.zip

    在Android应用开发中,图片缓存与加载器是至关重要的组成部分,尤其是在开发用户界面丰富的移动应用时。这个压缩包提供了一个Android应用源码示例,专门针对图片缓存和加载策略进行实现,非常适合毕业设计学习。以下...

    android端用于异步加载图片,内存缓存,文件缓存,图片时淡入淡出动画。-IT计算机-毕业设计.zip

    2. 将加载好的图片保存到内存缓存或者文件缓存中,以便后续快速访问。 3. 在主线程中更新UI,显示图片,同时添加淡入淡出动画,提供良好的视觉过渡效果。 内存缓存是一种提高应用性能的方式,它将常用的数据存储在...

    基于J2EE框架的个人博客系统项目毕业设计论文(源码和论文)

    第二章 系统设计 2.1. 系统分析 在整个blog进行开发之前,要确定出整个项目的整体架构,包括系统的选型、运行环境的确定及系统结构设计。下面对这进行详细介绍。 在进行软件系统开发的最初环节,一般都需要进行...

    Web应用程序中的数据缓存技术

    文章中提到的“2014第二国际企业系统会议(2014 Second International Conference on Enterprise Systems)”表明了这项研究是在一个学术会议的背景下发表的。这说明了数据缓存技术是Web应用和信息系统领域的一个...

    火车票订购系统论文

    8. **支付集成**:购票涉及在线支付,需要与第三方支付平台(如支付宝、微信支付)集成,处理支付状态的同步与异步通知。 9. **异常处理与日志**:良好的错误处理和日志记录是系统稳定运行的关键,便于定位和解决...

    论文管理系统part2

    从压缩包内的文件名"lib2"来看,这可能是一个库或模块的第二部分,通常在软件开发中,库是用来封装特定功能的代码集合,方便在不同项目中复用。在论文管理系统的上下文中,"lib2"可能是用来处理论文元数据、存储内容...

    JSP旅游网站建设设计与实现(源代码+论文)

    《JSP旅游网站建设设计与实现》是一份涵盖了JavaServer Pages(JSP)技术在旅游网站开发中的应用的详细研究。该资源包括了源代码和相关的毕业设计论文,为学习者提供了一个全面理解JSP在实际项目中的应用实例。以下...

    论程序底层优化的一些方法与技巧.ppt

    第二次优化引入了预处理宏,批量处理8个元素,利用数据局部性原理减少了缓存未命中的概率。第三次优化进一步利用汇编语言,进行更底层的指令优化,例如使用`cmpl`指令进行比较,`jge`进行跳转,以及`loop`指令控制...

    精品课程网站建设与开发论文

    9. **总结与反思**:论文的总结部分通常会对整个项目进行回顾,指出成功之处、遇到的挑战以及可能的改进空间,为其他类似项目提供经验和教训。 10. **未来趋势**:随着技术的发展,精品课程网站可能会融入AI辅助...

Global site tag (gtag.js) - Google Analytics