分析抖音的互联网架构|青训营

284 阅读7分钟

架构究竟是什么?如何更好的理解架构。我们知道中国文字博大精深可以说从文字的组成就能理解其含义。架构也不例外 “架构” 是由 “架” 、“构” 整体结构和组件的组合就形成了架构。以 Android 架构为例子一个 APP 通常是有 class(类)组成,而这些 class 之间如何如何组合、相互之间如何发生作用,则是影响这个 APP 本身的关键点。细分的话可以分为类、接口(连接器)、任务流。所谓类就是组成架构的核心 “砖瓦”,而接口则是这些类之间通讯的路径、通讯的机制、通讯的期望结果。任务流则是描述系统如何使用类和接口完成某一项需求比如:一次网络请求。 上面介绍架构中提到了房屋、木头、砖瓦可见架构和建筑有着彼此的联系。 架构设计目的 软件架构目的性大致可分为可扩展性、可定制化、可伸缩、可维护性: 可扩展性: APP 必须能够在用户的 UV/PV 数量快速增加的情况下,保持软件合理的性能。只有这样在快速的从 0 到 1 的需求迭代中才能后顾无忧。 可定制化: 在同一个软件系统中可能面向的用户群体是不同的、多样的,需要满足根据用户群的不同和市场需求的不同进行定制化。比如一个 APP 中某些功能只针对特定用户开放。 可伸缩性: 在新技术出现的时候,一个软件系统应当允许接入新技术,从而对现有系统进行功能和性能的扩展。 可维护性: 软件系统的维护包括两方面,一是修复现有的 bug,二是将新的迭代需求开发到现有系统中去。一个易于维护的系统可以有效地降低人力和物力。 抖音工程架构演进 阶段一:抖音原始工程架构(Original architecture of project) 抖音项目一开始是单体架构+Cocoapods,业务代码、工程配置、资源文件全部放在一个大业务仓库。由 Podfile 文件描述第三方仓库的依赖版本。 阶段二:分离壳工程后的工程架构(After splitting of host shell pod) 分离壳工程后,工程配置、部分系统资源、工程主入口被拆分到主宿主壳工程。 Podfile 拆分出版本依赖管理文件 Podfile.seer,由依赖管理平台进行各个版本的容器化管理,业务仓跟随宿主集成发版,打平依赖,解决版本依赖决议耗时问题。 大业务仓中的代码和资源被拆分到各个业务线的仓库下,由 podspec 文件描述内外依赖。业务线仓库增加 ModuleInterface subspec,存放对外接口,采用依赖注入方式实现接口隔离,初步建立接口层。 业务仓库之间规定只能依赖其他业务仓库的 ModuleInterface subspec,通过 lint 进行编译检查。 部分基础能力代码被拆分成基础仓库,跟第三方仓库一样独立发版。本地研发工具支持单仓开发和多仓开发,不参与代码修改的仓库通过二进制的方式进行链接。同时 CI 流程上也支持通过二进制打测试包,提高打包效率。 为了满足一个工程同时支持多个项目、部分业务线功能复用、部分业务线中台化发展的需求,我们把所有业务线抽象成独立的 Pod,所有业务 Pod 必须通过宿主的壳工程进行集成发版。 壳工程包含了项目依赖的 Pod 信息描述,同时还包括工程的配置、部分系统级别的资源文件、工程主入口代码。基于多份宿主壳工程,一份代码可以打包出抖音、抖音极速版等项目。 同时,基于宿主壳工程,一些业务线可以通过自动化同步生成自己的子壳工程,实现业务线自己的 Example 工程,进行独立开发,比如有语音通话的 Example 工程,有工具的 Example 工程,有直播的 Example 工程等等。 接口层顾名思义,只提供依赖的抽象接口,所有接口都是 protocol 协议声明。 接口层限制了所有其他依赖,类、枚举、 外部协议都采用前向声明,podspec 上只允许声明对 DI(依赖注入)框架的依赖。接口层满足封装、隔离和组合的原则。 业务层面对外封装了实现代码; 编译层面隔离了组件间依赖传递,减少头文件 import 嵌套提高编译缓存的命中率,对于 swift 业务组件,还能达到减少编译传递的问题; 架构层面声明抽象协议支持接口组合; DI 容器框架同时支持 stateless DI 容器,也支持 stateful DI 容器。 依赖打平 采用 Cocoapods 本身自带的版本依赖决议进行版本分析会消耗大量的时间; Podfile.lock 过于繁琐,可读性很差,难以解决 Podfile.lock 的冲突; 隐式依赖被动/不符合预期地升级,难以确定性地声明所有依赖,防止隐式依赖被升级; 依赖版本在 Podfile/Podfile.lock 重复声明,增加了解决冲突的成本; Podfile.lock 参与依赖版本决议流程比较复杂,会出现不符合预期的情况。Podfile.seer 文件 hook 掉 Cocoapods 采用 podfile.lock 进行版本决议的逻辑,采用 Podfile.seer 文件直接描述所有组件的版本信息,打平依赖。 阶段三:单仓多组件工程架构(Multicomponents in single repo) 采用单仓多组件后,每个业务线仓库支持添加 podspec 增加组件,实现更小粒度的二进制依赖。业务线仓库内划分业务实现层、业务接口层、服务层和基础层,都是通过集成方式发版。 新增的服务层主要存放公共的业务逻辑和通用服务,限制 UI,一是满足业务逻辑复用,二是满足子壳工程最小化二进制依赖。同时服务层的服务接口也达到隔离依赖传递的目的,在不同的宿主上,支持通过改变服务层实现替换后台能力或者底层能力。建立分层间的依赖准入规则,完善 lint 编译链接检查。 编译校验:分开编译各个 subspec,确保每个 subspec 的依赖是正确的(由于 subspec 没有编译隔离) 接口符号校验:校验当前接口组件(ModuleInterface)中符号是否完备的,以保证其他组件单独引用是否能正常使用。如 extern 声明的全局变量。 阶段四:Example 子壳工程架构(Subshell for bizcomponent in example project) 每个业务仓从宿主同步工程配置构建子壳工程。增加 AWELaunchKit 为子壳工程提供运行时的基础能力。通过服务层提供业务间运行时共享的服务能力,满足代码复用和更小二进制依赖。 AWELaunchKit 框架为宿主和其他子壳工程提供了基础服务的依赖和初始化配置。同时提供了一套启动加载的 BootTasks 管理框架,部分业务涉及启动相关的逻辑可以在业务仓对应的服务层中实现,并通过 BootTasks 管理框架注册到启动加载器里面。 同时框架还提供了一套宿主 UI 入口和自定义入口框架。为了方便测试和调试,也整合了整套测试调试框架。