【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属性强缓存了矢量数据,从而减小实时的数据请求。

posted @ 2020-03-29 19:02  ESCAGE  阅读(426)  评论(0编辑  收藏  举报