基于静态编译构建微服务应用

news2024/11/26 3:38:16

作者:饶子昊(铖朴)

Java 的局限性

传统的一个 Java 应用从代码编写到启动运行大致可以分为如下步骤:

  1. 首先,编写 .java 源代码程序。
  2. 然后,借助 javac 工具将 .java 文件翻译为 .class 的字节码,字节码是 Java 中非常重要的内容之一,正是因为它的出现,Java 才实现对底层环境的屏蔽,达到 Write once, run anywhere 的效果!
  3. 基于步骤 2 的 .class 文件会被打包成 jar 包或者 war 包进行部署执行,部署过程中通过 Java 虚拟机加载应用程序然后解释字节码运行业务逻辑。

整个过程如下图所示:

图片

图 1:Java 程序运行过程

上述过程既给 Java 程序带来了其他编程语言不具备的优势,比如跨平台,易上手等。但同时也给 Java 程序带来了一些性能问题,比如启动速度慢和运行时内存占用高等。

冷启动问题

图 1 中 Java 程序启动运行详细过程如下图 2 所示:

图片

*图 2:Java 程序的启动过程分析 [ 1] *

一个 Java 应用启动过程首先需要加载该应用程序对应的 JVM 虚拟机软件程序到内存中,如上图红色部分描述所示。然后 JVM 虚拟机再加载对应的应用程序到内存中,该过程对应上图中的浅蓝色类加载(Class Load,CL)部分。在类加载过程中,应用程序就会开始被解释执行,对应上图中浅绿色部分。解释执行过程 JVM 对垃圾对象进行回收,对应上图中的黄色部分。随着程序的运行的深入,JVM 会采用及时编译(Just In Time,JIT)技术对执行频率较高的代码进行编译优化,以便提升应用程序运行速度。JIT 过程对应上图中的白色部分。经过 JIT 编译优化后的代码对应图中深绿色部分。

经过上述分析,不难看出,一个 Java 程序从启动到达到被JIT动态编译优化会经过 VM init,App init 和 App active 几个阶段,相比于其他一些编译型语言,其冷启动问题比较严重。

运行时内存占用高问题

除了冷启动问题,从上述分析中不难看出,一个 Java 程序运行过程中,什么都不做首先就需要加载一个 JVM 虚拟机,该操作一般占用一定内存。另外,由于 Java 程序是先解释执行字节码,然后再做 JIT 编译优化。

由于相比于一些编译型语言其将编译优化的动作后置到运行时,因此非常容易出现实际加载的代码比实际需要运行的代码多很多的情况,造成了一些无效内存占用情况。综上所述就是为什么很多人常诟病 Java 程序运行内存占用高的几点主要原因。

更轻量化的 Java 程序

静态编译技术

既然,先解释执行再动态编译的 Java 传统程序运行方式存在上述诸多问题,那有没有一些方式可以让 Java 程序也跟其他程序语言,比如 C/C++ 一样,先编译后执行解决上述问题呢?

答案是肯定的,提前编译(Ahead-of-Time Compilation,AOT Compilation)或者叫静态编译在 Java 领域很早就被提了出来。其核心思想就是将 Java 程序的编译阶段提前到程序启动前,然后在编译阶段进行代码编译优化,让程序启动既巅峰,消除冷启动,降低运行时内存开销。

Java 领域静态编译的实现技术有很多,其中最具代表性的还属 Oracle 推出的 GraalVM 开源高性能多语言运行时平台 [ 2] 。看到这里有的读者可能会问:“高性能多语言运行时平台是什么?它跟静态编译本身有什么关系?”。

图片

图 3:GraalVM 多语言运行时平台

如上图 3 所示,GraalVM 中通过提供 Truffle 解释器实现框架,让开发人员可以使用 Truffle 提供的 API 快速实现特定语言的解释器从而实现对上图中各种编程语言所写的程序都能进行编译运行的效果,从而成为一个多语言运行时平台。GraalVM 实现静态编译能力的编译器就是 GraalVM JIT Compiler。静态编译框架和运行时由 Substrate VM 子项目实现,兼容 OpenJDK 运行时实现,提供了原生镜像程序运行时的异常处理、同步调度、线程管理、内存管理等功能。

