?
在我們打開瀏覽器
,
決定瀏覽某個網頁之前
(
指人眼看到屏幕上的內容之前
),
一般來說瀏覽器有幾個事情要做
,
首先根據
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")); } }
?
?
?
?
?
?
?
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
