现代Java平台-2021年版

早在2000年代初期,许多开发人员就被Java过于复杂的世界所吓坏。四种模式和中间件/ J2EE / Java EE的组合导致所谓的脱钩的荒谬程度,从我在2002年研究的开源J2EE电子商务系统的此序列图中可以明显看出:

现代Java平台-2021年版

早在2014年,我就发生了什么变化写了一篇文章:Java不烂,您只是在错误地使用了它。但是自从我写这篇文章以来已经过去了六年,并且事情还在不断改善,这使得Java平台成为构建微服务,数据管道,Web应用程序,移动应用程序等的绝佳选择。让我们来看一下Java平台的一些“现代”(截至2021年)方面。

三种排名前20位的编程语言

Java既是语言也是平台。老实说,过去十年来我没有写太多实际的Java,但是我已经在Java虚拟机(JVM)上构建了相当多的东西。Java语言在不断发展。Java语言架构师Brian Goetz将其描述为“最后的优势”:在其他语言彻底证明某个功能有用之后,再添加这些功能。对于数以百万计的Java开发人员而言,创新步伐缓慢是一件好事,他们能够继续专注于降低由演化引起的风险。长期以来,向后兼容一直是Java语言的一项重要功能,这是对于那些喜欢缓慢前进而可能又一无所获的企业而言。

除了Java语言本身之外,Java生态系统还涵盖了其他流行的语言,包括Scala和Kotlin。实际上,过去十年来我编写的大多数代码都在Scala中使用,Scala现在是第14大最受欢迎的编程语言(根据RedMonk)。对于我来说,Scala经历了一段旅程,从“没有分号的Java”方法开始,到现在尝试完全采用静态类型的函数式编程。Scala继续尝试即将进行的实验,即将发布的Scala 3版本结束了多年的博士学位研究,并且马丁·奥德斯基(Martin Odersky)将类型系统建立在可验证演算基础上的宏伟愿景。我真的很喜欢编写Scala代码并不断改进。感觉非常前沿,与Go等更古老的语言相比,我的个人生产力感觉要好得多。

在过去的两年中,我编写了很多Kotlin代码。Kotlin现在被RedMonk排名第18位,紧随Go之后。当Google决定将其设置为Android开发的默认设置时,Kotlin的使用量猛增。但是我没有做太多的Android编程。Kotlin已经成为构建后端的一种非常有用的语言。类型推断,显式可空性和结构化并发(协程)等语言功能使其非常适合构建现代服务器。当我写Kotlin时,我确实会错过Scala中的一些东西,但是总的来说,我的工作效率很高,而且Java开发人员更容易编写代码。

Scala和Kotlin使用共享工具,库等在Java平台生态系统上构建。例如,Netty(一个Java库)可能是按类型处理最多全局流量的HTTP服务器。它几乎存在于所有大型企业系统中。许多Scala和Kotlin服务器框架都使用Netty,因为互操作性才有效。

如果您离开Java语言已有一段时间了,并且想再看看一下,请查看Kotlin。它具有许多现代语言功能,不会有太大的不同。有关一些不错的功能的概述,请查看我的视频:Kotlin-更好,更云友好的Java。如果您想跳到更现代的东西,请查看Scala,其中最近的大部分精力都在编写并发和可组合的代码上,这些代码可以被编译器验证为正确。我真的很喜欢ZIO,这是一个Scala框架,可为纯功能程序提供类似Lego的体验。

专业和成熟的工具

当我在其他平台上使用开发工具时,通常会感到失望。我其实不需要在系统上安装一些特殊的本机库,因此可以安装依赖项。我其实也不需要花几个小时就可以设置本地开发人员工具链。IDE中的代码提示应该准确,快速。构建系统应该提供执行常见任务的标准方法:构建,运行,测试,调用静态代码工具,重新格式化代码以及我们一直在做的其他工作。

在Java平台世界中,有一些用于构建工具和IDE的选项。我在IntelliJ IDEA for Java,Kotlin和Scala方面拥有丰富的经验。但是VS Code也能很好地工作。使用Java和Kotlin时,我发现Gradle或Maven构建工具是成熟,快速且功能齐全的。在Scala中,我使用sbt,它提供了我理想的开发人员经验。所有这些都在IntelliJ中得到直接支持,因此我的构建系统和IDE对依赖项具有一致的理解。