因此,GraalVM 不仅可以作为一个多语言运行时平台,而且由于其中提供的 GraalVM JIT Compiler 静态编译器,其可用来对 Java 程序进行静态编译。

说完静态编译和 GraalVM 之间的关系,有的读者可能会好奇,基于 GraalVM 的静态编译与常规的 JVM 解释执行方式有哪些区别?基于静态编译的 Java 程序相比于目前应用广泛的 JVM 运行时编译 Java 程序整个从代码编写到编译执行的区别如下图 4 所示:

图片

图 4:静态编译与传统 JVM 运行过程对比

相比于 JVM 运行时方式,静态编译在运行之前会先对程序解析编译,然后生成一个跟运行时环境强相关的 native image 可执行文件,最后直接执行该文件即可启动程序进行执行。

说到这里可能有的读者又好奇,上图 4 中的静态编译过程到底会对 Java 程序做哪些解析操作?静态编译后的可执行程序垃圾回收问题怎么解决?如下图 5 所示,其描述了 GraalVM 静态编译技术实现中编译过程的输入与输出内容。

图片

图 5:静态编译输入输出

图 5 中左侧前三个输入内容 Applicaton,Libraries 和 JDK 是一个 Java 程序编译运行必备的三部分,不必多说。而 Substrate VM 就是 GraalVM 中实现静态编译的核心部分,在整个静态编译过程中扮演了重要作用。

其中在静态分析过程中,如上图 5 中间部分中所绘制,Substrate VM 通过上下文不敏感的指向分析(Points-to Analysis)来对应用程序做静态分析,其可以在不需要运行程序的情况下,基于源程序分析给出所有可能的可达函数列表然后作为后续编译阶段的输入对程序进行静态编译。该过程由于静态分析的局限性,无法覆盖 Java 中的反射、动态代理、JNI 调用等动态特性。这也造成了很多的 Java 框架由于在实现过程中使用了大量的上述特性,因此,都难以直接基于 Substrate VM 完成对自身所有代码的静态分析,需要通过额外的外部配置 [ 3] 来解决静态分析本身的不足。

例如像 Spring 社区因此开发了 AOT Engine [ 4] 如下图 6 所示来帮助解决 Spring 项目对其中的反射,动态代理等内容进行静态分析处理并将其转换为 Substrate VM 能在编译阶段可识别的内容,确保对 Spring 应用可基于 Substrate VM 顺利完成静态编译。

图片

图 6:Spring AOT Engine

在静态分析完成后,基于静态分析结果的可达函数列表,调用上文介绍的 GraalVM 中的 GraalVM JIT Compiler 编译器将应用程序编译为与目标平台强相关的本地代码以完成编译过程。

编译完成后,就会进入到上图 5 右侧 Native 可执行文件生成阶段。在该过程中,Substrate VM 会将静态编译阶段确定和初始化的内容以及跟 Substrate VM 运行时以及 JDK 库中的数据一起保存到最终可执行文件的 Image Heap 中。其中 Substrate VM 运行时就为最终可执行文件提供了运行过程中所需的垃圾回收、异常处理等能力。对于垃圾回收这块,在一开始的 GraalVM 社区版中仅提供了 Serial GC。企业版中提供了能力更强的 G1 GC。不过在最新的社区版中 GraalVM 团队也引入了 G1 GC [ 5] 以便为广大开发者提供更强大的静态编译使用能力。

适配 GraalVM 静态编译

