服务端架构治理|闲鱼如何有效提高应用的编译和启动速度

简介: 闲鱼服务端治理系列文章

作者:闲鱼技术——泊垚

背景

应用的发布是一件非常耗时的事情,尤其是当应用迭代了比较长的时间之后,一次预发的部署就可能需要花费十几分钟,其中服务启动一次,就可能花费五六分钟。如此漫长的发布可能带来两方面的问题:

  1. 开发过程中,在使用测试环境的时候发布验证的时候,我们往往希望快速迭代,快速验证,但是每次改完一个feature发布验证都要经过十几分钟的话,效率是非常低下的。
  2. 在线上发布过程中,如果遇到机器数量非常多的应用,那么一次发布,一批一批的部署下来,耗费的时间非常长,很容易超出发布窗口,带来线上风险。

针对应用发布耗时长的问题,笔者结合对手头应用idle-local的耗时分析,参考和尝试了多数的方案之后,制定了一套实施方案,能够有效提高应用的编译和启动速度。同时也着手优化和沉淀了一个启动加速工具(在其他同学的项目上迭代得到),能够针对整个过程中最耗时的启动阶段进行加速,有不错的效果。

构建部署耗时分析

idle-local项目耗时统计

image.png
注:

  1. mtop是我们项目的api层
  2. hsf是阿里的RPC框架
  3. pandora是一个的轻量级的隔离容器,用来隔离Webapp和中间件的依赖

重点优化分析

根据统计得到的应用编译部署耗时情况,以及理论上的加速空间,我们制定了以下几项优化的重点:

  1. 部署过程中,启动应用是最主要的耗时项,也是最容易随着应用迭代膨胀的部分,其中启动应用的主要耗时是bean的初始化。
  2. 构建过程中,代码编译是主要的耗时项,理论上存在较大的优化空间。
  3. 镜像中最后一层打包内容的大小会影响镜像push和pull的耗时,如果能够将变动范围分层优化,会有较大的收益。
  4. 停止应用的过程中,为了处理RPC服务HSF优雅下线和应用优雅关闭,花了比较多的时间,在非生产环境可以省略

构建部署速度治理方案

应用启动加速-Spring Bean异步初始化的原理与落地

加速效果:☆☆☆☆
配置简易:☆☆
推荐指数:☆☆☆☆
我们通过分析spring的初始化过程会发现,spring对于bean的创建,不论是通过遍历还是通过依赖触发,都是通过同步的方式对bean进行初始化的。
这就导致了当一个bean的初始化过程很久的时候,会严重阻塞后续bean的初始化,哪怕这两个bean之间完全没有相互依赖。
如果能够将bean的初始化过程放到异步线程中,则会大大提升bean的创建效率。
image.png
如图,在完成bean的实例化后,异步进行初始化,异步初始化实现的关键是保证bean在被使用之前初始化完成。
本节我们将从理论出发,逐步分析springBean的异步初始化办法:

孤立的Bean

我们将不被其他Bean依赖的Bean,定义为孤立的Bean。如图,beanA和beanB1两个bean,相互不依赖也不被其他bean依赖。
image.png
理论上,容器中存在孤立的Bean,这些Bean由于不被其他Bean依赖,在容器初始化过程中,这些Bean不会被其他bean使用,是可以自己异步进行初始化的,只需要容器初始化完成时,保障这些Bean已经被初始化完成即可。
然而完全孤立的Bean其实比较少,同时,找出孤立的Bean,需要遍历整个依赖树。我们需要一种覆盖范围更广,更容易定义的方式进行异步化。

暂时孤立的Bean

我们有这样的认知:

  1. 孤立的Bean一定是被spring通过遍历的方式创建的
  2. 被spring通过遍历的方式创建的bean不一定是孤立的Bean,他可能被后来的bean依赖并注入
  3. 被spring通过遍历的方式创建的bean有较大概率是孤立的Bean
  4. 被spring通过遍历的方式创建的bean的初始化距离它被其他的Bean依赖并注入,有一些时间差