使用数据库的应用程序通常不使用生产数据库类型进行开发和本地集成测试,因为这太痛苦了,有时速度很慢。这导致许多开发人员将内存数据库用于开发和测试。反过来,由于开发数据库和生产数据库之间的差异,这导致了巨大的问题和局限性。一些开发人员已开始使用具有数据库依赖性的Docker容器来提供一致性。但这在处理数据库的生命周期方面造成了痛苦。Testcontainers项目通过在应用程序生命周期中管理数据库(或其他容器化服务),已经成为解决此问题的好方法。例如,集成测试可以根据需要启动Postgres数据库,以进行一系列测试,每个测试用例或所有测试,并且最好将所有生命周期作为测试本身的一部分进行管理。

减少时间来验证更改对于生产力至关重要。在典型的开发人员工作流程中,我可以在几秒钟内测试我正在处理的内容。这意味着编译器会运行(根据我使用的语言和框架,验证级别会有所不同),正在运行的测试以及运行的测试(如果正在构建Web应用程序),则服务器会重新启动,并且浏览器会自动重新加载。除了保存文件之外,所有这些事情都可以发生而无需我采取任何行动。Gradle和sbt通过连续的构建/测试/开发模式立即支持这种开箱即用的功能。Quarkus框架在Maven中也支持此功能。

生产框架

在Java生态系统中,您只需要编写直接处理HTTP请求并将代码硬链接到调用链的代码即可。但是通常使用框架来简化如何将代码库的各个部分连接在一起以供重用和用于不同的环境(开发,测试,生产)。接线方式有不同的方法。Java生态系统中最典型的方法称为“依赖注入(DI)”,并已由Spring Framework普及。

Spring Boot(使用Spring框架的“现代”方式)是Java生态系统框架的王者,可与Java和Kotlin一起很好地工作。当我与面向非功能性程序员的团队一起编写代码时,通常会使用Kotlin和Spring Boot。该代码通常易于遵循,并且开发人员的经验非常好。该区域有许多替代方案,但以下两种方案比较突出,您可能要使用它们的原因:

要进行更全面的比较,请观看我的演讲:Kotlin Server Framework Smackdown

借助Scala,还有各种各样的框架,从类似Spring Boot到功能强大的框架。我一直是Play Framework开发人员工作效率的忠实拥护者,但是如果您想要进行这种风格的编程,请使用Kotlin。当您更深入地使用函数式编程时,Scala确实会发光。因此,ZIO是我的主要建议,但是还没有完善的Web框架。有一些库和基本的片段,但是还没有什么能与Play和Spring Boot的端到端框架体验相匹配。一个解决方案正在开发中,但是如果您现在想使用Scala为Web应用程序提供纯功能,最好的选择是http4s,它可以与ZIO结合使用。

反应性应用不断普及

无阻塞/响应性是“现代”与传统的主要标界元素之一。当传统系统仅在等待某些事情发生时(例如数据库或Web服务进行响应),它们通常会使用大量资源。这显然很愚蠢,但是要修复它通常需要新的编程模型,因为传统的命令性代码无法暂停并在等待结束后恢复。Java长期以来一直使用无阻塞IO(NIO),并且大多数网络库都已经使用了很长时间,其中Netty是最常用的。遗留代码库可能仍在使用阻塞API,因为进行响应式操作会带来复杂性障碍。直到最近,还没有成熟的反应式数据库库。您必须始终保持被动反应,以获取足够的价值来抵消成本。

Spring Boot以及大多数其他Java和Kotlin框架已经完全采用了反应式,并将其作为其“现代化”故事的核心部分。最近,甚至数据库层也通过R2DBC库获得了出色的响应式支持。但是,并不是所有的ORM /数据映射库都支持此功能。同样,大多数HTTP客户端库都支持反应式编程,但并非全部。

如果您使用Kotlin,则可以利用协程进行并发/与Spring Boot或Micronaut交互。结构化的并发模型使响应式操作几乎与典型的命令式编程一样容易。只要确保您正在使用的库在后台也没有阻塞即可。

反应式在Scala中已经很久了,Scala世界中的一些人创建了反应式宣言。与大型编程社区中的所有内容一样,您可以通过多种方式来进行响应。对于HTTP客户端,请支持ZIO的 sttp。对于数据库客户端,我真的很喜欢Quill,它支持各种非阻塞驱动程序。

