测试一直嚷着app初始页面load时间过长,无奈的app开发和前端同学检查过后,把包袱扔给了我这个后台开发。查验后,发现一个初始化调用竟然用了400+ms,再加上网络延迟和app自身数据加载的时间,已经开始挑战用户的耐性。之前写代码完全对效率视而不见,逃避不是解决问题的方法,于是对这个调用分析了一番。
这个接口做的事情包括两方面:业务校验和初始化数据加载。最长的执行路径多达17个远程服务调用,这个17个服务中,耗时最长的达200多ms,最小的不到10ms,总的加起来妥妥的近500ms,不能接受啊!
优化主要从以下几个方面进行:
1. 去冗余。整理了业务,把每次调用的路径都罗列出来,发现不管是业务校验还是数据加载都是存在冗余的调用的,去掉冗余后,减少到了13个服务调用;
2. 数据缓存。两个最耗时的服务调用,一个是获取商品信息,一个是获取用户信息。商品信息是比较固定,几乎不会变,且数据对象小,完全可以在服务器做本地缓存,并设定每天从远程服务器重新获取一次。为了防止突发的商品信息更新,也做了可配置的缓存失效开关。用户信息也比较固定,但数据总量大,保存在本地不现实,于是放到了缓存服务器上。毕竟与整个集团业务相比,我们的业务用户只是其中很小一部分,把这小部分缓存起来对效率提升是很大的。这两个接口改造后,整个接口调用时间已经降到了一半以上。
3. 业务优化,不要为了1%而牺牲99%。1和2过后,再分析调用效率,大部分都不超过10ms,但还有一个校验接口接近40ms。这个接口调用的业务目的就是判断当前用户请求是否满足某条件,若不满足则给出错误提示,避免用户进入下一步执行一系列操作后才发现不满足条件,导致糟糕的用户体验。从这里来看,这个考量无可厚非。但进一步研究,这个接口不满足条件的情况其实是很苛刻的,在实际中几乎不会出现。但为了应对这种极端条件下才会出现的情况,每一个用户每一次正常的执行都必须执行这个调用,其实是得不偿失的。因此,果断把这个调用去掉。由此,事件已经降到50ms。
4. 远程调用接口合并。一次请求里里有10几个远程服务调用,想想就可怕,有些调用是可以合并的。合并的原则必须与业务特性挂钩,根据业务特征和接口设计来确定哪些调用可以合并。将多个接口合并为一个,减少的不必要的网络耗时。(后续进行该工作)
5. 校验与数据加载分离。该接口的执行顺序其实是先进行业务校验,再加载数据。若业务校验不成功,则直接返回。由此可见,业务校验和数据加载其实是可以分离的。划分为两个接口,app先调用业务校验,若成功,则进入下一个展示页面异步请求初始化数据。这也是优化的考虑之一,但经过前三步之后,初始化接口50ms的耗时已经在接受范围内,再分离接口,多一次的请求,反而会导致额外的耗时,可能适得其反。因此这一步的优化暂且放到一边。
其实简单的优化,暴露的是设计考虑不全面和编码的不规范,还是得从平时做起。