基于这样的认知,我们可以按照以下方案进行异步化:
image.png
如图所示,被spring通过遍历的方式创建的bean我们可以暂时认为他是孤立的Bean,当我们发现他不是的时候(被其他Bean调用了getBean),阻塞并等待他的初始化,那么期间的异步初始化过程,也能为我们节省时间;如果他始终没有被依赖,那么说明他就是孤立的Bean。
至于在实际实现中,如何判断一个bean是被spring遍历到还是被其他bean依赖导致的创建,有一个可行的方法是:定义一个全局的标记,用来记录当前spring遍历到的bean,当且仅当这个标记是null的时候,表示当前正在获取的bean是被spring遍历到的,然后立即将当前bean写入标记,并在bean返回前将标记清除,我们可以通过增强beanFactory的getBean方法实现这部分逻辑。
这个方案的优点是能够自动识别并尝试异步初始化,无需复杂的配置即可实现效果不错的加速。

暂时不被使用的Bean

上述的两种异步化中,我们通过bean是否被“依赖”来决定是否异步初始化。但还有很多Bean,不是被spring通过遍历的方式创建的,这就导致我们上面的方案覆盖不到一些耗时的bean。
但事实上,我们Bean的初始化过程,只要在Bean被“使用”前完成即可,被“依赖”这个条件,是过于严格的。如果我们将Bean对象的方法访问判定为被“使用”的入口,那么我们可以通过对Bean进行代理,拦截Bean的方法访问,在其被“使用”之前,等待他初始化完成。
image.png
这样我们可以指定任意的Bean进行异步初始化。但有一种情况是不安全的:这个Bean定义了公共的变量,如果在初始化之前被访问,是不能被代理拦截的。我们在实现bean的时候,要注意不要暴露内部变量,这是一个很重要的习惯。

FactoryBean的处理

然而上述的并行化方式,对于有一种类型的bean是不适用的,那就是FactoryBean。不论有没有刻意注意过,写java应用的同学应该都接触过FactoryBean,最常见的就是我们的Mapper。FactoryBean创建bean的过程比较特殊,他会先创建一个FactoryBean的实例,然后由这个FactoryBean实例如创建出我们最终想要的bean实例。因此FactoryBean初始化的不是最终得到的实例,而是生成这个实例的工厂,而这个工厂的初始化完成与否,大概率会影响到bean的生成,因此他不能简单的将初始化过程异步化。
image.png
如图是一个Bean的获取过程:

  1. 如果这个Bean是一个单例并且没有被创建过,那么就会进入createBean,并且在其中完成初始化。
  2. 如果这个Bean是一个FactoryBean,那么createBean返回的不是bean本身,而是一个factory,真正的bean要在之后的getInstance方法中获取。

在我们的项目中,存在着大量HSFSpringConsumerBean的实例,他们都是FactoryBean,而且这些bean的初始化还相当的耗时。(HSFSpringConsumerBean是我们RPC框架HSF的consumer的FactoryBean)
好在FactoryBean也不是完全不能异步初始化,我们分析一个Bean的get获取过程,会发现,他分为createBean和getInstance两个阶段,在FactoryBean的处理中,如果能够将两个阶段人为分开,先异步完成第一阶段的调用,再触发第二阶段,就可以实现我们的目标。
image.png
如图,实现对factoryBean的加速,我们需要在FactoryBean被getBean之前将需要加速的Bean一起找出来,先手动触发它们的异步初始化,但不触发getObject方法,等这些FactoryBean初始化完成后,再交由spring按照原来的创建顺序,去触发他们的getBean方法(此时singleton已经创建,会直接进入getObject调用)。但如果这些Bean对其他的bean有依赖,可能会导致在第一步的异步初始化中产生间接依赖而触发getBean。
好消息是HSFSpringConsumerBean不对其他的bean有依赖,而且项目中绝大多数耗时的FactoryBean都是HSFSpringConsumerBean。如果在Spring初始化所有Bean之前,我们可以一次性并行把所有HSFSpringConsumerBean初始化掉,也能够获得较大的提升。对于其他不产生依赖的FactoryBean,也可以按照一样的方式处理,比如我们比较常见的mapper。
对于可能对其他bean产生依赖的FactoryBean,理论上我们也可以通过去阻塞这些bean的getBean方法,等待我们第一阶段预初始化的完成。目前项目中这些bean的比例很小,因此这部分功能尚未着手实现。

编译加速-module依赖关系优化