上节,简单介绍了静态编译技术以及其本身的局限性以后,很多外部社区开发者这时可能会疑问,一个 Java 开源项目如何快速进行静态编译适配?对于这个问题,其实最核心要解决的本质问题就是将开源框架中的 GraalVM 无法识别和处理的动态内容转换为其可识别的内容即可。因此该问题由于不同框架情况不一样,因此解决方式也会有一些差异。例如在 Spring 中,针对其自身框架开发的 AOT Engine 可以解决其框架提供的通过 @Configuration 注解注册类初始化过程无法在静态编译阶段被识别、提前在静态编译期生成原本在运行阶段才能生成的动态代理类解决直接静态编译代理类无法被有效生成等问题 [ 6] 从而实现 Spring 应用的静态编译适配。

对于很多基于 Spring 实现的开源框架,如果本身无法被 GraalVM 识别的动态特性都是由于 Spring 标准的那一套用法所导致,由于自身属于 Spring 体系,静态编译过程就肯定少不了 Spring AOT Engine 的参与,因此,框架自身就不需要再提供任何适配就可以具备静态编译能力。

对于非 Spring 体系项目或者自身使用了一些 JDK 中原生的反射或者其他 Java 动态特性,针对自身代码中的 Java 动态用法需要在项目中提供对应的静态配置文件才能在静态编译过程中让编译器识别其中的动态特性,对其进行编译构建才能实现项目的顺利编译与执行。针对这种情况,GraalVM 提供了一个名叫 native-image-agent 的 Tracing Agent 来帮助大家更方便地收集元数据并准备配置文件。该 Agent 会在常规 Java VM 上的应用程序运行过程中自动收集其中的动态特性使用情况并将其转换为 GraalVM 可以识别的配置文件。最后,将通过 Agent 生成的框架自身的动态配置文件存放在项目的:META-INF/native-image/<group.id>/<artifact.id> 目录下,就可以在静态编译过程中根据这些配置内容,识别项目包中的动态特性。

Spring Cloud Alibaba 2022.0.0.0 版本所包含的所有中间件客户端目前已完成了构建 GraalVM 原生应用的适配。由于项目自身的特定,项目整体实现中有大量的 Spring 语法导致的无法被 GraalVM 识别的动态特性用法,这块内容直接交由 Spring AOT Engine 来进行解决,社区未做额外适配工作。

除了 Spring 体系语法,项目本身还是有一些其他 Java 动态用法的,这块社区通过 native-image-agent 来进行解析与动态配置生成。

基于静态编译构建微服务

Spring Cloud Alibaba 2022.0.0.0 版本所包含的所有中间件客户端已完成了构建 GraalVM 原生应用的适配。为用户提供了开箱即用的静态编译能力。相关功能体验过程如下:

环境准备

首先需要在首先在机器上安装 GraalVM 发行版。您可以在 Liberica Native Image Kit 页面上手动下载它,也可以使用像 SDKMAN! 这样的下载管理器。本文演示环境为 MacOS,如果是 Windows 可参考相应文档 [ 7] 进行操作。执行以下命令安装 GraalVM 环境:

$ sdk install java 22.3.r17-nik
$ sdk use java 22.3.r17-nik

通过检查 java -version 的输出来验证是否配置了正确的版本:

$ java -version
openjdk version "17.0.5" 2022-10-18 LTS
OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.5+8-LTS)
OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.5+8-LTS, mixed mode)

应用构建

要使用 GraalVM 静态编译能力构建微服务,首先确保您项目的 Spring Boot 版本为 3.0.0 或以上,Spring Cloud 版本为 2022.0.0 或以上。然后在项目中引入 Spring Cloud Alibaba 2022.0.0.0 版本的所需模块依赖即可。

通过以下命令生成应用中反射、序列化和动态代理所需的 Hints 配置文件,前提是应用中引入了 spring-boot-starter-parent 父模块:

$ mvn -Pnative spring-boot:run