反应式事件驱动/流式

除了典型的面向请求的体系结构(Web应用程序和REST服务)之外,还有各种体系结构模式被归类为“事件驱动”或“流”,它们也已经变得Reactvie。

Akka的参与者模型是一种用于进行响应式/事件驱动式消息传递的框架。行为者的核心好处之一是监督,当事情出错时,监督可以很容易地恢复。Akka具有内置的Java和Scala支持以及一种处理网络集群参与者的方法。在Akka actor的上方,有一个称为“ Akka Streams”的流处理框架,我将它与Scala一起用于生产中,用于在Kafka上进行实时事件处理。

还有许多其他流处理框架和库,包括ZIO流,Flink,ksqlDB和Spark的微批处理。对于许多此类方法,Kafka已成为一种典型的消息总线,因为它对水平缩放,消息重放,每个主题的持久性设置以及最多一次/最少一次/有效一次的传递提供了强大的支持。

在传统体系结构中,通过更新数据库来处理事件,然后丢弃原始事件,从而使数据库成为事实的来源。在微服务架构和分布式系统中,这种方法充满了一些问题,例如最终的数据一致性,无法扩展以及添加新的数据处理客户端困难。为了克服这些问题,出现了新的体系结构模式,包括命令查询责任隔离(CQRS),事件源(ES)和无冲突复制数据类型(CRDT)。

CQRS将真值源从可变数据存储区切换到不可变事件流。这为可伸缩性,分发和附加新客户端/微服务提供了更好的方法。通常,您仍然需要一个“物化视图”来表示通过处理所有事件计算出的状态。使用CQRS的好处是,您始终可以通过重播所有事件来重新计算实例化视图,但是在大型数据集中可能要花费很长时间。快照是处理此问题的好方法,并且由于Kafka能够从给定的时间点重放,因此从快照重建实例化视图很简单。

CQRS的最大缺点是编程模型相当低级。幸运的是,ksqlDB和Cloudstate使事情变得更加轻松。我曾经和Kotlin一起使用过,并且有很好的经验。有关此方法的更多详细信息,请查看我关于Cloudstate的博客:带有Cloudstate和Akka Serverless的Google Cloud上的无状态服务器。

容器(不是J2EE品种)

在J2EE / Java EE时代,我们将应用程序服务器称为“容器”,因为它们运行打包为“ Web Application aRchives”或WAR文件的Web应用程序。今天,我们通常仍在容器中运行我们的应用程序,但它们现在是多语言的,支持许多不同的运行时。运行容器映像的流行环境包括Kubernetes,Docker和Cloud Run。有多种方法可以在Java生态系统中创建容器映像,包括Dockerfiles,Jib和Buildpacks。有关这些方法之间的差异的更多信息,请查看我的博客:比较容器化方法:Buildpacks,Jib和Dockerfile。

创建容器时,请选择提供操作系统和JVM的“基础映像”。有许多不同的选项,其中大多数是OpenJDK的变体,OpenJDK是Java平台标准版的开源参考实现。如果不确定使用哪个JVM基本映像,则可以尝试使用精简的Distroless Java映像:

OpenJDK的AdoptOpenJDK构建也是一个不错的选择,但它们可以在DockerHub上使用。

一些JVM在容器中运行时可以自动选择设置。例如,HotSpot JVM根据CPU数量和RAM数量来更改其使用的垃圾收集器。为了减少垃圾收集暂停,OpenJ9 JVM会检测CPU何时处于空闲状态,然后运行垃圾收集器。过去几年发布的OpenJDK版本会根据容器分配的RAM自动确定内存设置,并具有分配给容器进程的CPU资源的一致视图。

Java,Kotlin和Scala都在容器中运行良好,并且某些框架支持现成的容器化:Spring Boot容器化,Micronaut容器化(Gradle | Maven)和Quarkus容器化。否则,您可以轻松地使用构建工具插件来创建您的容器映像。

无服务器并避免JVM开销