加速效果:☆☆☆
配置简易:☆
推荐指数:☆☆☆
目前项目使用的多mudule结构,往往含有start,mtop(接口层),service等层,其中module之间又相互依赖,导致一个低层module依赖的中间件,又会继续被上层模块解析。而往往上层模块自身非常薄,却因为依赖了低层模块,不得不反复解析庞大的依赖树,导致编译时间非常长。调整module的方式,是一种解决方案,但会破坏项目的module,失去来原来多module的优势。
我们针对含有start,mtop,service的项目,提出一种改动较小的优化方式:
image.png
如图所示:

  1. mtop层在依赖service层的时候,排除service层的所有间接依赖,仅针对mtop层自身也需要依赖的内容进行手动引入。
  2. start层依赖mtop和service,这里不能再做排除,因为项目是在start层进行打包的,如果排除,则会导致依赖包没有被正常打包到项目中而无法启动。
  3. 按照这种方式优化,能够节省在mtop层解析service依赖树的开销,往往有几十秒之多。

我们同时也提出约定,在使用这种module结构的时候,控制好mtop和service的边界:

  1. mtop层是对service提供的服务进行mtop接口级别的封装,尽量仅依赖应用内service层和common定义的服务和对象,不处理复杂的中间件逻辑
  2. 对于外部服务的使用和中间件定义的服务和对象的使用,尽量在service层封装,同时不宜将外部服务和中间件定义的对象直接透给mtop层,造成依赖扩散
  3. 这约定之后,清晰mtop层与service层的边界,mtop层就是操作service层定义的方法和对象来完成接口,涉及外部定义的方法和对象的,收口到service。

镜像治理-分层构建

加速效果:☆☆☆
配置简易:☆☆☆
推荐指数:☆☆☆☆
image.png
如图,我们使用docker进行构建时,push/pull image 的时候, 如果某一层的镜像已经存在了, 就会直接使用缓存, 跳过重复的推送和拉取过程。而正常情况下,应用打出的包 (一般是 tgz 包) 是一个整体, 即使用户只修改了一行代码, 也要打出一个完整的包,包含所有依赖的jar文件, 导致每次push和pull的时候,都要传输所有的jar包,导致效率低下。
如果在打包的时候将jar包和项目代码分到不同的层里面,在绝大多数构建中,jar包不发生变化,则需要被更新的内容大大减小,进而提升push和pull的速度。同时,在应用启动过程中,tgz包解压需要花费一定的时间,在分层打包的改造中,去除了压缩解压过程,使得速度进一步提升。
image.png
该方案对镜像构建、镜像拉取、应用启动三个过程均有提速,在idle-local中,综合收益超过30秒(一次构建加一次部署)。

应用停止过程加速

加速效果:☆☆☆☆
配置简易:☆☆
推荐指数:☆☆☆☆
停止应用过程中,比较耗时的有两个步骤:1、hsf优雅下线;2、应用停止。其中,hsf优雅下线过程,会先通知应用进行hsf provider下线,然后等待一个比较安全的时间,对于生产环境来说,15秒是相对安全的值,对于预发环境来说,可以不等待。应用停止是通过kill -0 信号进行应用停止,应用会进行一些停止前的操作,不同应用不尽相同,如果停止失败,则使用kill -9强制退出;对于已经进行HSF优雅下线并且没有其他关键退出动作的应用来说,可以直接关闭应用,可以加速停止的耗时,对于非线上环境环境来说,是比较适用的。

效果及展望

image.png
经过一系列的优化措施,我们可以看到我们的系统编译和启动过程得到了不错的优化。其中紫色部分的耗时基本可以忽略,红色部分的耗时减少了一倍。
image.png
到目前为止,我们落地了一套有效的加速方案,在idle-local上取得了不错的效果。后续我们将继续完善这一系列方案,一方面,我们将着力建设一个基于监控的长效管控方案,能够让应用长期保持较好的状态;另一方面,我们会将上述内容抽象成一个一站式落地方案,支持在其他项目快速的配置落地。

