历时 1 年,上百万行代码!首次揭秘手淘全链路性能优化(二)

阅读数:1 2019 年 12 月 23 日 18:18

历时1年,上百万行代码!首次揭秘手淘全链路性能优化(二)

▐ 阶段划分

什么阶段做什么事情,前面打基础,只有夯实了基础,后期才能顺理成章。我们把手淘的启动阶段做了以下细分:

历时1年,上百万行代码!首次揭秘手淘全链路性能优化(二)
启动流程如下:
历时1年,上百万行代码!首次揭秘手淘全链路性能优化(二)

可以看到:整个流程很清晰,分阶段、多任务并发执行,不存在老框架下几条初始化链路交错在一起的情况,首页那一块位置不受干扰。

▐ 任务编排

无锁化,得益于“有向无环图”,通过构建任务间的依赖,启动框架严格按照图的顺序执行各项 SDK 的初始化,真正做到时序可预期,原本需要靠锁来保证状态同步的,现在转变成了“无锁化”。

开箱即用,对一项启动任务而言,极致的体验应该是:无论我身处何处,所依赖的基础库、中间件们都应该“开箱即用”。

多任务并发,早期任务少、业务简单,基础尚未成型,单流水线作业就够了;但随着业务日益膨胀,基础只会越来越厚,就必须多流水线齐头并进,协同作业,提高吞吐率。

无锁化的好处:

  • 代码执行效率高,SDK 的初始化基本上都需要考虑多线程安全问题,如果从时序上能保证顺序,也即不存在竞争,等同于“无锁”;
  • 减少 ANR,降低卡顿故障,比如我们之前查的网络库在 vivo y85a 上启动长时卡顿达 1s 以上的问题,如果我们能正确梳理各项 SDK 之间的依赖,类似的问题就可以避免了;
    任务编排是重中之重,是决定成败的重要因素:依赖梳理不当,执行效率上不去。

▐ 任务调度

要支持多任务并发,那肯定绕不开线程池,既然要用到线程池,那线程池大小需要一个比较合理的设置。

★ 核心思想
阶段(Stage)+ 线程池(ThreadPool Executor)

★ 线程池大小
因为我们的 SDK 大多涉及到 so 的加载、文件的读写,线程等待时间占比比较高,所以我采用了一个通用的估算方法:2N + 1,N 是 CPU 个数。

★ 线程优先级
把先于首页(落地页)的阶段的线程优先级都调高一些,以求得到优先调度,尽快执行;进入 idle 阶段后,性质原因,慢任务居多,调整线程池大小,同时把优先级调低,做到尽量不干扰 UI 主线程,在后台慢慢跑。
历时1年,上百万行代码!首次揭秘手淘全链路性能优化(二)

实际运行的 DAG 图

优化效果:

历时1年,上百万行代码!首次揭秘手淘全链路性能优化(二)

启动环境是应用中最为复杂环节,任务多,负载重,资源争抢下,不管是 CPU ,内存,网络,IO 都有可能成为瓶颈,启动框架的引入,让我们在面对这些挑战时,有了一个明确的方向,给出一个稍微系统化的解。当然,系统资源调度优化是个非常深刻的课题,加上手机各种硬件配置多样性,我们在这个领域仍然面临更大的挑战,当前只是一个开始。

网络的链路优化

▐ 问题定义

历时1年,上百万行代码!首次揭秘手淘全链路性能优化(二)
历时1年,上百万行代码!首次揭秘手淘全链路性能优化(二)
可以看到,手淘首次安装冷启动 30s 内,网络请求数达到 400 上下。非首次冷启动 30s 内,请求数相比首装冷启减少,但依然在 100+。启动场景下,存在着以下几个问题:

  • 请求过多:重复请求、请求滥发情况严重。
  • 数据量过大:资源文件的下载占流量的 80% 以上。
  • 业务方请求时机不合理:非首页 & 启动必要请求需延后。

过量的请求集中在启动阶段导致原本就有限的网络带宽和端上处理能力更加严峻。

本文转载自淘系技术公众号。

原文链接: https://mp.weixin.qq.com/s/PiqnHezWKWUU0byEhrboRg

评论

发布