在我们打开浏览器
,
决定浏览某个网页之前
(
指人眼看到屏幕上的内容之前
),
一般来说浏览器有几个事情要做
,
首先根据
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/
补充
,
在有些情况下
,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
的速度更快
(
当然他们的功能不是重叠的
,
各自适用的场景不一样而已
,
而且很多情况下他们可以配合使用
,
在后续的文章中将会讨论这个问题
).
如何在只使用
tomcat
的情况下
,
自动缓存
js
和
css
或者
image
等文件
.
第一步
:
写一个
filter,
可以根据路径的正则来判断该路径的请求是否需要设置
max-age:
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")); } }