这是2024年的第8篇文章
( 本文阅读时间:15分钟 )
01
02
03
04
05
既然软件供应链管理是我们在研发软件系统的时候不得不付出的成本,那么下一步应该思考的是,如何把这个成本降低。
除了建站这种是为了解决新的业务问题之外,几乎所有的软件供应链管理问题都可以理解成为:把一个或者多个目标软件/服务/配置升级到期望的版本。以 JDK 升级为例,为了完成升级目标,需要修改的软件和配置非常多,包括基础镜像,环境变量,JVM 参数,Maven 配置,数十个 Maven 依赖,以及代码改造(如 Mockito,Velocity,Spring 等),降低这类工作的成本,可以从以下几个角度来分析。
显式 or 隐式:软件/配置/服务的声明是显式(explicit)的还是隐式(implicit)的,显式的声明管理成本更低,反之则更高。例如我们可否在一个地方清晰地看到所负责应用依赖的所有云资源(explicit),还是需要去分析代码看应用使用的众多 Diamond 动态配置,逐个分析后才能知道其依赖的资源(implicit)。
结构化的 or 无结构的。软件/配置/服务的声明是否有清晰一致的结构,结构化的内容更容易理解,管理成本低,反之则高。例如 Maven 的 POM 清晰定义了 Java 依赖的声明结构,相比之下,各应用的启动脚本是无结构的,可以没有约束地定义,理解成本很高。清晰的结构通常是一种比较合理的抽象。
一处修改 or 处处修改。众多系统/应用的相关软件/配置变更可否在一处完成。例如当我们升级 JDK 的时候,会需要为几百个应用做几百几千次的代码修改才能完成,这种做法显然成本是很高的。如果这几百个应用都在同一个代码库中,即用 mono repo 的形式管理,且这个代码库的结构得到很好的维护,那么升级 JDK 需要的代码修改量就会大大降低。
自动验证 or 手工验证。软件/配置的修改,都需要在生产环境变更后才能生效,这一点和自动化单元测试和集成测试的逻辑是一致的,是否存在自动验证也会极大影响修改的成本。如果需要手工验证,再放大到数百应用,这个成本就非常高了。
很多团队都存在虚拟或者实体的架构组,这个架构组的工作职责之一是推动架构一致性,具体的工作往往是识别到各种软件配置的问题,带领并推动相关团队去做相应的升级。我认为在做这个工作的过程中一定要关注上述的四点,需要把系统的供应链做到显式、结构化,需要想办法降低修改的次数,并建立完善自动化验证的手段。
06
一名学生写一个用完即抛的,几千行代码量的课程作业,是不需要考虑架构一致性问题的。一个只有几名研发的创业团队,也不会去关心架构一致性问题,遇到需要升级的软件和服务,直接修改就完事了。只有当研发团队规模越来越大,代码的规模随之增长到数千万数亿行的时候,架构一致性问题才会凸显,因为这个时候所付出的成本增长得太快了。
说起 Scalability,大家通常想到都是软件架构的横向伸缩能力,即基于负载均衡,横向增加计算资源以应对用户请求量的增长。这里暗含了几个要点,首先用户的请求量的增长通常是线性或者指数性增长,其次是系统应对用户请求量增长的时候不会有服务质量(如响应时间)的下降,第三是应对用户请求量的增长涉及的研发人员投入是亚线性(sublinear)的。
除了计算能力的 Scalability,另一个软件架构中常见的 Scalability 问题是数据库的 Scalability,在这篇简单的介绍文章中我们可以了解到,数据库基于数据复制和分片能力,可以支撑数据读写的增长。在这个例子中上述的三点也是成立的,包括访问量线性/指数性的增长,服务质量的保持,以及过程中研发投入增长的亚线性,或者说架构能力具备后这个投入就是常量。
架构一致性问题是一个典型的 Scalability 问题,我们期望的是随着代码量的增长,使用服务的增长,应用数的增长,需要为止投入的维护成本不要线性增长。我先尝试总结下 Scalable 方案的模式:首先它的诱因必然是一种业务的增长(growth),例如用户访问量的上升;其次为了应对这种增长需要有技术的引入(Technology),例如负载均衡和计算资源横向扩展技术;最后同时实现两个目标,其一是服务水平的保持(Keep Service Level),例如服务响应时间不变,其二是人员投入的亚线性(Sublinear Human Interaction),例如水平扩展的计算架构下研发的投入不会随着业务量上升而同步上升。
基于前文总结的 Scalable 方案的模式,我现在分析下对于一家公司来说,代码修改这个场景是否是 Scalable 的。我们直接把几个关键的分析因子填入分析:
Growth(业务增长):一家公司代码量的积累,从开始的几千几万,逐渐增长到千万行,数亿行的规模。需要注意的是,应用数量的拆分并不会导致代码量的下降,相反可能会导致代码量上升。
Technology(技术):一家公司是否有相关技术支撑 Scalable 目标的达成。
Keep Service Level(服务水平保持):在全公司范围修改一部分代码(如修复 log4j 的漏洞)可否在可以可接受的时间范围内完成(如1天)。
Sublinear Human Interaction (人员投入亚线性):当公司的代码量从数万,增长百倍千倍万倍的时候,修改代码的人员投入是否可以控制到极小的增长。(如1人日增长到5人日,而不是500人日)。
结合到我们当下的现状分析,虽然我们我们存在数以亿行级别的代码量,但是很多代码的修改不需要考虑 Scalability 问题,这些代码主要集中在贴近上层业务的部分,例如淘宝的营销会场等,它们几乎不会被大规模复用。而一旦涉及到复用的代码变更,要在全公司层面完成统一的修改,就非常的困难且成本非常高(同样的升级,同样的修复,类似的验证,需要被重复成千上万次),各种基础软件的升级都是这样的例子,几乎需要每个研发去修改代码并发布,而且很难做到 100%。因此,代码修改的 Scalability 问题应该进一步明确为:被广泛复用代码(配置、服务),其被修改的 Scalability 问题。
07
7.1 专家服务
对于一个有着成百上千研发人员的技术团队来说,让每个研发去处理类似 JDK 升级这样的工作,是非常低效的。处理这样的问题需要非常丰富的知识,而且这些知识大家平时几乎都是用不到的,因此学习成本很高,而且学了一次之后,后续几乎都用不到了。因此,在一个团队中让少数几个专家去处理这类问题,效率会高很多。同样的问题,例如一个罕见的类冲突,专家几分钟就解决了,而普通的研发往往需要消耗数小时。进一步的,专家会把这些脑中的知识积累成高质量的文档,基于这些知识和大模型技术,这样的专家服务就可以 AI 服务的形式提供,如 Amazon 披露的:
Most developers actually only spend a fraction of their time writing new code and building new applications. They spend a lot more of their cycles on painful, sloggy areas like maintenance and upgrades. Take language version upgrades.
A large number of customers continue using older versions of Java because it will take months—even years—and thousands of hours of developer time to upgrade. Putting this off has real costs and risks—you miss out on performance improvements and are vulnerable to security issues.
...
Amazon Q will analyze the entire source code of the application, generate the code in the target language and version, and execute tests, helping you realize the security and performance enhancements of the latest language versions.
Recently, a very small team of Amazon developers used Amazon Q Code Transformation to upgrade 1,000 production applications from Java 8 to Java 17 in just two days. The average time per application was less than 10 minutes.
Aone Copilot 团队也投入在做类似的工作,相信不久的将来大家也能用到类似的产品能力。
7.2 IaC
IaC(Infrastructure As Code)即基础设施代码化,前文提到我们期望软件供应链能够得到显式和结构一致的描述,这正是代码的优势。实际工作中的场景是,这些基础设施的数据分散在各类系统中,有些系统的数据质量高,有些系统的数据质量较差。在建站这类的场景中,架构师无法从单一的系统中获取系统的全貌,而需要组织一个临时的团队从四处搜集数据,然后通过一次次的尝试去验证。IaC 就是要把基础设施的数据交还给用户,各系统负责处理基础设施的变更。
有了显式和一致结构的描述后,DRY(Don't Repeat Yourself)才有可能。例如,当数千 Java 应用的启动脚本都是各自维护和定制的时候,就无法从中去提取类似服务优雅上下线、服务预热、Spring Boot application profile 注入等通用的功能函数。这类编码抽象的思想在 Java / Go 这样的程序中大家都会自然而然的想到,但是在基础设施描述这类大量的配置类数据中,应用得就少很多。
7.3 Serverless
Serverless 这个词被赋予了非常的涵义,这里指的是通过把原来的应用分为 App 和 Runtime 两层,并实现这两层的单独维护演进。这种方案的核心思路是,通过让大量的 App 在运形态复用相同的 Runtime(这里包含了基本的 OS, JDK,Pandora),实现基础设施的收敛(一致);同时,通过相关的调度技术实现 Runtime 可独立升级,实现了原本需要大量重复的工作在一处修改就能完成。这个思想在云的 FaaS 产品上得到了广泛的应用,同时在内部业务中 Aone Serverless 也持续做了很多工作。
7.4 Mono Repo
相比于 Serverless 技术实在运形态通过调度技术把上层的代码和下层的代码组合起来,Mono Repo(大库)是在编译期间就可以确保 DRY。没有实践过 Mono Repo 的同学,可以想象一下把几十个应用的代码合并在一起,那大量的 infra 相关的代码,如 spring,http,jdk 依赖就都可以在唯一的地方处理和解决,那么版本升级就变得非常简单。当然,简单的把代码放在一起不能解决问题,还得做大量代码的重构才能实现我们的目标。这方面集团内有不少的先行者在尝试,例如在卓越工程的实践中就有相关介绍。
08
挑战与未来
拓展阅读
参考链接
[02] Database Scaling
https://www.mongodb.com/basics/scaling