之后应用会启动,进行预执行,需要尽可能完整的测试一遍应用的所有功能,保证应用的大部分代码都被测试用例覆盖,该过程会基于 GraalVM 的 native-image-agent 收集程序中的动态特性,这样可以确保完整生成应用运行过程中的所有必须的动态属性。运行完所有测试用例后,我们发现 resource/META-INF/native-image 目录下会生成以下一些 hints 文件:

  • resource-config.json:应用中资源 hint 文件
  • reflect-config.json:应用中反射定义 hint 文件
  • serialization-config.json:应用中序列化内容 hint 文件
  • proxy-config.json:应用中 Java 代理相关内容 hint 文件
  • jni-config.json:应用中 Java Native Interface(JNI)内容 hint 文件

注意事项:Spring Cloud Alibaba 2022.0.0.0 正式版本所有核心模块都已经默认将自身组件相关动态特性所需的配置内容都包含在了依赖中,因此上述预执行过程主要为了扫描应用自身业务代码以及其他第三方包中的动态特性,以便后续静态编译过程能顺利进行,应用能正常启动。

静态编译

以上步骤一切准备就绪后,通过以下命令来构建原生镜像:

$ mvn -Pnative native:compile

成功执行后,我们在 /target 目录可以看到生成的可执行文件。

程序运行

与普通可执行文件无异,通过 target/xxx 启动应用, 可以观察到类似如下的输出:

2023-08-01T17:21:21.006+08:00  INFO 65431 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-08-01T17:21:21.008+08:00  INFO 65431 --- [           main] c.a.cloud.imports.examples.Application   : Started Application in 0.553 seconds (process running for 0.562)

采用 GraalVM 静态编译技术的新版本 Spring Cloud Alibaba 应用,所有核心能力在启动速度和内存占用率方面如下表所示都有显著改善。

图片

说明: 上述测试代码样例来自 Spring Cloud Alibaba 项目中的 examples 模块,4c16g Mac 环境,每组数据测试 3 次取平均,具体数据因机器不同可能会有差异。

其他社区动态

新官网上线

从 2018 年的开源,到 2019 年第一个 GA 版本发布,社区已经走过了 5 个年头,项目发布了 35 个版本,累计 Star 数目超过 26k,Spring Cloud Alibaba 微服务解决被广泛地应用在各行各业的数字化转型过程中。

由于 Spring Cloud Alibaba 项目是作为跟 Spring 官方共建的一个基于 Spring Cloud 微服务解决方案标准,融入了阿里巴巴数十年微服务技术经验的一套微服务技术方案实现。项目早期的文档等相关内容一直都微服务在 spring.io 官网当中。但慢慢地社区也发现,spring.io 中由于无法添加中文内容,项目太多导致单个项目所能承载的项目或社区内容较少等问题。

因此,社区通过跟 Spring 官方沟通,在他们的支持下,基于国内开发者习惯,社区的全新社区官网正式上线。相关域名为:sca.aliyun.com,其中 sca 是项目的 Spring Cloud Alibaba 的首字母缩写。

图片

新 Committer 介绍

Spring Cloud Alibaba 社区近几个月涌现了一些积极参与社区维护迭代的外部贡献者,在此,向他们表示感谢!另外,对于其中一直参与社区活动,做出重要贡献 feature 的刘子明,社区按照新 Committer 提名与投票制度正式提名其为社区 Committer 并投票通过,成功当选,在此也向其表示祝贺!欢迎更多外部同学关注 Spring Cloud Alibaba 开源社区和贡献开源社区。

图片

相关链接:

[1] Java 程序的启动过程分析
https://shipilev.net/talks/j1-Oct2011-21682-benchmarking.pdf

[2] GraalVM 开源高性能多语言运行时平台https://www.oracle.com/java/graalvm/

[3] 外部配置https://www.graalvm.org/latest/reference-manual/native-image/metadata/

[4] AOT Engine
https://spring.io/blog/2021/12/09/new-aot-engine-brings-spring-native-to-the-next-level

[5] G1 GC
https://medium.com/graalvm/a-new-graalvm-release-and-new-free-license-4aab483692f5

[6] 动态代理类等问题
https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html#native-image.introducing-graalvm-native-images.understanding-aot-processing