JVM能够在运行的更多(或更长时间)内优化执行,这非常适合您购买服务器的典型数据中心使用情况,因此即使它们有时利用率较低,也可以使其保持运行状态。在云中,我们不必那样做。相反,我们可以使用仅在使用资源时分配资源的模型。这就是所谓的“无服务器”,它的缺点是服务器通常不会长时间处于运行状态,这在某种程度上消除了JVM的某些价值。由于无服务器是基于需求的,因此当有请求进入时,如果没有足够的后备资源来处理该请求,则需要启动一个新实例。此请求是“冷启动”,它们可能是对您的P99延迟的真正拖累。

JVM的冷启动会使用户等待不舒服的时间。想象一下,您单击购物车上的“结帐”按钮,并且在JVM启动,处理运行时DI批注,启动服务器,缓存缓存等期间,15秒钟似乎什么都没有发生。解决此问题的一种方法是不将JVM用于基于JVM的应用程序。什么??似乎是不可能的,但这正是GraalVM Native Image能够实现的。它可以提前将基于JVM的应用程序编译为本地可执行文件,该应用程序在极短的时间内启动并使用较少的内存,但可能看不到“热”的性能达到基于JVM的应用程序的水平。

GraalVM本机镜像很神奇,但是当然有一些警告。提前编译意味着Java中的其他一些魔术会变得棘手。Java中的许多库都使用运行时自省和修改(称为反射)来动态处理诸如依赖注入和序列化之类的事情。这些动态的神奇潜伏龙无法提前编译,因此要使用GraalVM Native Image,您必须将所有动态的东西告诉它。这可能有些棘手-可能但很棘手。主要的框架正在努力使这一过程变得容易且有些自动化,但是我经常遇到问题。

某些Scala东西确实在这里大放异彩,因为函数式程序员通常不喜欢动态的魔术龙,因为它们本质上并不是纯粹的函数。ZIO和http4s都是一个不错的选择。我在生产中有一台GraalVM Native Imageified http4s服务器,该服务器在100毫秒内启动,具有16MB的容器映像。因为我建立在一些纯粹的功能基础上,所以使用GraalVM Native Image进行提前编译非常容易。

通常,对GraalVM本机镜像的支持正在改善,并且在未来几年中,我确信大多数现代Java,Kotlin和Scala程序都将没有JVM的情况下运行。提前编译确实需要一些时间,所以这只是我在CI / CD管道中所做的。我仍然使用JVM进行本地开发,以保持我的开发和测试迭代超级快。

恐惧,不确定性,怀疑和治理

OpenJDK是一个常规的开源项目,具有多供应商/分布式电源管理结构。Java社区流程和JDK增强建议为任何人提供了贡献的方法。Kotlin和Scala都由拥有典型开源治理模型的基金会所拥有。因此,在大多数方面,核心Java平台技术都可以像其他自由开放式编程平台一样工作。但是,有一些部分是专有的。要使用Java品牌(Oracle拥有)标记自定义JDK,它必须通过技术兼容性套件中的测试,该套件必须为此目的而从Oracle获得许可。Java语言API也可能具有版权。

在任何编程平台上,都有被锁定的风险,从而导致意外的成本。幸运的是,已有一些减轻这些风险的方法,例如将Kotlin或Scala与AdoptOpenJDK一起使用。

未来

Java生态系统在许多方面都在不断创新。在语言方面,Java,Kotlin和Scala都朝着不同的方向发展,但其效果却有所共享。例如,Scala的模式匹配可能是所有编程语言中最好的之一。这在某种程度上有助于激发Kotlin和Java中更好的模式匹配。JVM在垃圾回收和性能方面也进行了大量创新。当Project Loom(JVM上的光纤和延续)成熟时,反应式编程将变得更加容易。GraalVM是一项了不起的工程,它正在激励Java社区减少动态魔术龙的使用(这是一件了不起的事情)。Netty已经开始致力于io_uring支持(完全异步的Linux syscall)。通过CloudDT之类的项目,通过CRDT和CQRS分发的数据开始蓬勃发展。还有更多!Java生态系统中发生了许多令人兴奋的事情!

因此,您想使用Java平台进行现代化,但是在如此广阔的生态系统中,您如何选择?我很乐意提供建议,但是应用程序种类繁多,辅助因素也很多,因此很难提供一刀切的指南。相反,这里有一些问题要问:

展开阅读全文

页面更新:2024-05-13

标签:反应式   映像   容器   应用程序   生态系统   框架   事情   语言   事件   代码   功能   数据库   服务器   测试   方法   平台   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top