0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Java与云原生的矛盾原因

jf_ro2CN3Fa 来源:芋道源码 作者:芋道源码 2022-10-31 11:15 次阅读

0. 前言

前阵子在 B 站刷到了周志明博士的视频,主题是云原生时代下 Java,主要内容是云原生时代下的挑战与 Java 社区的对策。这个视频我在两年前看到过,当时也是印象深刻。现在笔者也是想和大家一起看看相关项目的推进以及一些细节。这篇笔记会大量参考视频中提到的内容,如果读者看过相关视频,可以跳过这篇笔记。

视频分享中提到,Java 与云原生的矛盾大概原因有二:

首当其冲的是 Java 的“一次编写,到处运行”(Write Once, Run Anywhere) 。在当年是非常好的做法,直接开启了许多托管语言的兴盛期。但云原生时代大家会选择以隔离的方式,通过容器实现的不可变基础设施去解决。虽然容器的“一次构建,到处运行”(Build Once, Run Anywhere)和 Java 的“一次编写,到处运行”(Write Once, Run Anywhere)并不是一个 Level 的——容器只能提供环境兼容性和有局限的平台无关性(指系统内核功能以上的 ABI 兼容),但服务端的应用都跑在 Linux 上,所以对于业务来说也无伤大雅。

其二,则是 Java 总体上是面向长时间的“巨塔式”服务端应用而设计的

静态类型动态链接的语言结构,利于多人协作开发,让软件触及更大规模;

即时编译器、性能制导优化、垃圾收集子系统等 Java 最具代表性的技术特征,都是为了便于长时间运行的程序能享受到硬件规模发展的红利。

但在,微服务时代是提倡服务围绕业务能力(不同的语言适合不同的业务场景)而非技术来构建应用,不再追求实现上的一致。一个系统由不同语言、不同技术框架所实现的服务来组成是完全合理的。

服务化拆分后,很可能单个微服务不再需要再面对数十、数百 GB 乃至 TB 的内存。有了高可用的服务集群,也无须追求单个服务要 7×24 小时不可间断地运行,它们随时可以中断和更新。不仅如此,微服务对镜像体积、内存消耗、启动速度,以及达到最高性能的时间等方面提出了新的要求。这两年的网红概念 Serverless(以及衍生出来的 Faas) 也进一步增加这些因素的考虑权重。

而这些却正好都是 Java 的弱项:哪怕再小的 Java 程序也要带着厚重的 Rumtime(Vm 和 StandLibrary)——基于 Java 虚拟机的执行机制,使得任何 Java 的程序都会有固定的内存开销与启动时间,而且 Java 生态中广泛采用的依赖注入进一步将启动时间拉长,使得容器的冷启动时间很难缩短。

举两个例子。

软件工业中已经出现过不止一起因 Java 这些弱点而导致失败的案例。如 JRuby 编写的 Logstash,原本是同时承担部署在节点上的收集端(Shipper)和专门转换处理的服务端(Master)的职责,后来因为资源占用的原因,被 Elstaic.co 用 Golang 的 Filebeat 代替了 Shipper 部分的职能。

又如 Scala 语言编写的边车代理 Linkerd,作为服务网格概念的提出者,却最终被 Envoy 所取代,其主要弱点之一也是由于 Java 虚拟机的资源消耗所带来的劣势。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro

视频教程:https://doc.iocoder.cn/video/

1. 变革之火

1.1 Complie Native Code

显然,如果将字节码直接编译成可以脱离 Java 虚拟机的原生代码则可以解决所有问题。

如果真的能够生成脱离 Java 虚拟机运行的原生程序,将意味着启动时间长的问题能够彻底解决,因为此时已经不存在初始化虚拟机和类加载的过程。也意味着程序马上就能达到最佳的性能,因为此时已经不存在即时编译器运行时编译,所有代码都是在编译期编译和优化好的。同理,厚重的 Runtime 也不会出现在镜像中。

Java 并非没有尝试走过这条路。

从 GCJ 到 Excelsior JET 再到 GraalVM 中的 SubstrateVM 模块再到 2020 年中期建立的 Leyden 项目,都在朝着提前编译(Ahead-of-Time Compilation,AOT)生成原生程序这个目标迈进。Java 支持提前编译最大的困难在于它是一门动态链接的语言,它假设程序的代码空间是开放的(Open World),允许在程序的任何时候通过类加载器去加载新的类,作为程序的一部分运行。要进行提前编译,就必须放弃这部分动态性,假设程序的代码空间是封闭的(Closed World),所有要运行的代码都必须在编译期全部可知。