项目 优化效果 配置成本
Spring Bean异步初始化(自动) 10秒*n,随代码规格提升效果更明显 引入实现了加速的jar包
Spring Bean异步初始化(手动) 20秒*n,随代码规格提升效果更明显 引入实现了加速的jar包并配置bean
HSFSpringConsumerBean优化 10秒*n,随代码规格提升效果更明显 引入实现了加速的jar包
module依赖关系优化 40秒 需要调整pom并处理依赖
docker镜像分层 30秒 修改打包脚本
停止过程加速 40秒(非线上环境) 修改停止脚本
相关文章
|
7月前
|
人工智能 自然语言处理 开发工具
统一多模态 Transformer 架构在跨模态表示学习中的应用与优化
本文介绍统一多模态 Transformer(UMT)在跨模态表示学习中的应用与优化,涵盖模型架构、实现细节与实验效果,探讨其在图文检索、图像生成等任务中的卓越性能。
统一多模态 Transformer 架构在跨模态表示学习中的应用与优化
|
6月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
1055 3
|
9月前
|
人工智能 监控 安全
NTP网络子钟的技术架构与行业应用解析
在数字化与智能化时代,时间同步精度至关重要。西安同步电子科技有限公司专注时间频率领域,以“同步天下”品牌提供可靠解决方案。其明星产品SYN6109型NTP网络子钟基于网络时间协议,实现高精度时间同步,广泛应用于考场、医院、智慧场景等领域。公司坚持技术创新,产品通过权威认证,未来将结合5G、物联网等技术推动行业进步,引领精准时间管理新时代。
|
8月前
|
存储 编解码 Serverless
Serverless架构下的OSS应用:函数计算FC自动处理图片/视频转码(演示水印添加+缩略图生成流水线)
本文介绍基于阿里云函数计算(FC)和对象存储(OSS)构建Serverless媒体处理流水线,解决传统方案资源利用率低、运维复杂、成本高等问题。通过事件驱动机制实现图片水印添加、多规格缩略图生成及视频转码优化,支持毫秒级弹性伸缩与精确计费,提升处理效率并降低成本,适用于高并发媒体处理场景。
470 0
|
4月前
|
人工智能 JavaScript 前端开发
GenSX (不一样的AI应用框架)架构学习指南
GenSX 是一个基于 TypeScript 的函数式 AI 工作流框架,以“函数组合替代图编排”为核心理念。它通过纯函数组件、自动追踪与断点恢复等特性,让开发者用自然代码构建可追溯、易测试的 LLM 应用。支持多模型集成与插件化扩展,兼具灵活性与工程化优势。
350 6
|
5月前
|
人工智能 Cloud Native 中间件
划重点|云栖大会「AI 原生应用架构论坛」看点梳理
本场论坛将系统性阐述 AI 原生应用架构的新范式、演进趋势与技术突破,并分享来自真实生产环境下的一线实践经验与思考。
|
5月前
|
机器学习/深度学习 人工智能 vr&ar
H4H:面向AR/VR应用的NPU-CIM异构系统混合卷积-Transformer架构搜索——论文阅读
H4H是一种面向AR/VR应用的混合卷积-Transformer架构,基于NPU-CIM异构系统,通过神经架构搜索实现高效模型设计。该架构结合卷积神经网络(CNN)的局部特征提取与视觉Transformer(ViT)的全局信息处理能力,提升模型性能与效率。通过两阶段增量训练策略,缓解混合模型训练中的梯度冲突问题,并利用异构计算资源优化推理延迟与能耗。实验表明,H4H在相同准确率下显著降低延迟和功耗,为AR/VR设备上的边缘AI推理提供了高效解决方案。
676 0
|
4月前
|
机器学习/深度学习 自然语言处理 算法
48_动态架构模型:NAS在LLM中的应用
大型语言模型(LLM)在自然语言处理领域的突破性进展,很大程度上归功于其庞大的参数量和复杂的网络架构。然而,随着模型规模的不断增长,计算资源消耗、推理延迟和部署成本等问题日益凸显。如何在保持模型性能的同时,优化模型架构以提高效率,成为2025年大模型研究的核心方向之一。神经架构搜索(Neural Architecture Search, NAS)作为一种自动化的网络设计方法,正在为这一挑战提供创新性解决方案。本文将深入探讨NAS技术如何应用于LLM的架构优化,特别是在层数与维度调整方面的最新进展,并通过代码实现展示简单的NAS实验。
|
6月前
|
Web App开发 Linux 虚拟化
Omnissa Horizon 8 2506 (8.16) - 虚拟桌面基础架构 (VDI) 和应用软件
Omnissa Horizon 8 2506 (8.16) - 虚拟桌面基础架构 (VDI) 和应用软件
340 0
Omnissa Horizon 8 2506 (8.16) - 虚拟桌面基础架构 (VDI) 和应用软件