对大部分应用系统来说,在某种程度上,应用程序的快速加载和及时取得最新数据两个方面同样重要。倾向于积极使用缓存数据,可能会导致提供的数据陈旧;而倾向于及时获取最新数据,可能会牺牲加载时间。当然,也可以鱼与熊掌兼得,但是可能会需要更多的硬件,更复杂的软件,或两者都需要(意味着一个字:钱)。
如何权衡取决于特定的应用系统和业务要求,本文就是我们的团队使用React.js和应用缓存来解决这一问题的一个实例。
标签是每当你在浏览器上打开一个标签去送出一份慈善捐助的好理由。这是一件很伟大的事——但事实上,我们仅仅点击了一个价值 100,000美元的里程碑来完成慈善捐助——但是,我们有一个疑问。
我们的应用也太慢了。大家都明白这点。当用户更换新的标签页时,他们需要得是速度与连贯性。而且,我们也没有宣布:载入页面的延迟成为了人们关闭标签的首选理由。
我们想让我们的页面除了更有用,还要更好地被接受。但随着我们向页面中加了些附加功能后, 我们的页面载入问题也越来越突出了。因为人们需要我们的 APP 能快速地提供内容信息。
我们正在用 Django 的模板系统做一个交互式服务器来召唤或服务一个页面。当使用者是在快速的网络环境中,而且我们的服务状态是健康的情况下,服务器响应时间是 ~65毫秒,还不是比较惨。然而,如果在你父母的房子*里打开一个标签,或者我们的数据库产生了一个短暂的停顿时,这可能会给你在对其的信任上,泼了一盆冷水。
比较让人烦恼,我应该承认我们所建立的 APP 并没有采用标准的前端框架,除了仅仅是使用了 JQuery。 考虑到我们的 APP 有太多的互动,而且太混乱了。在各种各样的代码类型上,我要怎么才能喜欢它。
我们需要去修改它。
* 我爱你们,老妈、老爸!时代华纳有线电视, 没有太多什么了。
当准备去处理这个问题时,我们必须决定优先处理哪些以及放弃哪此需求。在这里我们提出了一些建议:
页面必须能快速载入。这是没得讨价还价的。
我们的页面必须是非本地 URL。我们提高了 VIA 捐助广告的价格,网络在线广告需要去核识真实性来确保这些广告是够安全的。由于浏览器端的用户页面插件总是将我们的广告移除,以至于网络广告只能使用 http 或 https 协议。
我们希望页面中的内容是最新的,但不必是实时性的。我们通过设备对用户数据进行同步, 并保持完美的体验。我们以分页的形式显示出用户的反馈;例如,我们显示出新用户的统计数据;我们有时也要运行捐助设备来以滚动条的形式显示出 "募集资金" 量。虽然我们愿意去接收一定程度上稍旧的数据(就像页面展示后才提交数据),但理想得是在提交数据的瞬间发生。
我们要减少前端混乱的代码。将非优先权最高的代码肃清,这是一件让人兴奋的事。
让我们动起手来实践关于处理这些问题的思路。
我们一度认为应该增加首先扩展服务器端缓存结构,目前我们已经十分依赖Django的低级缓存(low-level cache),它有助于达到我们的目标,但是我们不得不在每次都要写语句来判断是否存在缓存到期或失效情况,我想这张摘自一场精彩演讲(an excellent presentation)的幻灯片能够反映出Django在缓存问题上面临的挑战:
此外,为了更好地从服务器端缓存中获益,我们的缓存系统看起来是一个多层次的结构:(先是)每个用户完整的页面缓存,然后是用户数据的模块化的缓存,(同时)每当数据变化时还要智能判断数据是否失效。因为在实施过程中已经遭遇到一些与缓存相关的(系统)错误(bugs),所以我们并不希望继续增加了缓存系统的复杂性。
更重要的是,还存在网络传递差异的问题,例如对一个新的TAB页面来说,在快速和慢速的因特网网络上的表现有着显著的差异,即使将我们的服务响应时间降低到小于1毫秒,对大部分用户而言,这个页面显示的还是不够快的。
不,这样可不行。
"应用缓存? 他不是个douchebag么?"
不,别这么粗鲁!
… 好吧, 也许他是有点儿. 在使用应用缓存之前,充分了解它的怪癖和陷阱是明智的.我们主要关心的是应用缓存会降低我们在调试时的透明度,因为我们服务器在轻便的请求上没有日志 (接下来我们将解决这个问题). 在代码变更后的另一个与之前不同的小问题是,在两个视图页上应用了这些变更: 它需要一个页面去提示浏览器获取资源, 另一个页面则去使用新的资源.这不是很理想, 但是在我们的案例中是可以接受的. 在一般情况下, 我们的团队在应用缓存的限制下相对没有多少烦恼; 更多我们的app不适用的情况下解决起来会更轻松.
好吧, 也许我们可以与应用缓存合作. 可能这是一个方法在不必通过大量的重构去实现它?
我们快速而粗糙的主意就是,使用 Django 处理视图模版并返回一个html页面来保持我们当前页面的原状.在任何用户数据变更时,浏览器会从服务器和应用缓存那里获取一个重新渲染的页面 .
我们的游戏计划:
我们将在当前页面上激活应用缓存, 所以它将会绕过服务器去加载.
当一个用户制造了一些数据改动而我们又想保留时, 我们的页面将会使用一个ajax请求去保存数据到数据库里,通常我们就是这么做的.
我们将会从应用缓存清单引入一个对用户特殊的版本号,所以对于每个用户来说这份清单都是独一无二的. 当用户更新任意数据时, 我们将会对这个用户的应用缓存清单的内容创建一个新的版本,而且浏览器会知道并获取页面资源来更新.
在客户端方面, 我们将会在用户修改任意数据时检查应用缓存并更新.浏览器将会获取用户的缓存清单, 查看已经被处理成一个新版本号的被更改的内容,并且重新获取页面的内容.
理论上, 当用户下次浏览这个页面时, 应用缓存会提供一个在服务端重新渲染过的最新的页面.
从好的方面讲,这些选择将会引入极小的工程投资.
一个小缺点:这个选项没起作用。
浏览器获取资源的速度不够快是主要的问题。 如果你在新的标签页修改了数据(例如,在你的便签里添加了一条笔记),然后在几秒内打开了一个新的标签,应用缓存可能还没有获取到你修改的新的页面,显示的依旧是你没添加笔记的旧页面。从用户体验的角度看,这就像是数据丢失 — 即使是技术上的数据延迟 ,也是我们无法接受的。
当多个设备参与时这个问题会变得更严重。如果你在设备A上对你的新标签页有修改,接着在设备B打开一个标签页,保证你得到的是旧的数据。在随后的页面加载之前你都看不到新的数据。
这不是很好。 抱歉,这是个快速而粗糙的选择。
更简洁地做到这一点,我们可以结合客户端模板使用应用缓存,在本地存储数据。这看起来是个很好的选择,除了应用缓存的“第二页加载”那个问题所出现的糟糕情况,它是非常快的,并且它还可以清理掉我们的前端(重构...哇?)。作为奖励,我们的新标签页在在线的时候将被访问。
我们选择使用 React.js 作为模板是有一些原因的。最主要的一个就是我们有一些在其他领域使用应用的经验。我们也觉得学习曲线比Angular更浅显些,我们也是严肃地考虑过其他方案的。说来奇怪,长久以来建立一个前端框架都是在我们已有的jQuery上努力,我们的数据被改变更像是React中的“状态”,这会让我们转换到React更容易些。
我们还选用了 Facebook 的 Flux 构型, 因为我们认可单向的数据流可以让我们的代码更顺理成章. Flux 的调度器也能让我们更加容易的进行数据同步,下面我会对此进行描述.
它是如何运作的
新的tab一打开,浏览器就会从应用缓存中获取我们的页面。我们的React应用或从Flux存储中获取数据 (1), 后者会去本地存储里抽取数据 (2). React 应用一安装,页面就会被加载 (3, 4). 然后,我们的页面会向应用服务器进行一次Ajax调用 (5), 发送应用的所有数据—其实就是一个带有所有Flux存储数据的对象. 服务器接收到用户所有的本地数据,并使用用户在数据库中的数据对其进行调和 (这会在下面的 "数据同步" 中有更详细的描述), 然后向应用返回最新的数据 (6). 应用会从服务器接收到最新的数据,并且更新每一个Flux存储 (7, 8). Flux 存储一更新,存储会触发一个变化事件 (3), 而 React 组件就会更新他们的状态 (4). 当用户改变了什么东西的时候,就会发起一个动作 (11), 更新存储的数据 (7, 8); 当存储更新并触发变化时间的时候,我们会将数据持久化到本地存储中 (9) 而如果用户在线的话,就将数据持久化到数据库中 (10).
2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务