近年来,Instagram发布了许多功能-我们推出了故事,过滤器,创建工具,通知和消息直递,以及许多其他功能和优化。 但是,随着产品功能的增长,一个不幸的副作用是我们的网络性能开始下降。 在过去的一年中,我们有意识地努力来改善这一状况。 到目前为止,我们的不懈努力已使Feed页的加载时间累计提升了近50%。 这一系列博客文章将概述我们为实现这些改进所做的一些工作。
缓存优先
由于我们已经在页面加载的尽可能早的时间点将数据推送到客户端,因此,将数据发送到客户端的更快的唯一方法就是根本不必获取或推送任何数据。
我们可以使用缓存优先渲染方法来实现,尽管这确实意味着我们必须在短时间内向用户显示陈旧的Feed数据信息。 通过这种方法,当页面加载完毕后,我们会立即向用户显示其先前的提要和故事的缓存副本,然后在可用时将其替换为新数据。
我们使用Redux来管理instagram.com上的状态,总体而言,我们实现该方法的方式是将Redux存储的子集存储在客户端上的indexedDB表中,然后在首次加载页面时重新更新该存储。 但是,由于indexedDB访问、服务器数据获取、用户交互三者间的异步特性,如果用户在缓存数据上进行交互可能会存在问题。但是我们依旧想确保这些交互仍作用于从服务器获取的新状态数据。
例如,如果我们要以简便的方式处理缓存,则可能会遇到以下问题:我们开始同时从缓存和网络加载,并且由于已准备好缓存的数据,因此将其显示给用户。 然后,用户点击喜欢该帖子,但是一旦最新的网络数据返回,它就会用一个副本覆盖该帖子,该副本会丢失用户对于缓存副本的操作(请参见下图)。
为了解决此问题,我们需要一种将交互应用于缓存状态,同时还存储了这些交互,以便以后可以在服务器上的新状态上重放它们。
如果您以前曾经使用过Git或类似的源代码控制系统,则此问题可能看起来很熟悉。 如果我们将缓存的提要状态视为一个分支,而将服务器提要响应视为主服务器,则我们有效地要做的是执行一个变基操作,将本地分支中的提交(喜欢,评论等)应用到主分支。
我们做了以下设计:
- 在页面加载时,我们发送对新数据的请求(或等待其推送)
- 创建Redux状态的阶段性子集
- 在请求/推送未完成期间,我们存储所有调度的动作
- 请求到达后,我们会将操作与新数据以及所有待处理的操作一起应用到暂存状态
- 提交暂存状态后,我们只需用暂存状态替换当前状态即可。
通过具有暂存状态,可以重新使用所有现有的reducer行为。 它还使暂存状态(具有最新数据)与当前状态保持独立。 另外,由于分段是使用Redux实现的,因此我们只需要调度操作即可使用它!
API
function stagingAction(
key: string,
promise: Promise<Action>,
): AsyncAction<State, Action>
function stagingCommit(key: string): AsyncAction<State, Action>
Staging API包括两个主要函数: stagingAction & stagingCommit。
stagingAction接受一个Promise,该Promise将解决要分派到暂存状态的动作。 它初始化暂存状态并跟踪自初始化以来已调度的所有操作。 在源代码控制的类比中,我们可以将其视为创建本地分支,因为当新数据到达时,现在发生的任何操作都将排队,并应用于暂存状态。
stagingCommit将staging状态提交到current状态。 如果对暂存状态的任何异步操作处于暂挂状态,它将在提交之前等待。 这类似于源代码管理中的变基,因为我们将所有本地更改(来自缓存分支)应用在主服务器(服务器中的新数据)之上,使本地分支保持最新状态。
为了启用staging,我们用处理stagingCommit动作并将stage的动作应用于新状态。 要使用所有这些,我们只需要调度相关的动作,一切都将为我们处理。 例如,如果我们要获取一个新的feed并将其应用于暂存状态,则可以执行以下操作:
function fetchAndStageFeed() {
return stagingAction(
'feed',
(async () => {
const {data} = await fetchFeedTimeline();
return {
type: FEED_LOADED,
...data,
};
})(),
);
}
// Fetches the new feed and stages it
store.dispatch(fetchAndStageFeed());
// any other actions dispatched until the stagingCommit action
// will be applied to the 'feed' staged state
// Commits staging to the current state
store.dispatch(stagingCommit('feed'));
对Feed和Stories功能同时使用缓存优先渲染功能,分别使显示完成时间缩短了2.5%和11%,并通过本机iOS和Android Instagram应用提供了更好的用户体验。
静待第四部分
在第四部分,我们讲介绍如何裁剪代码大小,并且通过代码大小和执行优化提升性能。
请关注奶爸码农公众号,第一时间获得最新信息。
『奶爸码农』从事互联网研发工作10+年,经历IBM、SAP、陆金所、携程等国内外IT公司,目前在美团负责餐饮相关大前端技术团队,定期分享关于大前端技术、投资理财、个人成长的思考与总结