这一点不仅仅影响到了类加载器的正常运作。除了无法再动态加载外,反射(通过反射可以调用在编译期不可知的方法)、动态代理、字节码生成库(如 CGLib)等一切会运行时产生新代码的功能都不再可用——如果将这些基础能力直接抽离掉,Hello world 还是能跑起来,大部分的生产力工具都跑不起来,整个 Java 生态中绝大多数上层建筑都会轰然崩塌。

随便列两个 Case:Flink 的 SQL API 会解析 SQL 并生成执行计划。这个时候会通过 JavaCC 动态生成类加载到代码空间中去;Spring 也有类似的情况,当 AOP 通过动态代理的方式去生成相关逻辑时,本质还是在 Runtime 时生成代码并加载进去。

要获得有实用价值的提前编译能力,只有依靠提前编译器、组件类库和开发者三方一起协同才可能办到——可以参考 Quarkus。

Quarkus 和我们上述的方法如出一辙,以 Dependency Inject 为例:所有要运行的代码都必须在编译期全部可知,在编译期就推导出来相关的 Bean,最后交给 GraalVM 来运行。

1.2 Memory Access Efficiency Improvement

Java 即时编译器的优化效果拔群,但是由于 Java “一切皆为对象”的前提假设,导致它在处理一系列不同类型的小对象时,内存访问性能很差。这点是 Java 在游戏、图形处理等领域一直难有建树的重要制约因素,也是 Java 建立 Valhalla 项目的目标初衷。

这里举个例子来说明此问题,如果我想描述空间里面若干条线段的集合,在 Java 中定义的代码会是这样的:

publicrecordPoint(floatx,floaty,floatz){}
publicrecordLine(Pointstart,Pointend){}
Line[]lines;

面向对象的内存布局中,对象标识符(Object Identity)存在的目的是为了允许在不暴露对象结构的前提下,依然可以引用其属性与行为,这是面向对象编程中多态性的基础。在 Java 中堆内存分配和回收、空值判断、引用比较、同步锁等一系列功能都会涉及到对象标识符,内存访问也是依靠对象标识符来进行链式处理的,譬如上面代码中的“若干条线段的集合”,在堆内存中将构成如下图的引用关系:

9b7d204c-583d-11ed-a3b6-dac502259ad0.jpg

计算机硬件经过 25 年的发展,内存与处理器虽然都在进步,但是内存延迟与处理器执行性能之间的冯诺依曼瓶颈(Von Neumann Bottleneck)不仅没有缩减,反而还在持续加大,“RAM Is the New Disk”已经从嘲讽梗逐渐成为了现实。

一次内存访问(将主内存数据调入处理器 Cache)大约需要耗费数百个时钟周期,而大部分简单指令的执行只需要一个时钟周期而已。因此,在程序执行性能这个问题上,如果编译器能减少一次内存访问,可能比优化掉几十、几百条其他指令都来得更有效果。

额外知识:冯诺依曼瓶颈 不同处理器(现代处理器都集成了内存管理器,以前是在北桥芯片中)的内存延迟大概是 40-80 纳秒(ns,十亿分之一秒),而根据不同的时钟频率,一个时钟周期大概在 0.2-0.4 纳秒之间,如此短暂的时间内,即使真空中传播的光,也仅仅能够行进 10 厘米左右。

数据存储与处理器执行的速度矛盾 是冯诺依曼架构的主要局限性之一,1977 年的图灵奖得主 John Backus 提出了“冯诺依曼瓶颈”这个概念,专门用来描述这种局限性。

Java 编译器的确在努力减少内存访问,从 JDK 6 起,HotSpot 的即时编译器就尝试通过逃逸分析来做标量替换(Scalar Replacement)和栈上分配(Stack Allocations)优化。基本原理是,如果能通过分析得知一个对象不会传递到方法之外,那就不需要真实地在对象中创建完整的对象布局。完全可以绕过对象标识符,将它拆散为基本的原生数据类型来创建,甚至是直接在栈内存中分配空间(HotSpot 并没有这样做),方法执行完毕后随着栈帧一起销毁掉。

不过,逃逸分析是一种过程间优化(Interprocedural Optimization),非常耗时,也很难处理那些理论上有可能但实际不存在的情况。这意味着它是 Runtime 时发生的 。而相同的问题在 C、C++ 中却并不存在,上面场景中,程序员只要将 Point 和 Line 都定义为 struct 即可,C# 中也有 struct,是依靠 .NET 的值类型(Value Type)来实现的。这些语言在编译期就解决了这些问题。

而 Valhalla 的目标就是提供类似的值类型支持,提供一个新的关键字(inline),让用户可以在不需要向方法外部暴露对象、不需要多态性支持、不需要将对象用作同步锁的场合中,将类标识为值类型。此时编译器就能够绕过对象标识符,以平坦的、紧凑的方式去为对象分配内存。

