【问题标题】:Incremental updates using browser cache使用浏览器缓存的增量更新
【发布时间】:2017-05-26 03:07:30
【问题描述】:

客户端(AngularJS 应用程序)从服务器获取相当大的列表。这些列表可能包含成百上千个元素,这可能意味着有几兆字节未压缩(并且一些用户(管理员)会获得更多数据)。

我不打算让客户端获得部分结果,因为排序和过滤不应该打扰服务器。

压缩效果很好(大约 10 倍),并且由于列表不经常更改,304 NOT MODIFIED 也有很大帮助。但是缺少另一个重要的优化:

由于列表的典型更改相当小(例如,修改两个元素并添加一个新元素),因此转移更改听起来是个好主意。我想知道如何正确地做到这一点。

GET/offer/123/items 之类的东西应该总是返回报价编号 123 中的所有项目,对吗?这里可以使用压缩和304,但不能增量更新。像 GET /offer/123/items?since=1495765733 这样的请求听起来像是要走的路,但是浏览器缓存没有被使用:

  • 要么什么都没有改变,答案是空的(缓存它没有意义)
  • 或发生了某些变化,客户端更新其状态,并且自 1495765733 以来不再请求更改(缓存它更没有意义)

显然,当使用“since”查询时,不会为“resource”缓存任何内容(原始查询只使用一次或根本不使用)。

所以我不能依赖浏览器缓存,只能使用localStoragesessionStorage,它们有一些缺点:

  • 它被限制在几兆字节(浏览器 HTTP 缓存可能更大,并且会自动处理)
  • 当我达到极限时,我必须实施一些替换策略
  • 浏览器缓存存储了我没有得到的已压缩数据(我必须重新压缩它们)
  • 它不适用于获得更大列表的用户(管理员),因为即使是单个列表也可能已经超出限制
  • 它在注销时被清空(客户的要求)

鉴于有 HTML 5 和 HTTP 2.0,这很不令人满意。我错过了什么?

是否可以将浏览器 HTTP 缓存与增量更新一起使用?

【问题讨论】:

  • 为什么不分两个阶段做请求呢?首先,请求新的 id,带有可选的 since 过滤器,不可缓存。然后,通过包含 id 作为查询参数,请求完整的项目列表,包括项目详细信息,作为完整的可缓存请求。
  • @ConstantinGALBENU 我看不出它有什么帮助。当我包含所有 id 时,我会获取整个列表,这是我需要避免的。当我只包含新 id 时,我会收到一个我再也不需要的请求,因为下一次会有不同的新 id。
  • 您在服务器端使用哪种语言来处理这些请求?
  • @JoeCoder Java(嵌入式 Jetty Servlet)。

标签: ajax rest http caching single-page-application


【解决方案1】:

我认为您缺少一件事:简而言之,标题。我认为您可以做的并且符合(大多数)您的要求的方法是:

  • GET/offer/123/items正常做,没什么特别的。
  • 后续的GET /offer/123/items 将与Fetched-At: 1495765733 标头一起发送,指示您的服务器何时发送初始请求。

从现在开始,可能有两种情况。

  • 要么没有找零,发304就可以了。
  • 但是,如果有更改,请返回新项目,因为之前发送的时间戳具有标头,但在您的响应中设置 Cache-Control: no-cache

这使您可以进行增量更新,并缓存初始兆字节大小的元素。

不过还有一个缺点,就是缓存只进行一次,它不会缓存更新。您说您的列表不经常更新,所以它可能已经适合您,但如果您真的想进一步推动这一点,我可以再想一件事。

收到增量更新后,您可以在后台触发另一个没有 Fetched-At 标头的请求,您的应用程序根本不会使用该标头,但只会在那里更新您的 http 缓存。它不应该像听起来那样糟糕,因为您的框架不会使用新的框架更新其数据(并可能触发重新渲染),唯一值得注意的缺点是网络和内存消耗。在移动设备上可能会出现问题,但它听起来不像是打算在移动设备上显示的应用程序。

我绝对不知道你的用例,只是把它扔在那里,但你真的确定做某种分页不起作用吗?兆字节的数据对于普通人来说听起来和处理很多;)

【讨论】:

  • 不错的答案,确实!当然,没有人愿意看到数千行,但是客户端可以过滤和排序它们,这样人们就可以得到他们可以使用的东西。分页可以工作,但客户端可以很好地做到这一点而不会产生任何流量。而且可能更快。
  • 谢谢!我明白了,只要它被考虑过,那就是一件好事,想要确定。
  • 我们是否知道浏览器不会将Cache-Control: no-cache 视为释放内存的机会?如果有人告诉我不要缓存某个东西的新版本,我也不会保留旧版本。实际上,sec14.9.1 甚至没有说不能存储响应。我想,我们也需要no-store。 +++我会全力以赴(我不必支持所有浏览器,所以它是可行的),但这需要一些时间。
  • 这是一个很好的观点,是的 no-store 可能是一个更好的选择。在收到不可缓存的响应(no-storeno-cache)后,我找不到任何关于潜在删除的信息,对我来说这两种行为都是有意义的,所以我想这可能取决于你所说的浏览器
【解决方案2】:

