引言:
性能优化大家肯定不陌生。以耗时优化为例,拆解链路往往可以分为,用户点击进入页面,端上发起网络请求,端上获得服务端返回数据,大前端渲染然后完成首屏加载。在一些应用中,网络耗时是大头,也是优化的重要方向。网络耗时也有很多方法,比如减少网络接口的耗时,或者预加载。本文想聊聊这两个方向的优化
接口耗时优化:
接口耗时的一个重要的方向是优化接口包大小。一个利器是
分块网络传输
简单介绍一下分块网络传输(Chunked Transfer Encoding):
一种数据传输方式,主要用于 HTTP 协议中,当服务器向客户端发送数据时,如果数据的长度不确定或者数据是逐步生成的,就可以使用分块传输编码。
原理
- 服务器将数据分成若干个小块,每个小块都有一个指定的大小。这些小块被依次发送给客户端,而不是一次性发送整个数据。
- 每个数据块的大小以十六进制数表示,并在块的开头作为块的长度指示。这个长度指示后面跟着一个 CRLF(回车换行符),表示块的结束。
- 当所有的数据块都发送完毕后,服务器会发送一个长度为 0 的块,表示数据传输的结束。这个块的格式为 “0\r\n\r\n”。
如何优化
分块网络传输允许服务端发送多个数据块。拆包减少网络数据包大小。通过对服务端数据打包耗时分析,可以将一些相对独立且耗时较多的内容,进行拆分。将基础内容放到第一个包当中,将耗时多的部分拆到第二个包里面。
回到用户链路,相当于端到端接口这一步,我们发起请求后会有多次回调。收到第一次数据就可以进入渲染,然后等收到第二个包,再增量的渲染独立的部分。
这里服务端做了顺序的强校验,保证先返回第一个包,再返回第二个包。不会让第二个包早于第一个包返回。
这里客户端也需要做一些改造,支持chunked。比如chunked的回调会被多次调用,(因为有多个数据包)。chunked的error除了无网络,错误数据外,也可能发生错误顺序。
预加载
原理
提前发起网络请求,后续可以进行复用。缩短客户端等待数据接口的时间。具体来说,每次客户端发送请求的时候,记录当前请求的请求参数。后续发送请求的时候,对比核心参数,如果参数误差在阈值内,则复用请求,将回调block绑定到预请求的block上。当结果返回的时候,会一起调用回调。
场景
主动搜索场景预请求。在原本的架构中,用户进入页面,我们会配置这个页面,填充参数并发起网络请求。从router到页面,然后到viewdidload 真正发起请求,这之间有一些耗时。所以如果我们把网络请求的时机提前到router,就能提前发起请求。
关于复用,这里提供两个版本。一种是纯客户端改造,客户端请求服务端数据,存在本地。当正式请求的时候,复用预请求数据。但是有个问题是,由于我们的业务数据最终是服务端记录为准。如果预请求的数据没有被用户真实消费(比如用户还没看到首屏就退出了),服务端不会感知,就会造成PV虚高,导致一些中间转化指标下跌,有些转化指标是实验护栏指标,会影响实验推全。
另一种是服务端客户端协同改造。客户端请求服务端的时候,请求参数携带一个标记字段。表示本次是预请求。此时服务端不会真正将本次请求结果落表。等客户端首屏渲染的时候,额外发起一次请求,通知服务端,可以将本次结果落表。这个方法可以避免PV虚高,但是也有一些问题。如果客户端通知服务端落表的那次接口调用失败了,可能导致结果不被记录。会影响推荐模型的训练。不过实验回收比上一个方法容易。
被动搜索场景预请求。比如一些场景下,前序页面给用户推荐了推荐词。那么用户点击推荐词发起搜索的时候,搜索词是确定的。当用户点击这个推荐词的时候,到router的时候,还有一段时间可以优化。具体来说,我们可以做的激进一点,当用户touch begin的时候就可以发请求。(注意,touch begin的时候,还是在手势识别阶段。用户最终不一定是点击这个词,也可能是长按,那就不会打开页面。我们需要cancel掉这次请求)。
被动场景预请求的时机更前置,因此在部分场景下的收益格外优秀。
其他方案
除了上面的优化外,还有许多落地的网络相关的性能优化。比如首屏卡片数量优化,根据手机尺寸,减少首屏请求返回的卡片数量;网络图片体积优化,先用低清的图片进行占位,然后等高清图片返回后,重新加载上去(这个后面单开一章聊聊),以及gecko资源预加载来减少接口耗时等等。网络优化方法实在是太多了,一篇讲不完。
这里还有一个有趣的巧事,笔者做touch begin优化的时候,正好主端也在做touch begin。大家想到一块去了。组织内的业务交流,还是能带来不少的思路碰撞的。