Valhalla 目前还处于 Preview 阶段。可以在这里看到推进的情况。希望能在下个 LTS 版本正式用上它吧。

1.3 Coroutine

Java 语言抽象出来隐藏了各种操作系统线程差异性的统一线程接口,这曾经是它区别于其他编程语言的一大优势。不过,这也是曾经。

Java 目前主流的线程模型是直接映射到操作系统内核上的 1:1 模型,这对于计算密集型任务这很合适,既不用自己去做调度,也利于一条线程跑满整个处理器核心。但对于 I/O 密集型任务,譬如访问磁盘、访问数据库占主要时间的任务,这种模型就显得成本高昂,主要在于内存消耗和上下文切换上。

举个例子。64 位 Linux 上 HotSpot 的线程栈容量默认是 1MB,线程的内核元数据(Kernel Metadata)还要额外消耗 2-16KB 内存,所以单个虚拟机的最大线程数量一般只会设置到 200 至 400 条,当程序员把数以百万计的请求往线程池里面灌时,系统即便能处理得过来,其中的切换损耗也相当可观。

Loom 项目的目标是让 Java 支持额外 的 N:M 线程模型,而不是像当年从绿色线程过渡到内核线程那样的直接替换,也不是像 Solaris 平台的 HotSpot 虚拟机那样通过参数让用户二选其一。

Loom 要做的是一种有栈协程(Stackful Coroutine),多条虚拟线程可以映射到同一条物理线程之中,在用户空间中自行调度,每条虚拟线程的栈容量也可由用户自行决定。

此外,还有两个重点:

尽量兼容所有原接口。这意味着原来所有的线程接口都可以当作协程使用。但我觉得挺难的——假如里面的代码调到 Native 方法,这个 Stack 就和这个线程绑定了,毕竟 Coroutine 是个用户态的东西。

支持结构化并发:简单来说就是异步的代码写起来像同步的代码,这点 GO 做的很好。毕竟嵌套的回调函数着实让人痛苦。

上述的内容如果拆开来细说,基本就是:

协程的调度;

协程的同步、互斥与通讯;

协程的系统调用包装,尤其是网络 IO 请求的包装;

协程堆栈的自适应。

小知识:每个协程,都有一个自己专享的协程栈。这种需要一个辅助的栈来运行协程的机制,叫做 Stackful Coroutine;而在主栈上运行协程的机制,叫做 Stackless Coroutine。

Stackless Coroutine意味着:

运行时:活动记录放在主线程的栈上

暂停时:堆中保留活动记录

可以调用其他函数

只能在顶层暂停运行,不可以在子函数/子协程里暂停

而Stackfull Coroutine意味着:

运行时:单独的运行栈

可以在调用栈的任何一级暂停

生命周期可以超过它的创建者

可以从一线程上跑到另一个线程上

因此,一个完备的协程库基本顶得上一个操作系统里的进程部分了。只是它在用户态,进程在内核态。

这个项目可以在这里看到。目测 JDK 19 就可以尝尝鲜了。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://gitee.com/zhijiantianya/yudao-cloud

视频教程:https://doc.iocoder.cn/video/

2. 小结

目前在云原生领域,Java 可能未必是好的选择——在这个领域最让人难以忍受的就是其庞大的 Runtime 以及较长的 Startup 时间,在以前这是 Java 优点的来源,但到了云原生时代,则成了 Java 显而易见弱点。因此 Java 想在云原生时代继续保持前几十年的趋势,解决这个问题迫在眉睫。从这个点来看,我很看好 Quarkus。

Valhalla 带来的优化很多场景都可以用上,一些长时间运行应用也可以获得更多的性能收益。

而协程针对的是 IO 密集型场景,本身也可以通过 NIO、AIO 方式来避免线程的大量消耗。因此 Loom 在笔者看来更像是锦上添花的事。

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2954

    浏览量

    104511
  • 容器
    +关注

    关注

    0

    文章

    492

    浏览量

    22035
  • 编译器
    +关注

    关注

    1

    文章

    1617

    浏览量

    49026
  • 云原生
    +关注

    关注

    0

    文章

    240

    浏览量

    7934