我会完全放弃请求/响应周期并转向推送模型。 具体来说,WebSockets

这是在提供实时代码数据表的金融交易网站上使用的标准技术。这是一个这样的生产应用程序,展示了 WebSockets 的强大功能:

https://www.poloniex.com/exchange#btc_eth

WebSocket 应用程序有两种状态:全局和用户。上面的链接将显示三个全局数据表。登录后,底部会显示另外两个用户数据表。

这不是 HTTP;您将无法将其添加到 Java Servlet 中。您需要在通过 TCP 通信的服务器上运行一个单独的进程。好消息是,有现成的成熟解决方案可用。 Lightstreamer 是一个基于 Java 的解决方案,具有非常不错的免费许可选项,包括客户端和服务器 API(并与 Angular2 集成)。他们也有一个组织良好的demo page。还有一些适配器可用于与您的数据源集成。

您可能不愿放弃现有的 servlet 方法,但从长远来看,这将不那么令人头疼,而且规模惊人。 HTTP 轮询,即使是设计良好的仅标头请求,也不能很好地适应频繁更新的大型列表。

---------- 编辑 ----------

由于列表更新不频繁,WebSockets 可能是矫枉过正。根据 cmets 在此答案上提供的更多详细信息,我建议使用基于 DOM、AJAX 更新的排序器和过滤器,例如 DataTables,它具有一些内置的 options for caching。为了跨会话重用客户端数据,应修改上一个链接中的 ajax 请求,以在每次 ajax 请求后将current data in the table 保存为localStorage,并且当客户端启动新会话时,使用此数据填充表。这将允许插件管理过滤、排序、缓存和基于浏览器的持久性。

【讨论】:

  • 你在回避我的问题,不过,这是一个很好的答案。可以将 servlet 与 websockets 一起使用(我从未尝试过,但应该不难)。我正在考虑将长轮询作为 websockets 的一个问题较少(且功能较弱)的替代方案。 +++谢谢你的链接。我放弃 servlet 没有任何问题,因为只有一个类将剩余代码与 HTTP 隔离开来。
  • 我在回避你的问题。这就像当我回答有关“我如何同步两个列表?”的问题时。并建议使用哈希图。 :)
  • 是的,我猜 Tomcat 确实将 WebSockets 实现硬塞进了一个带有 HTTP 适配器的 Servlet。请注意,它不适合。阅读他们关于生产使用的警告:tomcat.apache.org/tomcat-7.0-doc/web-socket-howto.html
  • 当然,回避这个问题有时是最好的选择。这非常令人印象深刻,但可能有点矫枉过正(我的列表很大,我不想显示过时的数据,但更新很少)。我会阅读它,稍后再回来,谢谢。 +++ 顺便说一句,它并没有解决缓存问题:由于我将排序和过滤留给客户端,它需要加载或缓存几兆字节的数据。
  • 它解决了一个不同的问题,即快速将许多更改传播到许多客户端上的小列表。我的问题是得到一个大列表,它通常与以前的版本略有不同。这来自于我让客户端进行所有排序和过滤的不同寻常的决定。 Lightstreamer 的客户端从不存储数兆字节的数据,因为它们只接收要显示给用户的数据。他们的数据变化非常快,因此发送和存储超出显示范围的数据毫无意义。如果我想使用 Lightstreamer,我必须做很多改变;这是可行的,但价格不菲。
【解决方案3】:

我正在考虑类似于 Aperçu 的想法,但使用两个请求。这个想法还不完整,所以请耐心等待......

  • 客户端请求GET /offer/123/items,可能带有ETagFetched-At 标头。

服务器回答

  • 200 和完整列表(如果缺少任何一个标头,或者自 Fetched-At 时间戳以来有太多更改)
  • 304 如果从那以后没有任何改变
  • 304 和一个特殊的 Fetch-More 标头告诉客户端要获取更多数据,否则

最后一种情况违反了 HTTP 的工作方式,但 AFAIK 是让浏览器缓存 一切 我希望它缓存的内容的唯一方法。由于整个通信都是加密的,代理不能因为我违反规范而惩罚我。

客户端通过请求GET /offer/123/items/errata 来响应Fetch-Errata。这样,资源就被分成了两个请求。拆分是丑陋的,但有角度的 $http 拦截器可以隐藏应用程序的丑陋。

第二个请求也是可缓存的,也可以有一个Fetched-At 标头。细节尚不清楚,但一些强大的handwavium让我相信它可以工作。实际上,勘误表本身可能是不准确的,但仍然有用并得到勘误表本身......等等。

使用 HTTP/1.1,更多的请求可能意味着更多的延迟,但由于节省的带宽,拥有几个请求仍然是有利可图的。服务器可以决定何时停止。

使用 HTTP/2,可以一次发送多个请求。服务器可以有效地处理它们,因为它知道它们属于一起。还有一些handwavium...

我觉得这个想法很奇怪,但很有趣,我很期待 cmets。请随意对我投反对票,但请留下解释。

【讨论】:

    猜你喜欢
    • 2018-10-14
    • 2019-07-05
    • 1970-01-01
    • 2021-03-24
    • 1970-01-01
    • 1970-01-01
    • 2016-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多