【GISER&&前端優化】前端緩存的幾種主流選擇

  這周遇到了一個新需求,產品反饋地圖瓦片服務的圖片資源沒有Http緩存,每次移動地圖範圍都會向後臺發處請求/響應數據,影響了客戶端的地圖加載體驗。所以需要增加這樣一種緩存:1)針對同一個請求資源地址URL,首次加載需要緩存數據,後續加載直接讀取緩存;2)後臺數據發生更新時,需要實時更新緩存;
  在完成這個需求之前,我藉機補習了一下前端的緩存體系:

一  HTTP緩存
  提起前端緩存,首先第一反應就是瀏覽器自帶的緩存機制,通過在Http報文頭部中設置一些屬性字段,告知瀏覽器對本次請求響應的資源進行緩存,之後對於相同URL的資源請求則直接從緩存中讀取。這個思路沒錯,那具體應該如何實現呢?這就需要我們對這些屬性進行簡單介紹一下:

  1  強緩存
   首次請求資源A設置緩存,再次請求資源A時直接查找緩存,如果緩存命中,則不發送新的請求;否則發送新請求;
   控制強緩存的主要屬性字段:
    【Expire】:HTTP1.0標準下的字段,指定一個日期或時間,在時間過期前執行上述邏輯,時間過期後重新執行
    【Cache-Control】:
      field description
      private 僅客戶端緩存
      public 客戶端和代理服務器緩存
      max-age=xxx 緩存內容將在xxx秒後失效(相對請求時間)
      s-max-age 僅適用於public共享緩存
      no-cache 需要使用協商緩存來驗證緩存(會發送新的請求)
      no-store 所有內容都不緩存
      must-revalidate

  2 協商緩存
  顧名思義,由服務端協助客戶端實現緩存機制:即在資源B已緩存的前提下,再次請求資源B時,會繼續想服務器端發送請求,通過服務器發送請求,獲取資源B是否失效的信息,如果未失效,返回未失效Flag告知前端,從而在緩存中獲取資源B,如果失效,則返回新的數據和報文緩存頭設置;
常用的協商緩存包括:
  【Cache-Control的must-revalidate】:標識每次必須向服務端請求驗證資源

  【Etag】-【if-none-match】:資源的唯一標識,即在第一次請求資源C時,會計算一個能夠唯一標識當前資源C的tag值,並放在response的Etag屬性字段中,當資源C緩存在前端後,後續的每一次請求資源C都會將tag值設置在請求request的if-none-match中,服務端根據tag值進行判斷當前請求資源C是否發生變化,如變化這表示緩存未命中,返回新的資源C,否則返回緩存命中標識,直接返回304獲取資源C在前端的緩存。

  【last-modified】-【if-modified-since】:同上Etag,只不過此處一般以時間戳作爲判斷依據,服務端則需要獲取資源C的文件最後修改時間(這個在JAVA中File提供了getLastModified方法),並將時間戳賦值給response中的last-modified屬性字段,後續的每一次請求會將時間戳放在if-modified-since中,與後臺的新時間戳對比,如果時間戳不同,則表示緩存未命中,返回新的資源C,否則返回緩存命中標識,前端獲取緩存值;

  3 實現
  OK,梳理完上述的Http緩存機制後,我們很容易發現如果要滿足需求(即客戶端緩存數據,且能夠在數據發生變化後請求返回新的數據,更新緩存),應當選擇協商緩存。
策略選擇好之後,實現還是比較簡單的,我們這裏以時間戳爲例:
  3.1)  後臺在返回數據時增加時間戳判斷:

Public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
//......
  String filePath
= ""; //   File fileC = new File(filePath);   long lastNew = fileC.lastModified();   String lastNewStr = Long.toString(lastNew); //獲取當前資源的最後修改時間   String lastOldStr = request.getHeader("if-modified-since"); //獲取當前資源緩存的最後修改時間   if(lastNewStr.equalsIgnoreCase(lastOldStr)){     Response.setStatus(304); //返回304 not modified   }else{   //獲取新的數據   } // ...... }

  3.2) 驗證效果
  效果比較尷尬,因爲在實現了這類緩存後,請求響應資源的效率提高並不明顯,雖然實現了緩存,但是每次資源校驗依然要經過一次完整的請求響應過程,其中的連接開銷應該是耗時的主要部分,而非數據的傳輸。所以,只能臨時更改策略,採用期限強緩存,後臺如果更新數據,則需要客戶端手動刷新緩存。

  實現方法如下,採用Cache-Control的max-age,以一天時間爲緩存期限:

Public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
  //…
  response.setHeader("Cache-Control", "max-age=86400");
  //…
}

 

二  前端數據庫緩存
  在完成這個任務後,我突然想起來之前在完成矢量地圖渲染時,爲了提高前端中文字體加載效率,曾經使用過前端數據庫indexDB作爲緩存技術,這個和Http緩存又有什麼不同呢?一般來說,前端數據庫主要有一下幾種:
  1 cookie
  常用場景:用戶個人信息保存(用戶名/密碼)
  存在問題:
    容量:4KB
    數據安全問題

  2 localStorage/sessionStorage【鍵值對】:本地瀏覽器數據庫,根據瀏覽器不同,緩存容量不同,一般5MB左右,適合存儲一些常用結構簡單的數據;
    localStorage.setItem("key", "value")
    localStorage.getItem("key")
    localStorage.removeItem("key")
    localStorage.clear()
  優勢場景:
    兼容性好,易操作
  缺點:
    容量小,每個域名分配3-5M空間

  3 indexedDB【結構化對象】:本地數據庫存儲,使用索引高效檢索,異步處理;
  優勢場景:
    a 異步操作,可大量讀寫數據而不阻塞瀏覽器主線程
    b 支持事務
    c 存儲空間大(250MB->更多)
    d 支持二進制存儲ArrayBuffer和Blob
  缺點:
    操作比較繁瑣複雜,不如localStorage方便
  實現:
    var databaseName = "test"
    var version = "20191121"
    var request = window.indexedDB.open(databaseName, version) //創建一個indexedDB數據庫
  具體操作略微有些複雜,需要熟悉相關API接口,此處就不作詳細介紹了,具體可以參照阮一峯的教程;

三  比較
  比較Http緩存和前端數據庫緩存,暫時沒有發現這些緩存有什麼明顯的差異,只是針對應用場景不同而已:
  1)Http緩存一般能夠緩存一些前端請求的一些公共資源,減少一些不必要的請求響應開銷,提高web端的體驗
  2)而前端數據庫緩存則能夠緩存一些高頻常用的業務數據,提高業務請求的數據返回速度,比如高德地圖和百度地圖: 【高德地圖】:打開Chrome控制檯的Application選項卡,可以查看IndexedDB中的數據,其中包含了road/region等矢量瓦片數據;  【百度地圖】:打開IndexedDB空空如也,但是在LocalStorage中發現了一些POI搜索記錄;並且百度地圖的矢量數據服務利用了Cache-Control屬性強緩存了矢量數據,從而減小實時的數據請求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章