[7] 相应文档
https://medium.com/graalvm/using-graalvm-and-native-image-on-windows-10-9954dc071311

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/922583.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python】强化学习:原理与Python实战

搞懂大模型的智能基因&#xff0c;RLHF系统设计关键问答 RLHF&#xff08;Reinforcement Learning with Human Feedback&#xff0c;人类反馈强化学习&#xff09;虽是热门概念&#xff0c;并非包治百病的万用仙丹。本问答探讨RLHF的适用范围、优缺点和可能遇到的问题&#xff…

再学http-为什么文件上传要转成Base64?

1 前言 最近在开发中遇到文件上传采用Base64的方式上传&#xff0c;记得以前刚开始学http上传文件的时候&#xff0c;都是通过content-type为multipart/form-data方式直接上传二进制文件&#xff0c;我们知道都通过网络传输最终只能传输二进制流&#xff0c;所以毫无疑问他们本…

pycharm的【陷阱】,你中过招吗?

一直以来&#xff0c;也有不少初学 python 的小伙伴&#xff0c;一不小心就跳进了虚拟环境和系统环境的【陷阱】中。 本文就基于此问题&#xff0c;来说说在 pycharm 当中如何使用系统环境、虚拟环境。 pycharm 当中&#xff0c;每一个项目在运行时&#xff0c;都需要指定一个…

GIS、CAD数据为基础进行城市排水系统水力建模,水力模拟在排水防涝、海绵城市设计等应用方法,城市内涝一维二维耦合水力计算原理,利用软件工具实现城市内涝模拟

目录 专题一 数据准备 专题二 建立模型 专题三 模拟计算 专题四 海绵城市关键控制指标计算 专题五 其他功能 更多应用 随着计算机的广泛应用和各类模型软件的发展&#xff0c;将排水系统模型作为城市洪灾评价与防治的技术手段已经成为防洪防灾的重要技术途径。本次将聚焦…

事件捕获和事件冒泡

事件捕获和事件冒泡与事件流有关系。 以下代码&#xff0c;点击 aa &#xff0c;控制台会打印什么呢&#xff1f; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content&q…

文件容灾备份方案,软件容灾备份方案

信息是企业的核心资产。然而&#xff0c;信息数据丢失的风险接踵而至。事故系统异常、病毒攻击、硬件损坏和自然灾害都可能导致重要数据的丢失。这就是为什么文档灾难恢复备份计划如此重要。本文将详细介绍文档灾难恢复备份计划的必要性&#xff0c;以及如何实施有效的备份方案…

数据结构(7)

B树 B树中允许一个节点拥有多个key。设定参数M&#xff0c;构造B树 1.每个结点最多右M-1个key&#xff0c;并且以升序排列 2.每个结点最多右M个子结点 3.根节点至少右两个子结点 通过磁盘预读&#xff0c;将数据放到B树中&#xff0c;3层B树可容纳1024*1024*1024差不多10亿…

数据结构基础:P3-树(上)----编程作业01:List Leaves

本系列文章为浙江大学陈越、何钦铭数据结构学习笔记&#xff0c;系列文章链接如下&#xff1a; 数据结构(陈越、何钦铭)学习笔记 文章目录 一、题目描述二、整体思路与实现代码 一、题目描述 题目描述&#xff1a; 给定一棵树&#xff0c;按照从上到下、从左到右的顺序列出所有…

Netty源码剖析之FastThreadLocal机制

版本信息&#xff1a; JDK1.8 Netty-all:4.1.38.Final 传统的ThreadLocal机制 讲netty的FastThreadLocal机制&#xff0c;就不得不提及到JDK自带的ThreadLocal机制&#xff0c;所以下面会用一小段篇幅介绍一下ThreadLocal机制&#xff5e; ThreadLocal的机制&#xff0c;大致…

【云原生】Docker私有仓库 RegistryHabor

