瀏覽器快取原理
Web快取存在於伺服器和客戶端之間。Web快取密切注視著伺服器-客戶端之間的通訊,監控請求,並且把請求輸出的內容(例如html頁面、 圖片和檔案)另存一份;然後,如果下一個請求是相同的URL,則直接使用儲存的副本,而不是再次請求源伺服器。
使用Web快取的好處是顯而易見的:
-
減少網路延遲,加快頁面開啟速度--快取比源伺服器離客戶端更近,因此,從快取請求內容比從源伺服器所用時間更少,快取的使用能夠明顯加快頁面開啟速度,達到更好的體驗。
- 降低伺服器的壓力--給網路資源設定有效期之後,使用者可以重複使用本地的快取,減少對源伺服器的請求,間接降低伺服器的壓力。同時,搜尋引擎的爬蟲機器人也能根據過期機制降低爬取的頻率,也能有效降低伺服器的壓力。
- 減少網路頻寬損耗--無論對於網站運營者或者使用者,頻寬都代表著金錢,當Web快取副本被使用時,只會產生極小的網路流量,可以有效的降低運營成本。
現在的大型網站,隨便一個頁面都是一兩百個請求,每天 pv 都是億級別,如果沒有快取,使用者體驗會急劇下降、同時伺服器壓力和網路頻寬都面臨嚴重的考驗。 快取和重用以前獲取的資源的是優化網頁效能很重要的一個方面。
缺點也是有的:
- 快取沒有清理機制--這些快取的檔案會永久性地儲存在機器上,在特定的時間內,這些檔案可能是幫了你大忙,但是時間一長,我們已經不再需要瀏覽之前的這些網頁,這些檔案就成了無效或者無用的檔案,它們儲存在使用者硬碟中只會佔用空間而沒有任何用處,如果要快取的東西非常多,那就會撐暴整個硬碟空間。
- 給開發帶來的困擾--明明修改了樣式檔案、圖片、視訊或指令碼,重新整理頁面或部署到站點之後看不到修改之後的效果。
所以在產品開發的時候我們總是想辦法避免快取產生,而在產品釋出之時又在想策略管理快取提升網頁的訪問速度。瞭解瀏覽器的快取命中原理和清除方法,對我們大有裨益。
快取的分類
在Web應用領域,Web快取大致可以分為以下幾種型別:
1.資料庫資料快取
Web應用,特別是社交網路服務型別的應用,往往關係比較複雜,資料庫表繁多,如果頻繁進行資料庫查詢,很容易導致資料庫不堪重荷。為了提供查詢的效能,會將查詢後的資料放到記憶體中進行快取,下次查詢時,直接從記憶體快取直接返回,提供響應效率。比如常用的快取方案有memcached,redis等。
2.伺服器端快取
代理伺服器快取
代理伺服器是瀏覽器和源伺服器之間的中間伺服器,瀏覽器先向這個中間伺服器發起Web請求,經過處理後(比如許可權驗證,快取匹配等),再將請求轉發到源伺服器。代理伺服器快取的運作原理跟瀏覽器的運作原理差不多,只是規模更大。可以把它理解為一個共享快取,不只為一個使用者服務,一般為大量使用者提供服務,因此在減少相應時間和頻寬使用方面很有效,同一個副本會被重用多次。常見代理伺服器快取解決方案有Squid,Nginx,Apache等。
CDN快取
CDN(Content delivery networks)快取,也叫閘道器快取、反向代理快取。CDN快取一般是由網站管理員自己部署,為了讓他們的網站更容易擴充套件並獲得更好的效能。瀏覽器先向CDN閘道器發起Web請求,閘道器伺服器後面對應著一臺或多臺負載均衡源伺服器,會根據它們的負載請求,動態將請求轉發到合適的源伺服器上。雖然這種架構負載均衡源伺服器之間的快取沒法共享,但卻擁有更好的處擴充套件性。從瀏覽器角度來看,整個CDN就是一個源伺服器,瀏覽器和伺服器之間的快取機制,在這種架構下同樣適用。
3.瀏覽器端快取
瀏覽器快取根據一套與伺服器約定的規則進行工作,在同一個會話過程中會檢查一次並確定快取的副本足夠新。如果你瀏覽過程中,比如前進或後退,訪問到同一個圖片,這些圖片可以從瀏覽器快取中調出而即時顯現。
4.Web應用層快取
應用層快取指的是從程式碼層面上,通過程式碼邏輯和快取策略,實現對資料,頁面,圖片等資源的快取,可以根據實際情況選擇將資料存在檔案系統或者記憶體中,減少資料庫查詢或者讀寫瓶頸,提高響應效率。
快取如何發揮作用
請看下面的圖:
快取控制設定欄位和原理
1.HTML Meta標籤控制快取
瀏覽器快取機制,其實主要就是HTTP協議定義的快取機制(如: Expires; Cache-control等)。但是也有非HTTP協議定義的快取機制,如使用HTML Meta 標籤,Web開發者可以在HTML頁面的<head>節點中加入<meta>標籤,程式碼如下:
<META HTTP-EQUIV="Pragma" CONTENT="no-cache"> <META HTTP-EQUIV="Expires" CONTENT="0">
上述程式碼的作用是告訴瀏覽器當前頁面不被快取,每次訪問都需要去伺服器拉取。使用上很簡單,但只有部分瀏覽器可以支援,
事實上這種禁用快取的形式用處很有限:
1. 僅有IE才能識別這段meta標籤含義,其它主流瀏覽器僅能識別“Cache-Control: no-store”的meta標籤。
2. 在IE中識別到該meta標籤含義,並不一定會在請求欄位加上Pragma,但的確會讓當前頁面每次都發新請求(僅限頁面,頁面上的資源則不受影響)。
而且所有快取代理伺服器都不支援,因為代理不解析HTML內容本身。而廣泛應用的還是 HTTP頭資訊來控制快取,下面我主要介紹HTTP協議定義的快取機制
2.HTTP頭資訊控制快取
我們先來瞅一眼http1.1協議報文首部欄位中與快取相關的欄位
1.通用首部欄位
2.請求首部欄位
3.響應首部欄位
4.實體首部欄位
http1.0 時代快取欄位詳解
在 http1.0 時代,給客戶端設定快取方式可通過兩個欄位Pragma
和Expires
來規範。雖然這兩個欄位早可拋棄,但http協議做了向下相容,所以依然可以看到。
1.Pragma
Pragma:設定頁面是否快取,為Pragma則快取,no-cache則不快取
當該欄位值為no-cache
的時候,會知會客戶端不要對該資源讀快取,即每次都得向伺服器發一次請求才行。
2.Expires
有了Pragma來禁用快取,自然也需要有個東西來啟用快取和定義快取時間,對http1.0而言,Expires就是做這件事的首部欄位。 Expires的值對應一個GMT(格林尼治時間),比如Mon, 22 Jul 2002 11:12:01 GMT
來告訴瀏覽器資源快取過期時間,如果還沒過該時間點則不發請求。
如果Pragma頭部和Expires頭部同時存在,則起作用的會是Pragma,需要注意的是,響應報文中Expires所定義的快取時間是相對伺服器上的時間而言的,其定義的是資源“失效時刻”,如果客戶端上的時間跟伺服器上的時間不一致(特別是使用者修改了自己電腦的系統時間),那快取時間可能就沒啥意義了。
http1.1時代快取欄位詳解
1.Cache-Control
針對上述的“Expires時間是相對伺服器而言,無法保證和客戶端時間統一”的問題,http1.1新增了 Cache-Control 來定義快取過期時間。注意:若報文中同時出現了 Expires 和 Cache-Control,則以 Cache-Control 為準。
也就是說優先順序從高到低分別是 Pragma -> Cache-Control -> Expires 。
Cache-Control也是一個通用首部欄位,這意味著它能分別在請求報文和響應報文中使用。在RFC中規範了 Cache-Control 的格式為:
<span
class= "hljs-string" > "Cache-Control" <span
class= "hljs-string" > ":" cache-directive< /span >< /span >
|
作為請求首部時,cache-directive 的可選值有:
Cache-Control: no-cache:這個很容易讓人產生誤解,使人誤以為是響應不被快取。實際上Cache-Control: no-cache是會被快取的,
只不過每次在向客戶端(瀏覽器)提供響應資料時,快取都要向伺服器評估快取響應的有效性。
Cache-Control: no-store:這個才是響應不被快取的意思。
作為響應首部時,cache-directive 的可選值有:
Cache-Control 允許自由組合可選值,例如:
<span
class= "hljs-keyword" >Cache-Control:
<span class= "hljs-keyword" >max-age=<span
class= "hljs-number" >3600,
must-revalidate< /span >< /span >< /span >
|
它意味著該資源是從原伺服器上取得的,且其快取(新鮮度)的有效時間為一小時,在後續一小時內,使用者重新訪問該資源則無須傳送請求。
當然這種組合的方式也會有些限制,比如 no-cache 就不能和 max-age、min-fresh、max-stale 一起搭配使用。
2.Last-Modified/If-Modified-Since
Last-Modified/If-Modified-Since要配合Cache-Control使用。
(1) Last-Modified:標示這個響應資源的最後修改時間。web伺服器在響應請求時,告訴瀏覽器資源的最後修改時間。
(2) If-Modified-Since:當資源過期時(使用Cache-Control標識的max-age),發現資源具有Last-Modified宣告,則再次向web伺服器請求時帶上頭 If-Modified-Since,表示請求時間。web伺服器收到請求後發現有頭If-Modified-Since 則與被請求資源的最後修改時間進行比對。若最後修改時間較新,說明資源又被改動過,則響應整片資源內容(寫在響應訊息包體內),HTTP 200;若最後修改時間較舊,說明資源無新修改,則響應HTTP 304 (無需包體,節省瀏覽),告知瀏覽器繼續使用所儲存的cache。
3.Etag/If-None-Match
Etag/If-None-Match也要配合Cache-Control使用。
(1) Etag:web伺服器響應請求時,告訴瀏覽器當前資源在伺服器的唯一標識(生成規則由伺服器覺得)。Apache中,ETag的值,預設是對檔案的索引節(INode),大小(Size)和最後修改時間(MTime)進行Hash後得到的。
(2)If-None-Match:當資源過期時(使用Cache-Control標識的max-age),發現資源具有Etage宣告,則再次向web伺服器請求時帶上頭If-None-Match (Etag的值)。web伺服器收到請求後發現有頭If-None-Match 則與被請求資源的相應校驗串進行比對,決定返回200或304。
4.既生Last-Modified何生Etag?
你可能會覺得使用Last-Modified已經足以讓瀏覽器知道本地的快取副本是否足夠新,為什麼還需要Etag(實體標識)呢?HTTP1.1中Etag的出現主要是為了解決幾個Last-Modified比較難解決的問題:
(1) Last-Modified標註的最後修改只能精確到秒級,如果某些檔案在1秒鐘以內,被修改多次的話,它將不能準確標註檔案的修改時間
(2)如果某些檔案會被定期生成,當有時內容並沒有任何變化,但Last-Modified卻改變了,導致檔案沒法使用快取
(3)有可能存在伺服器沒有準確獲取檔案修改時間,或者與代理伺服器時間不一致等情形
Etag是伺服器自動生成或者由開發者生成的對應資源在伺服器端的唯一識別符號,能夠更加準確的控制快取。Last-Modified與ETag是可以一起使用的,伺服器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最後才決定是否返回304。
5.不太常用的兩個http欄位If-Unmodified-Since/If-Match
(1)If-Unmodified-Since: Last-Modified-value
告訴伺服器,若Last-Modified沒有匹配上(資源在服務端的最後更新時間改變了),則應當返回412(Precondition Failed) 狀態碼給客戶端。
當遇到下面情況時,If-Unmodified-Since 欄位會被忽略:
1. Last-Modified值對上了(資源在服務端沒有新的修改); 2. 服務端需返回2XX和412之外的狀態碼; 3. 傳來的指定日期不合法
(2)If-Match: ETag-value
告訴伺服器如果沒有匹配到ETag,或者收到了“*”值而當前並沒有該資源實體,則應當返回412(Precondition Failed) 狀態碼給客戶端。否則伺服器直接忽略該欄位。
瀏覽器快取流程圖
小結一下,瀏覽器第一次請求
瀏覽器第二次請求
如何配置
1)通過程式碼的方式,在web伺服器返回的響應中新增Expires和Cache-Control Header;
比如在JavaWeb裡面,我們可以使用類似下面的程式碼設定強快取:
java.util.Date date = new java.util.Date(); response.setDateHeader("Expires",date.getTime()+20000); //Expires:過時期限值 response.setHeader("Cache-Control", "public"); //Cache-Control來控制頁面的快取與否,public:瀏覽器和快取伺服器都可以快取頁面資訊; response.setHeader("Pragma", "Pragma"); //Pragma:設定頁面是否快取,為Pragma則快取,no-cache則不快取
還可以通過類似下面的java程式碼設定不啟用強快取:
response.setHeader( "Pragma", "no-cache" );
response.setDateHeader("Expires", 0); response.addHeader( "Cache-Control", "no-cache" );//瀏覽器和快取伺服器都不應該快取頁面資訊
2)通過配置web伺服器的方式,讓web伺服器在響應資源的時候統一新增Expires和Cache-Control Header。
tomcat提供了一個ExpiresFilter專門用來配置強快取
nginx和apache作為專業的web伺服器,都有專門的配置檔案,可以配置expires和cache-control,
Nginx伺服器的配置方法為:
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ { #過期時間為30天,#圖片檔案不怎麼更新,過期可以設大一點, expires 30d; } location ~ .*\.(js|css)$ { #如果頻繁更新,則可以設定得小一點。 expires 1d;
add_header Cache-Control max-age=86400;
etag on;
}
Apache伺服器的配置方法為:
<Location ~ "\.(js|css|png|jpg|gif|bmp|html)$"> ExpiresActive On ExpiresDefault "access plus 1 hours" Header set Cache-Control max-age=3600 Header unset Pragma </Location> <Location ~ "\.(do|jsp|aspx|asp|php|json|action|ashx|axd|cgi)$"> Header set Cache-Control no-cache,no-store,max-age=0 Header unset Expires
Etag INode Mtime Size </Location>
3.快取配置的一些注意事項
1.只有get請求會被快取,post請求不會
2.Etag 在資源分佈在多臺機器上時,對於同一個資源,不同伺服器生成的Etag可能不相同,此時就會導致304協議快取失效,客戶端還是直接從server取資源。可以自己修改伺服器端etag的生成方式,根據資源內容生成同樣的etag。需要注意的是分散式系統裡多臺機器間檔案的last-modified必須保持一致,以免負載均衡到不同機器導致比對失敗,Yahoo建議分散式系統儘量關閉掉Etag(每臺機器生成的etag都會不一樣,因為除了 last-modified、文件節點也很難保持一致)
使用者行為與快取
快取的清除方法
由於在開發的時候不會專門去配置強快取,而瀏覽器又預設會快取圖片,css和js等靜態資源,所以開發環境下經常會因為強快取導致資源沒有及時更新而看不到最新的效果,解決這個問題的方法有很多,常用的有以下幾種:
1)直接ctrl+f5,這個辦法能解決頁面直接引用的資源更新的問題;
2)使用ctrl+shift+delete清除快取;
3)如果用的是chrome,可以F12在network那裡把快取給禁掉(這是個非常有效的方法):
4)在開發階段,給資源加上一個動態的引數,如css/index.css?v=0.0001,由於每次資源的修改都要更新引用的位置,同時修改引數的值,所以操作起來不是很方便,一般使用前端的構建工具來修改這個引數或 在動態頁面比如jsp裡開發就可以用伺服器變數來解決(v=${sysRnd});
1.原生寫法
function addVersion(asset){ asset.forEach(function(item,index){ if(item.indexOf('.js') != -1){ document.write('<script src="'+item+'?v='+ (new Date().getTime()) +'"><\/script>'); }else if(item.indexOf('.css') != -1){ document.write('<link rel="stylesheet" href="'+item+'?v='+(new Date().getTime())+'">'); } }); }
2.採用gulp外掛
(1)gulp-rev-append
(2)gulp-rev和gulp-rev-collector也能實現同樣的功能
// 修改html和css檔案,給靜態檔案打戳 gulp.task('stamp', function(){ gulp.src(['rev/*.json', dest.css + "**/*.css"]). pipe(revCollector({ replaceReved: true })). // 修改為 ?v=stamp 形式 pipe(replace(/\-([0-9a-z]{8,})\.(png|jpg|gif|ico)/g, function(a, b, c){ return '.' + c + '?v=' + b; })). pipe(gulp.dest(dest.css)); gulp.src(['rev/*.json', src.html]). pipe(revCollector({ replaceReved: true })). // 修改為 ?v=stamp 形式 pipe(replace(/\-([0-9a-z\-]{8,})\.(css|js)/g, function(a, b, c){ return '.' + c + '?v=' + b; })). pipe(gulp.dest(dest.html)); });
5)如果資源引用的頁面,被嵌入到了一個iframe裡面,可以在iframe的區域右鍵單擊重新載入該頁面,以chrome為例:
6)如果快取問題出現在ajax請求中,最有效的解決辦法就是ajax的請求地址追加隨機數;
7)還有一種情況就是動態設定iframe的src時,有可能也會因為快取問題,導致看不到最新的效果,這時候在要設定的src後面新增隨機數也能解決問題;
8)如果你用的是grunt和gulp這種前端工具開發,通過它們的外掛比如grunt-contrib-connect或gulp-connect來啟動一個靜態伺服器,則完全不用擔心開發階段的資源更新問題,因為在這個靜態伺服器下的所有資源返回的respone header中,cache-control始終被設定為不快取:
與之相關的--本地儲存和離線儲存
localStorage/sessionStorage
localStorage.setItem("name", "Robert");
localStorage.getItem("name");
這樣的存取最多可以儲存5M的資料(localStorge在Android webview中不支援擴容,只有在pc瀏覽器中超限才會彈出擴容提示 ),給你更多選擇的空間。但是由於本地儲存是基於字串的儲存,儲存一串沒有結構的字串並不是一個理想的選擇。因此,我們可以利用瀏覽器中原生的JSON支援來將JavaScript物件轉化成字串,從而儲存到本地資料中,在讀取的時候也可以將其轉換回JavaScript物件。
快取和使用圖片的方法
//在本地儲存中儲存圖片 var storageFiles = JSON.parse(localStorage.getItem("storageFiles")) || {}, elephant = document.getElementById("elephant"), storageFilesDate = storageFiles.date, date = new Date(), todaysDate = (date.getMonth() + 1).toString() + date.getDate().toString();
// 檢查資料,如果不存在或者資料過期,則建立一個本地儲存 if (typeof storageFilesDate === "undefined" || storageFilesDate < todaysDate) { // 圖片載入完成後執行 elephant.addEventListener("load", function () { var imgCanvas = document.createElement("canvas"), imgContext = imgCanvas.getContext("2d"); // 確保canvas尺寸和圖片一致 imgCanvas.width = elephant.width; imgCanvas.height = elephant.height; // 在canvas中繪製圖片 imgContext.drawImage(elephant, 0, 0, elephant.width, elephant.height); // 將圖片儲存為Data URI storageFiles.elephant = imgCanvas.toDataURL("image/png"); storageFiles.date = todaysDate; // 將JSON儲存到本地儲存中 try { localStorage.setItem("storageFiles", JSON.stringify(storageFiles)); } catch (e) { console.log("Storage failed: " + e); } }, false); // 設定圖片 elephant.setAttribute("src", "elephant.png"); } else { // Use image from localStorage elephant.setAttribute("src", storageFiles.elephant); }
sessionStorage的資料只儲存到特定的會話中,不屬於持久化的儲存,所以關閉瀏覽器會清除資料。和localstorage具有相同的方法。
離線儲存
設定方法
1. 在HTML5的html標籤中新增一個 manifest="XXX.appcache" 屬性宣告
<!DOCTYPE html> <html manifest="list.appcache">
2.XXX.appcache檔案中定義需要快取的檔案清單(裡面的資原始檔的路徑是相對於manifest的路徑而言的)
CACHE MANIFEST # VERSION 0.3 # 直接快取的檔案 CACHE: # 需要線上訪問的檔案 NETWORK: # 替代方案 FALLBACK:
CACHE MANIFEST --(必須) 此標題下列出的檔案將在首次下載後進行快取
#V1.0.2
../addDevice.html
../static/css/reset.css
../static/js/addDevice.js
../static/img/ms1.png
../static/img/clean-face.jpg
NETWORK----(可選)
(1)萬用字元'*'表示不在CACHE MANIFEST清單裡的檔案,每次都要重新請求
*
(2)或者指定特定檔案,比如login.asp不被離線儲存,每次都要重新發起請求
login.asp
FALLBACK----(可選) 斷網時訪問指定路徑時的替換檔案
如斷網時訪問/html5/ 目錄下的所有資原始檔,則用 "offline.html" 替代
/html5/ /offline.html
更新原理
更新了manifest檔案,瀏覽器會自動的重新下載新的manifest檔案並把manifest快取列表中的所有檔案重新請求一次(第二次重新整理替換本地快取為最新快取),而不是單獨請求某個特定修改過的資原始檔,因為manifest是不知道哪個檔案被修改過了的。
對於全域性更新不必要擔心,因為沒有更新過的資原始檔,請求依舊是304響應,只有真正更新過的資原始檔才是伺服器返回的才是200.
所以控制離線儲存的更新,需要2個步驟,一是更新資原始檔,二是更新manifest檔案,只要修改manifest檔案隨意一處,瀏覽器就會感知manifest檔案更新,而我們的資原始檔名稱通常是固定的,需要更新manifest檔案怎麼操作呢?一個比較好的方式是更新以# 開頭的版本號註釋,告訴瀏覽器這個manifest檔案被更新過。
manifest資源是滯後靜默更新的
第二次重新整理介面之後,才能看到更新後的效果
/*code1,簡單粗暴的*/
applicationCache.onupdateready = function(){
applicationCache.swapCache(); //強制替換快取
location.reload(); //重新載入頁面
};
/*code2,快取公用方法*/
// var EventUtil = {
// addHandler: function(element, type, handler) {
// if (element.addEventListener) {
// element.addEventListener(type, handler, false);
// } else if (element.attachEvent) {
// element.attachEvent("on" + type, handler);
// } else {
// element["on" + type] = handler;
// }
// }
// };
// EventUtil.addHandler(applicationCache, "updateready", function() { //快取更新並已下載,要在下次進入頁面生效
// applicationCache.update(); //檢查快取manifest檔案是否更新,ps:頁面載入預設檢查一次。
// applicationCache.swapCache(); //交換到新的快取項中,交換了要下次進入頁面才生效
// location.reload(); //重新載入頁面
// });
applicationCache 提供瞭如下的事件:
Event handler Event handler event type
onchecking checking
onerror error
onnoupdate noupdate
ondownloading downloading
onprogress progress
onupdateready updateready
oncached cached
onobsolete obsolete
提供瞭如下的API:
void update();
// 更新, 但是這個方法適用於一些長期開啟的頁面,而不會有重新整理動作,比如郵件系統,所以這個就比較適合做自動更新下載
void abort();
// 取消
void swapCache();
// 替換快取內容 ,對於manifest檔案的改變,通常是下一次的重新整理才會觸發下載更新,第二次重新整理才會切換使用新的快取檔案,通過這個方法,可以強制將快取替換
注意事項
站點中的其他頁面即使沒有設定manifest屬性,請求的資源如果在快取中也從快取中訪問
系統會自動快取引用清單檔案的 HTML 檔案
如果manifest檔案,或者內部列舉的某一個檔案不能正常下載,整個更新過程將視為失敗,瀏覽器繼續全部使用老的快取
在manifest中使用的相對路徑,相對參照物為manifest檔案
站點離線儲存的容量限制是5M
manifest檔案中CACHE則與NETWORK,FALLBACK的位置順序沒有關係,如果是隱式宣告需要在最前面
manifest中必須一一宣告檔名,這很令人頭痛
引用manifest的html必須與manifest檔案同源,在同一個域下
除此之外,還增加了兩大問題:
(1)PV UV的計算難題,由於當前頁面被強制加入manifest,那麼PV 和UV的統計,成了一個難題,因為請求不再是傳送到伺服器;
(2)快取對於某個使用manifest的檔案,其帶有的引數可能是隨機性的統計引數,如sid=123sss, sid=234fff ,尤其是比如商品詳情的id欄位等,這樣每個頁面都自動加入到manifest中,將會帶來很大的儲存開銷,而且是毫無意義的;
所以伴隨而來的,是如何在現有的體系架構下進行資料統計的難題,
對於第一個問題 常規方案是進入離線儲存頁面後自動發出ajax請求,以告知伺服器統計PV UV;
對於第二個問題,是將GET請求方式改成POST方式。
離線儲存的適用場景
1.單頁應用
2.對實時性要求不高的業務
3.webApp