`
ahuaxuan
  • 浏览: 633400 次
  • 性别: 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; 限制游客浏览精华和置顶...

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

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

    香农编码实验报告.doc

    湖 南 大 学 信息科学与工程学院 实 验 报 告 "实验名称 "香农编码 " "课程名称 "信息论与编码 " " " " 1、实验目的 (1)进一步熟悉Shannon编码算法; (2)掌握C语言程序设计和调试过程中数值的进制转换、数值与...

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

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

    存储篇 1:浏览器缓存机制介绍与缓存策略剖析(1).md

    至少当我确定自己的研发方向、并接到第一个性能优化任务时,我做的第一件事是向搜索引擎求助,第二件事是买书,然后开始了摸着石头过河,前后花费了大量的时间和精力。我深感性能优化实在是前端知识树中特别的一环...

    《基于Python中PyQt5实现简易浏览器的设计与实现.docx》万字、已降重、毕业论文、本科

    第二章 PyQt5与Python基础知识 2.1 PyQt5框架介绍 2.2 Python基础知识回顾 第三章 简易浏览器设计与界面实现 3.1 系统功能需求分析 3.2 界面设计与布局 3.3 界面逻辑与交互 第四章 浏览器核心功能实现 4.1 URL解析与...

    论文研究-软件定义网络中利用IMKVS结合NFV的分布式网络负载均衡策略.pdf

    第二阶段是基于IMKVS的专业化缓存,可以实现通信管理和数据复制。仿真结果表明,相比于一致性哈希,缓存服务器上的负载可改善24%,网络上的负载可改善7%,策略能够使资源利用更合理,获得更好的用户体验。

    华西网源码

    第二个分区为 icon_2.png …… 第N个分区为 icon_N.png 4)头部配色修改(包括频道和对于的分区:PS:这个默认机制是分区实现的) 需要特别说明,这次的调整,头部配色的修改方法也有很大的变换,由以前的两...

    可伸缩服务架构:框架与中间件 第二部分

    框架与中间件》以高可用服务架构为主题,侧重于讲解高可用架构设计的核心要点:可伸缩和可扩展,从应用层、数据库、缓存、消息队列、大数据查询系统、分布式定时任务调度系统、微服务等层面详细讲解如何设计可伸缩、...

    工程硕士学位论文 基于Android+HTML5的移动Web项目高效开发探究

    第二章 多窗口类浏览器设计 11 2.1 多窗口类浏览器需求分析 11 2.1.1 Activity简介 11 2.1.2 Fragment简介 11 2.1.3 多窗口类浏览器需求 12 2.2 多窗口浏览器模式的实现机制 12 2.2.1安卓移动端多窗口浏览器框架 12 ...

    彩蛋篇:CDN 的缓存与回源机制解析(1).md

    至少当我确定自己的研发方向、并接到第一个性能优化任务时,我做的第一件事是向搜索引擎求助,第二件事是买书,然后开始了摸着石头过河,前后花费了大量的时间和精力。我深感性能优化实在是前端知识树中特别的一环...

    基于SpringCloud的绿植养护软件的设计与实现.docx

    第二章系统需求分析 2.1系统业务需求 2.1.1用户模块 2.1.2设备模块 2.1. 3设备监测模块 2.1.4设备控制模块 2.2系统架构设计 第三章软件框架与通信协议研究 3.1微信小程序 3.2 SpringBoot 微框架 3.3 SpringCloud ...

    数据库设计准则及方法论.docx

    数据库设计准则及方法论全文共5页,当前为第2页。数据库设计准则及方法论全文共5页,当前为第2页。 数据库设计准则及方法论全文共5页,当前为第2页。 数据库设计准则及方法论全文共5页,当前为第2页。 合理的使用...

    基于SSM的个人博客系统设计软件程序源码+数据库+WORD毕业设计论文文档.zip

    第二章 相关技术介绍 2 2.1 B/S 简介 2 2.2 JAVA 简介 2 2.3 JSP 简介 3 2.4 springMVC简介 3 第三章 可行性分析 4 3.1 技术可行性分析 4 3.2 经济可行性分析 4 3.3 操作可行性 4 3.4 法律可行性 4 第四章 系统设计 ...

    面向新型互联网架构的移动性管理关键技术研究(博士学位论文-胡章丰)

    第二,研究了该架构下的标识映射与移动位置管理问题;第三,针对移动终端的频繁位置更新问题,提出了一种位置更新的优化方法;第四,针对云计算系统中的虚拟机迁移问题,提出了一种新型的虚拟机跨三层网络动态迁移...

    Microsoft SQL Server 2008技术内幕:T-SQL查询(第二卷)

    第2章 集合论和谓词逻辑 2.1 自然语言表述到数学表示的转换 2.1.1 严格定义(well-Definedness) 2.1.2 相等、恒等和同一性 2.1.3 数学命名约定 2.1.4 数字 2.1.5 上下文 2.1.6 函数、参数和变量 2.1.7 指令...

    asp.net知识库

    第2章 并发操作的一致性问题 (2) Using sqlite with .NET Visual Studio 2005 中的新 DataSet 特性 MySQL 和 .Net2.0配合使用 与DotNet数据对象结合的自定义数据对象设计 (二) 数据集合与DataTable 与DotNet数据对象...

    Web开发敏捷之道-应用Rails进行敏捷Web开发-第三版.rar

    23.10 再论缓存 417 23.11 新增模板系统 421 第24章 Web 2.0 423 24.1 Prototype 423 24.2 Script.aculo.us 438 24.3 RJS模板 451 24.4 结论 456 第25章 ActionMailer 457 25.1 发送邮件 457 25.2 接收邮件 465 25.3...

    基于生态农业的电子商务微信小程序毕业设计(论文).docx

    第二章 需求采集与分析 3 2.1需求采集 3 2.2需求分析 3 第三章 开发中采用的编程思想 5 3.1AOP 面向切面编程思想 5 第四章 系统分析与设计 6 4.1程序思维导图 6 4.1.1后端逻辑导图 6 4.1.2小程序端逻辑导图 ...

Global site tag (gtag.js) - Google Analytics