原文标题:追随云原生的 Java

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    进击的 Java云原生时代的蜕变

    导读:云原生时代的来临,与Java 开发者到底有什么联系?有人说,云原生压根不是为了 Java存在的。然而,本文的作者却认为云原生时代,
    发表于 09-17 15:54

    只需 6 步,你就可以搭建一个云原生操作系统原型

    编者按: 过去的三年对基础软件领域来说是不平凡的三年,是波涛汹涌的三年。随着国际形势和行业格局的变化,大家一定充分感受到了云原生和操作系统这两个话题的热度。那么当云原生和操作系统这两个热点话题相遇
    发表于 09-15 14:01

    华为云发布云原生基础设施,云原生2.0时代正式开启

    早在2010年,Paul Fremantle在一篇博客文章中首次提到了Cloud Native(云原生)。当时的云原生特指适合云计算模式的应用程序编写方式。可能Paul Fremantle也不会想到,十年过去了,云原生会引领IT
    的头像 发表于 09-30 11:25 2220次阅读

    云原生应用中的“云”指的是什么?

    云原生应用是独立的小规模松散耦合服务的集合,旨在提供备受认可的业务价值,例如快速融合用户反馈以实现持续改进。简而言之,通过云原生应用开发,您可以加速构建新应用,优化现有应用并在云原生架构中集成。其
    的头像 发表于 11-27 17:24 2178次阅读

    华为云正式提出云原生2.0的概念

    华为云发布云原生产业白皮书,并提出云原生2.0的概念。
    的头像 发表于 12-07 11:51 3690次阅读

    云原生时代的Java,仍在受欢迎吗

    Java 诞生距今已有 25 年,但它仍然长期占据着“天下第一”编程语言的宝座。只是其统治地位并非坚不可摧,反倒可以说是危机四伏。云原生时代,Java 技术体系的许多前提假设都受到了挑战,目前已经有
    的头像 发表于 12-07 14:35 1357次阅读

    引领云原生2.0时代,赋能新云原生企业

    十年云计算浪潮下,DevOps、容器、微服务等技术飞速发展,云原生成为潮流。Forrester首席分析师戴鲲表示,云原生是企业数字化转型的基础,企业需要建立云原生优先的战略,构建一体化全栈云原
    的头像 发表于 12-11 16:04 1792次阅读

    云原生解决了什么问题?

    尽管Heroku、Pivotal、CNCF等众多厂商都对云原生下了不同的定义,但从本质上考虑,因为云原生构建了易观测松耦合容错性高的系统,所以其始终都在追求着三大目标:加速创新、降低成本、提高效率。
    的头像 发表于 12-15 17:15 6836次阅读
    <b class='flag-5'>云原生</b>解决了什么问题?

    云原生2.0时代 我们还要做什么?

    华为云自2015年以创始会员的身份参与了云原生计算基金会的组建,在过去的这5年时间里,华为云全面见证了云原生技术和产业的兴起和发展:开源项目能力的完善期、云原生产业的发展与融合期,再到如今,
    的头像 发表于 12-21 13:36 1795次阅读

    如何更好地构建云原生应用生态,推动业界更好地落地云原生

      近年来,“云原生”成为炙手可热的概念,云原生技术在制造、政务、电信、金融等垂直行业的应用占比也在快速攀升,有力地支撑了业务系统重构,越来越多的企业和开发者开始把业务与技术向云原生演进。   中国
    的头像 发表于 12-24 11:13 2581次阅读

    解读腾讯云原生 鹅厂云原生的“新路”与“历承”

    在云计算产业中,云原生是一个长期讨论的“老话题”。而在今年新基建、产业数字化的宏观背景下,云原生的应用主体开始扩张,关于这条技术路径的讨论也重新火热了起来。 云原生突然“翻红”的原因
    的头像 发表于 12-28 18:10 3470次阅读

    华为云中什么是云原生服务中心

    云原生服务中心(Operator Service Center,OSC)是面向服务提供商和服务使用者的云原生服务生命周期治理平台,提供大量的云原生服务,并使用自研部署引擎,支持所有服务包统一管理
    发表于 07-27 15:44 684次阅读
    华为云中什么是<b class='flag-5'>云原生</b>服务中心

    什么是分布式云原生

    什么是分布式云原生 华为云分布式云原生服务(Ubiquitous Cloud Native Service, UCS)是业界首个分布式云原生产品,为企业提供云原生业务部署、管理、应用生
    发表于 07-27 15:52 1548次阅读

    京东云原生安全产品重磅发布

    “安全产品那么多,我怎么知道防住了?”“大家都说自己是云原生的,我看都是换汤不换药”在与客户沟通云原生安全方案的时候,经常会遇到这样的吐槽。越来越的客户已经开始了云原生化的技术架构改造,也意识到
    的头像 发表于 07-26 10:36 410次阅读
    京东<b class='flag-5'>云原生</b>安全产品重磅发布

    云原生和非云原生哪个好?六大区别详细对比

    云原生和非云原生各有优劣,具体选择取决于应用场景。云原生利用云计算的优势,通过微服务、容器化和自动化运维等技术,提高了应用的可扩展性、更新速度和成本效益。非云原生则可能更适合对延迟敏感
    的头像 发表于 09-13 09:53 311次阅读