目录 1.Docker私有仓库&#xff08;Registry&#xff09; 1.1 Registry的介绍 1.2 Registry的部署 步骤一&#xff1a;拉取相关的镜像 步骤二&#xff1a;进行 Registry的相关yml文件配置&#xff08;docker-compose&#xff09; 步骤三&#xff1a;镜像的推送 2. Regist…

Unable to Locate package python2| Linux Ubuntu系统下python2的安装

Linux Ubuntu系统下python2的安装 FSL的安装脚本是用Python2写的&#xff0c;新版本的Ubuntu &#xff08;16以后&#xff09;在默认情况下没有安装Python2。在终端输入 python2&#xff0c;若提示没有相应的命令&#xff0c;则需要先安装Python2&#xff0c;如下指令&#xf…

如何把aac转化为mp3?大家和我一起往下学习

如何把aac转化为mp3&#xff1f;aac是一种先进的音频编码格式&#xff0c;通过较小的文件大小提供出色的音质体验。然而&#xff0c;由于其相对较少的普及度&#xff0c;与MP3相比&#xff0c;兼容性稍显不足&#xff0c;有些播放器可能无法直接识别aac格式。在某种程度上&…

ORB-SLAM2算法9之图像帧Frame

文章目录 0 引言1 Frame类1.1 构造和重载函数1.1.1 双目相机1.1.2 RGBD相机1.1.3 单目相机 1.2 成员函数1.2.1 特征点去畸变1.2.2 特征点网格分配1.2.3 双目匹配1.2.4 RGBD相机深度计算 1.3 成员变量 2 Frame类的用途 0 引言 ORB-SLAM2算法7详细了解了System主类和多线程和ORB…

Spring Boot(Vue3+ElementPlus+Axios+MyBatisPlus+Spring Boot 前后端分离)【一】

&#x1f600;前言 本篇博文是关于Spring Boot(Vue3ElementPlusAxiosMyBatisPlusSpring Boot 前后端分离)【一】&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章…

华为OD机试 - 过滤组合字符串 - 深度优先搜索dfs算法(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

共享数据-vue3

vuex方案 安装vuex4.x 两个重要变动&#xff1a; 去掉了构造函数Vuex&#xff0c;而使用createStore创建仓库 为了配合composition api&#xff0c;新增useStore函数获得仓库对象&#xff1b;获取路由对象使用useRouter global state 由于vue3的响应式系统本身可以脱离…

node.js 简单实验 创建一个简单的web服务

概要&#xff1a;用一个最简单是例子感受一下node.js 的能力 1.代码 var http require("http") http.createServer(function (request, response) { response.writeHead(200, {Content-Type: text/plain}); response.end(Hello World\n); }).listen(8081); cons…

浪潮信息企业级SSD:降本又增效?AIPR技术解决高并发读取性能大问题

NAND闪存作为一种非易失性存储介质&#xff0c;凭借其功耗低、重量轻、性能佳和断电后仍然能保存数据等特点&#xff0c;成为比硬盘驱动器更好的存储设备&#xff0c;非常适合作为便携设备的存储器来使用。 固态硬盘&#xff08;Solid State Disk&#xff0c;简称SSD&#xff0…

灯笼解算—kinfx

kinfx 刚刚开始学 做的可能比较复杂了。 skleton ——画的骨骼 rigpose 主要控制动态 sin((0.05*Framech(“…/Ctrol/ofset”))*ch(“…/Ctrol/freq”))*ch(“…/Ctrol/amp”) 用的简单的sin函数 变成实心 方便控制弯曲 原地做完 匹配ani位置 matrix mat point(1, transform…

智慧能源管理系统助力某制造企业提高能源利用效率

随着全球能源需求不断增加和能源价格的上涨&#xff0c;企业和机构日益意识到能源管理的重要性。传统的能源管理方式不仅效率低下&#xff0c;还容易造成资源浪费和环境污染。因此&#xff0c;许多企业开始探索采用智慧能源管理系统来提高能源利用效率&#xff0c;降低能源成本…