<plugin>
<groupId>io.openliberty.tools</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>3.8.2</version>
</plugin>
您可以在您的Liberty应用程序中添加新的Spring Boot Support 3.0特性来使用Spring Boot 3.x的功能。现在还可以使用私钥对OpenID Connect客户端进行身份验证,这比使用客户端秘钥更安全。如果使用LTPA或JWT cookies,现在可以根据上下文根为不同的应用程序使用不同的cookies。
最后,生日快乐!Open Liberty已经成立6年了,可以看看我们项目的创建历史。
在Open Liberty 23.0.0.9里:
查看23.0.0.9里修复的bug列表.
如果您正在使用Maven, 在您的pom.xml文件里包含以下内容:
<plugin>
<groupId>io.openliberty.tools</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>3.8.2</version>
</plugin>
或者对于Gradle, 在您的 build.gradle文件里包含以下内容:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'io.openliberty.tools:liberty-gradle-plugin:3.6.2'
}
}
apply plugin: 'liberty'
或者如果您使用容器镜像:
FROM icr.io/appcafe/open-liberty
或者看看我们的下载页面.
Liberty Spring Boot 3.0特性为在Liberty上运行Spring Boot 3.0的应用程序提供了更完整的支持。它还提供了在容器中创建应用程序时精简应用程序的功能。
之前的Liberty版本提供了对Spring Boot 1.5和Spring Boot 2.0应用程序的支持。Liberty还支持打包成WAR文件的Spring Boot应用程序,这在最近的这篇博客文章里有演示。使用Open Liberty 23.0.0.9,您可以通过启用springBoot-3.0特性来部署Spring Boot 3.x的应用程序,可以同时支持JAR和WAR文件类型。
在Liberty上运行Spring Boot 3.x的应该程序,您必须使用Java 17或更高版本运行。另外,如果您的应用程序使用Jakarta Servlet特性,那么必须是Jakarta Servet 6.0。在server.xml文件中配置这些特性,示例如下:
<features>
<feature>springBoot-3.0</feature>
<feature>servlet-6.0</feature>
</features>
与以前版本的Spring Boot support特性一样,您可以将Spring Boot应用程序JAR文件放在/dropins/ Spring /目录中。或者,在server.xml文件中指定Spring Boot配置元素。例如:
<springBootApplication id="spring-boot-app" location="spring-boot-app-0.1.0.jar" name="spring-boot-app" />
有关更多信息,请参见配置和部署Spring Boot应用程序。
如果您已经使用了以前版本的Spring Boot Support特性,现在正在将您的应用程序迁移到Spring Boot 3,请注意以下要求:
必须使用Jakarta EE 10功能.
安全配置需要在应用程序中修改.
新的服务器模版 (springBoot3
)可用.
Liberty中的OpenID Connect客户端现在支持带有OpenID Connect令牌端点的‘private_key_jwt’客户端身份验证方法。
当OpenID Connect客户端调用OpenID Connect提供程序的令牌端点时,需要向OpenID Connect提供程序提供身份验证数据。客户端可以使用几种不同的方法进行身份验证,但大多数方法都需要客户端密钥。‘private_key_jwt’身份验证方法使客户端能够使用非对称密钥来创建签名的JSON Web令牌(jwt)来进行身份验证,而不是使用客户端秘钥。使用此身份验证方法的OpenID Connect客户端不再需要拥有客户端秘钥。
在您的客户端应用程序中,使用‘tokenEndpointAuthMethod’属性的‘private_key_jwt’选项,以及客户端‘server.xml’文件中的‘openidConnectClient’或‘oidcLogin’元素中的‘tokenEndpointAuthSigningAlgorithm’和‘keyAliasName’属性启用此功能。
例如,当您使用OpenID Connect Client特性时,需要包括以下配置:
<featureManager>
<feature>openidConnectClient-1.0</feature>
</featureManager>
...
<openidConnectClient tokenEndpointAuthMethod="private_key_jwt" keyAliasName="privateKeyJwtAliasRS512" ... />
如果您正在使用社交媒体登录功能,请包括以下配置:
<featureManager>
<feature>socialLogin-1.0</feature>
</featureManager>
...
<oidcLogin tokenEndpointAuthMethod="private_key_jwt" tokenEndpointAuthSigningAlgorithm="E512" keyAliasName="privateKeyJwtAliasES512" ... />
‘tokenEndpointAuthSigningAlgorithm’属性为用于客户端身份验证的JWT签名指定签名算法。‘keyAliasName’属性指向用于签署JWT的密钥,并且还必须是对应于私钥的公钥的别名。私钥必须位于OIDC客户端配置中由‘sslRef’引用的SSL配置指定的密钥库中。公钥必须位于以下位置之一:
由‘trustStoreRef’属性指定的信任库
由‘sslRef’引用的SSL配置指定的信任库
由‘sslRef’引用的SSL配置指定的密钥库
有关配置选项的更多信息,请参阅文档:
有关JWT认证的更多信息,请查看:
在以前的版本中,LTPA和JWT cookies总是将cookie路径设置为/,因此向域上的任何路径发出的任何请求都包含该cookie。现在可以将LTPA或JWT cookie路径设置为应用程序上下文根目录。通过这种配置,您可以为不同的应用程序使用不同的LTPA和JWT令牌。
在‘webAppSecurity’元素中启用‘useContextRootForSSOCookiePath’属性。例如,在‘server.xml’中添加以下行:
<webAppSecurity useContextRootForSSOCookiePath="true"/>
有关Open Liberty中LTPA的更多信息,请参阅文档:
您是否知道我们可以重新打包云原生Java应用程序,使其以毫秒为单位启动,而不会影响吞吐量、内存、开发-生产对等性或Java语言特性?并且不需要重构应用程序代码吗?方法就是……
在无服务器(serverless)环境中,通过在没有请求处理时关闭不需要的应用程序实例,缩容至0(scale-to-zero)可以帮助降低部署应用程序的总体云计算成本。当应用程序的活动开始时,新的实例能快速启动,而不会给应用程序终端用户带来明显的延迟。
尽管JDK技术有很大改进,使得它可以在不到1秒的时间内启动,比如类数据共享和动态AOT编译,但它的启动速度仍然不够快,无法支持从零缩放。但是,JDK对于优化吞吐量和内存、确保开发与生产的一致性以及支持各种Java语言特性等非常重要。那么,我们如何既改善启动时间,又从完整运行的JDK中获益呢?
Open Liberty运行时中的InstantOn功能使用IBM Semeru JDK和称为用户空间中的检查点/恢复点(CRIU)的Linux技术来获取应用程序进程的检查点或时间点快照。然后可以非常快速地恢复这个检查点,使应用程序进程恢复到使用检查点时的状态。应用程序可以多次恢复,因为Open Liberty和Semeru JDK保留了容器中每个恢复进程的唯一性。每个恢复的应用程序进程都可以不必先经历整个启动序列而运行,从而节省高达90%的启动时间(取决于您的应用程序)。InstantOn只需要对Java应用程序进行很少的修改就可以实现这种改进。
下图演示了如何在容器镜像构建期间生成检查点,通过将它们恢复到应用程序进程的检查点阶段来快速启动生产环境中多个应用程序实例的。
InstantOn不能在容器镜像构建之外使用。应用程序容器镜像提供了一个一致的环境,这是确保Open Liberty应用程序进程可靠恢复所必需的。由于InstantOn检查点包含在应用程序容器镜像的最后一层中,因此从检查点创建到镜像恢复期间,镜像底层中的资源不会发生变化。
下面的教程将引导您使用Linux上的 Open Liberty Java运行时、InstantOn、IBM Semeru JDK和Podman容器工具对应用程序进行容器化。有关使用Open Liberty容器化应用程序的信息,请参阅使用Podman进行容器化微服务指南。
目前,Open Liberty 23.0.0.6或更高版本只支持在x86-64/amd64架构上运行InstantOn。我们所有的测试都是在RHEL 9.0和Ubuntu 22.04上完成的,但如果具备以下先决条件,也可以在其他Linux发行版本上运行:
内核必须支持Linux CAP_CHECKPOINT_RESTORE功能,此功能是在内核5.9版本中引入的
必须安装Linux发行版最新版本的Podman
Linux发行版必须允许使用Podman或Docker来执行特权容器构建
有关运行时和主机构建系统先决条件的更多信息,请参阅Open Liberty InstantOn文档。
如果您手上没有自己的应用程序,您可以按照Open Liberty入门指南中的示例应用程序进行操作:
首先,克隆指南的Git存储库:
git clone https://github.com/openliberty/guide-getting-started.git
cd guide-getting-started
然后,在’finish/'目录下构建应用程序,并将其部署到Open Liberty:
cd finish
mvn liberty:run
当您看到下面的消息时,您的Open Liberty实例已经准备好了:
The defaultServer server is ready to run a smarter planet.
在http://localhost:9080/dev/system/properties的URL上查看该服务。在启动Open Liberty的命令行会话中按CTRL+C停止正在运行的Open Liberty实例。
最后,为应用程序构建WAR:
mvn package
这个命令会构建一个`target/guide-getting-started.war`的归档。现在,我们可以在使用InstantOn特性的容器镜像中加入这个WAR。
为了比较使用和不使用InstantOn时Open Liberty应用程序容器镜像启动所需的时间,我们首先先介绍如何在不使用InstantOn的情况下构建容器镜像。然后,再说明如何使用InstantOn构建并运行生成的容器。
构建不使用InstantOn的应用程序容器镜像:
podman build -t getting-started .
这个命令创建的是一个不包含任何检查点镜像的getting-started入门容器镜像。
运行这个应用容器:
podman run --name getting-started --rm -p 9080:9080 getting-started
请注意Open Liberty报告它已启动所花费的时间,并通过http://localhost:9080/dev/system/properties URL检查容器中运行的服务。检出应用程序后,在运行`podman run`命令的命令行会话中按CTRL+C停止正在运行的容器。
Open Liberty容器镜像包含构建具有检查点运行时进程的应用程序容器镜像的先决条件。应用程序可以使用Open Liberty镜像作为基础来构建自己的应用程序容器镜像,并以此为基础,使用检查点进程创建自己的应用程序容器镜像。
通过在应用程序容器镜像的构建步骤中启动Open Liberty运行时,可以创建一个InstantOn检查点。在此启动期间,运行时处理配置、加载所有启用的功能并开始处理配置的应用程序。根据应用程序的需要,您可以在Open Liberty启动期间选择两个特定阶段中的一个来检查进程。您必须配置Dockerfile以指定您选择的阶段(稍后会介绍)。
官方link:/docs/latest/container-images.htmlIBM Container Registry] (ICR)的Open Liberty镜像包含了InstantOn检查应用程序进程所需的所有先决条件。对于本例,`getting-started`入门应用程序容器镜像使用来自ICR的`icr.io/appcafe/open-liberty:full-java11-openj9-ubi`映像作为父镜像。目前,InstantOn只支持基于Java 11和Java 17的Open Liberty UBI镜像。
更新应用的Dockerfile文件,在文件末尾添加`checkpoint.sh`脚本的`RUN`命令,如下面的例子所示
FROM icr.io/appcafe/open-liberty:full-java11-openj9-ubi
ARG VERSION=1.0
ARG REVISION=SNAPSHOT
LABEL \
org.opencontainers.image.authors="Your Name" \
org.opencontainers.image.vendor="IBM" \
org.opencontainers.image.url="local" \
org.opencontainers.image.source="https://github.com/OpenLiberty/guide-getting-started" \
org.opencontainers.image.version="$VERSION" \
org.opencontainers.image.revision="$REVISION" \
vendor="Open Liberty" \
name="system" \
version="$VERSION-$REVISION" \
summary="The system microservice from the Getting Started guide" \
description="This image contains the system microservice running with the Open Liberty runtime."
COPY --chown=1001:0 src/main/liberty/config/ /config/
COPY --chown=1001:0 target/*.war /config/apps/
RUN configure.sh
RUN checkpoint.sh afterAppStart
此配置将应用程序进程检查点添加为应用程序容器镜像的最后一层。`checkpoint.sh`脚本允许指定’afterAppStart’或’beforeAppStart',以指示在启动的哪个阶段执行进程检查。
两个选项可选来确定检查点发生在应用程序启动之前还是之后:
beforeAppStart
: 检查点发生在处理配置的应用程序元数据之后。如果应用程序有作为应用程序启动的一部分运行的任何组件,则在执行应用程序中的任一代码之前采取检查点。这个选项是InstantOn提供的最早检查点阶段。
afterAppStart
: 此选项是检查点能够发生的最后阶段,因此在恢复应用程序实例时,它有可能提供最快的启动时间。检查点发生在所有已配置的应用程序报告为已启动之后。它发生在打开任何监听端口以侦听应用程序的传入请求之前。
afterAppStart`阶段会为应用程序提供最快的启动时间,但它也可能导致一些应用程序代码在服务器进程检查点发生之前运行。由于本教程中使用的`getting-started`应用程序在其启动逻辑中没有做任何会导致恢复问题的事情,因此我们可以选择使用`afterAppStart
。
为了InstantOn创建检查点并恢复进程,CRIU二进制文件需要额外的Linux功能。虽然Open Liberty容器镜像包含赋予二进制文件的必要功能。但是,容器在启动时还必须要赋这些功能。
使用podman,您可以使用`-–cap-add`和`--security-opt`选项为容器构建赋予必要的功能,以便在容器构建步骤中创建检查点。启动Podman容器的用户必须具有赋予它必要的Linux功能的权限,因此必须以root或sudo身份运行以下命令:
podman build \
-t dev.local/getting-started-instanton \
--cap-add=CHECKPOINT_RESTORE \
--cap-add=SYS_PTRACE\
--cap-add=SETPCAP \
--security-opt seccomp=unconfined .
Dockerfile中的最后一条指令是运行`checkpoint.sh`脚本。当您执行前面的Podman构建命令时,它会启动Open Liberty并在Dockerfile中指定的阶段执行检查点。容器进程数据持久化后,Open Liberty停止,容器镜像构建完成。生成的应用程序容器镜像包含检查点进程数据作为容器镜像的最后一层。输出如下面的例子:
Performing checkpoint --at=afterAppStart
Launching defaultServer (Open Liberty 23.0.0.6/wlp-1.0.78.cl230620230612-1100) on Eclipse OpenJ9 VM, version 11.0.19+7 (en_US)
[AUDIT ] CWWKE0001I: The server defaultServer has been launched.
[AUDIT ] CWWKG0093A: Processing configuration drop-ins resource: /opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/keystore.xml
[AUDIT ] CWWKG0093A: Processing configuration drop-ins resource: /opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/open-default-port.xml
[AUDIT ] CWWKZ0058I: Monitoring dropins for applications.
[AUDIT ] CWWKZ0001I: Application guide-getting-started started in 1.886 seconds.
[AUDIT ] CWWKC0451I: A server checkpoint "afterAppStart" was requested. When the checkpoint completes, the server stops.
使用以下命令运行`getting-started-instanton`容器:
podman run \
--rm \
--cap-add=CHECKPOINT_RESTORE \
--cap-add=SETPCAP \
--security-opt seccomp=unconfined \
-p 9080:9080 \
getting-started-instanton
`--cap-add`的选项赋予容器两个Linux功能,这两个功能是CRIU恢复应用程序进程所需的。当Open Liberty恢复应用程序进程时,它会输出以下消息:
[AUDIT ] Launching defaultServer (Open Liberty 23.0.0.6/wlp-1.0.78.cl230620230612-1100) on Eclipse OpenJ9 VM, version 11.0.19+7 (en_US)
[AUDIT ] CWWKZ0001I: Application guide-getting-started started in 0.233 seconds.
[AUDIT ] CWWKT0016I: Web application available (default_host): http://850ba43df239:9080/dev/
[AUDIT ] CWWKT0016I: Web application available (default_host): http://850ba43df239:9080/metrics/
[AUDIT ] CWWKT0016I: Web application available (default_host): http://850ba43df239:9080/health/
[AUDIT ] CWWKT0016I: Web application available (default_host): http://850ba43df239:9080/ibm/api/
[AUDIT ] CWWKC0452I: The Liberty server process resumed operation from a checkpoint in 0.283 seconds.
[AUDIT ] CWWKF0012I: The server installed the following features: [cdi-4.0, distributedMap-1.0, jndi-1.0, json-1.0, jsonb-3.0, jsonp-2.1, monitor-1.0, mpConfig-3.0, mpHealth-4.0, mpMetrics-5.0, restfulWS-3.1, restfulWSClient-3.1, ssl-1.0, transportSecurity-1.0].
[AUDIT ] CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 0.297 seconds.
如果Open Liberty未能恢复检查点进程,它会通过启动没有检查点的镜像来恢复,并输出以下消息:
CWWKE0957I: Restoring the checkpoint server process failed. Check the /logs/checkpoint/restore.log log to determine why the checkpoint process was not restored. Launching the server without using the checkpoint image.
检查Open Liberty启动所需的时间,并将其与没有InstantOn的时间进行比较。
InstantOn通过从检查点状态恢复进程,显著改善了Open Liberty应用程序的启动时间。对第一次响应时间(即服务第一个请求所花费的时间)的改进也令人印象深刻,但在这种情况下,显然更多的应用程序逻辑是在恢复之后运行。我们测量了在容器中运行和使用afterAppStart 检查点两种情况下运行多个应用程序的指标。
Pingperf是一个非常简单的ping类型应用程序,涉及单个REST端点
Rest crud有点复杂,涉及JPA和远程数据库
AcmeAir Microservice Main使用MicroProfile功能
这些实验表明,与没有InstantOn的普通JVM模式相比,所有3个应用程序的启动时间都有了健康的改善,第一次响应的时间也提高了8.8倍。[1]
这篇文章描述了如何通过使用Open Liberty InstantOn特性生成应用程序容器镜像来配置云原生应用程序,使其几乎立即启动。InstantOn的关键价值主张是,您可以重新打包云原生Java应用程序,使其以毫秒为单位启动,而不会影响吞吐量、内存、开发-生产对等性或Java语言特性。 该特性现在可以在公共云AWS EKS和Azure AKS环境中X86-64/AMD64平台上的Open Liberty 23.0.0.6中可用。
在未来,我们计划扩大我们的平台覆盖范围,并扩展到能够在更受管理的公共云和混合云环境中运行。我们还打算探索更大的Open Liberty特性集来支持InstantOn。有关Open Liberty InstantOn的更多详细信息,请参阅link:/docs/latest/instanton.html[使用Open Liberty InstantOn的容器化应用程序的快速启动文档】,该文档会包含已知限制条件以及Semeru JDK对该特性支持情况的更详细讨论。
翻译:Haiyan Zhang
我们很高兴地宣布 Open Liberty 23.0.0.3发布了,这是自5年前首次亮相以来最大的运行时版本之一! 此版本包括 Jakarta EE 10 Platform、Web Profile,并且首次包含Core Profile。还在构成profiles的大部分功能中添加了许多新的增强功能。
包含Jakarta EE Core Profile 10的MicroProfile 6也将在23.0.0.3版本中正式亮相。它包括新的MicroProfile Telemetry 1.0特性,以及对Metrics、OpenAPI和JWT Authentication规范的更新。
在23.0.0.3发行版中还引入了对Java SE 20(撰写本文时的最新版本)的支持,并提供了各种新特性和更改。许多值得注意的错误修复也包含在这个Open Liberty版本中。
Open Liberty 23.0.0.3里包含:
如果您使用 Maven, 可以参考:
<dependency>
<groupId>io.openliberty</groupId>
<artifactId>openliberty-runtime</artifactId>
<version>23.0.0.3</version>
<type>zip</type>
</dependency>
或者是 Gradle:
dependencies {
libertyRuntime group: 'io.openliberty', name: 'openliberty-runtime', version: '[23.0.0.3,)'
}
或者您使用的是 container images:
FROM icr.io/appcafe/open-liberty
或者可以查看我们的 下载页面, 我们已经添加了Jakarta EE 10 和MicroProfile 6 的包.
Jakarta EE 10 Core Profile, Web Profile和Platform现在正式支持Open Liberty! 我们首先要感谢所有在我们的各种测试版中提供反馈的人。
Jakarta EE 10标志着一个重要的里程碑。这是自2017年Java EE 8以来第一个提供规范更新的Jakarta版本,因此也是自Eclipse基金会接管该规范以来第一个提供规范更新的版本。在对现有规范的众多更新中,它还引入了Core Profile。Core Profile的目标是轻量级运行时,比如Open Liberty,它针对运行云原生Java微服务进行了优化。
Jakarta Platform以及Core和Web Profile由以下规范组成:
规范 | 变更 | Liberty特性文档 |
---|---|---|
新规 |
||
重大更新 |
||
次要更新 |
||
次要更新 |
||
次要更新 |
||
未变更 |
规范 | 变更 | Liberty特性文档 |
---|---|---|
新规 |
||
重大更新 |
||
重大更新 |
||
重大更新 |
||
重大更新 |
||
重大更新 |
||
重大更新 |
||
重大更新 |
||
重大更新 |
||
次要更新 |
||
次要更新 |
||
次要更新 |
||
未变更 |
||
未变更 |
不适用 |
|
未变更 |
||
未变更 |
||
未变更 |
不适用 (见 Javadoc) |
规范 | 变更 | Liberty特性文档 |
---|---|---|
重大更新 |
||
次要更新 |
||
次要更新 |
不适用 (见 Javadoc) |
|
次要更新 |
||
次要更新 |
||
次要更新 |
||
次要更新 |
||
未变更 |
||
XML Binding 4.0 (optional) |
重大更新 |
|
XML Web Services 4.0 (optional) |
重大更新 |
Liberty为运行包含在Jakarta EE 10 Web Profile (webProfile-10.0)和Jakarta EE 10 Platform (jakartaee-10.0)中的所有组件规范提供了便利的特性。这些便利的特性使您能够使用各自规范中的所有API快速开发应用程序。对于应用程序客户端中的Jakarta EE 10特性,请使用Liberty jakartaeeClient-10.0特性。
要使用Jakarta EE Platform 10特性,请在server.xml文件里添加jakartaee-10.0 feature
<featureManager>
<feature>jakartaee-10.0</feature>
</featureManager>
或者,要启用Jakarta EE Web Profile 10功能,请在server.xml文件中添加webProfile-10.0 feature:
<featureManager>
<feature>webProfile-10.0</feature>
</featureManager>
虽然没有针对Core Profile的便利功能,但您可以通过server.xml文件里添加以下功能来启用等效功能:
<featureManager>
<feature>jsonb-3.0</feature>
<feature>jsonp-2.1</feature>
<feature>cdi-4.0</feature>
<feature>restfulWS-3.1</feature>
</featureManager>
要在应用程序客户端容器上运行Jakarta EE 10特性,请在应用程序的client .xml文件中添加以下条目:
<featureManager>
<feature>jakartaeeClient-10.0</feature>
</featureManager>
更多的信息请参考
MicroProfile在业界如何优化Java微服务上持续创新。MicroProfile 6.0版本允许应用程序使用MicroProfile APIs和 Jakarta EE Core Profile 10以及其他各种新功能和改进。MicroProfile 6.0包含以下规范:
规范 | 变更 | Liberty特性文档 |
---|---|---|
新规 |
||
重大更新 |
||
次要更新 |
||
次要更新 |
||
未变更 |
||
未变更 |
||
未变更 |
||
未变更 |
||
新规 |
要使用所有MicroProfile 6特性,请在server.xml文件中添加microProfile-6.0 feature:
<featureManager>
<feature>microProfile-6.0</feature>
</featureManager>
可以通过MicroProfile 6.0 specification, release, Javadoc, 还有Differences between MicroProfile 6.0 and 5.0查看更多信息
Java 20包含下面的特性和变更:
如何使用Java 20:
获取Open Liberty 23.0.0.3 版本.
编辑Liberty server.env 文件,将 JAVA_HOME 指向Java 20 installation安装路径.
有关Java 20的更多信息,可以参考Java 20相关发布说明,API Javadoc 页面, download page and Java 20 迁移手册.
在Open Liberty中试用Java 20预览特性,请确保使用 --enable-preview进行编译,并在jvm.options文件中添加相同的参数。 |
我们花了一些时间来修复bug。下面的部分描述了在这个版本中解决的一些问题。如果您感兴趣,这里是 23.0.0.3中修复的完整错误列表。
启用了撤销的AcmeCA特性在某些操作系统和JDK组合上可能无法初始化
当在MacOS上运行带有IBM JDK8和自动证书管理环境(ACME)支持2.0功能并启用证书撤销检查的混合JDK时,SSL/TLS端点可能无法完成初始化,并且无法用于通信。
FFDC里记录如下NullPointerException,表示初始化流程失败:
Exception = java.lang.NullPointerException
Source = com.ibm.ws.security.acme.internal.AcmeProviderImpl
probeid = 921
Stack Dump = java.lang.NullPointerException
at sun.security.provider.certpath.CertPathHelper.setDateAndTime(CertPathHelper.java:71)
at sun.security.provider.certpath.RevocationChecker.checkCRLs(RevocationChecker.java:525)
at sun.security.provider.certpath.RevocationChecker.checkCRLs(RevocationChecker.java:464)
at sun.security.provider.certpath.RevocationChecker.check(RevocationChecker.java:393)
at sun.security.provider.certpath.RevocationChecker.check(RevocationChecker.java:336)
at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:125)
at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:225)
at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:145)
at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:84)
at java.security.cert.CertPathValidator.validate(CertPathValidator.java:304)
at com.ibm.ws.security.acme.internal.CertificateRevocationChecker.isRevoked(CertificateRevocationChecker.java:371)
....
此问题已解决,端点已成功初始化,并可用于服务通信。
被注入jakarta.enterprise.event.Event(或类似javax)的passivated (即可序列化)bean将无法正常恢复。当启用会话持久性并且从数据库序列化和反序列化会话数据时,可能会遇到这种情况。这会导致以下FFDC事件发生:
+
Stack Dump = java.lang.ClassCastException: cannot assign instance of org.jboss.weld.event.EventImpl$SerializationProxy to field org.apache.myfaces.flow.cdi.FlowScopeContextualStorageHolder.flowDestroyedEvent of type jakarta.enterprise.event.Event in instance of org.apache.myfaces.flow.cdi.FlowScopeContextualStorageHolder
at java.base/java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2076)
at java.base/java.io.ObjectStreamClass$FieldReflector.checkObjectFieldValueTypes(ObjectStreamClass.java:2039)
at java.base/java.io.ObjectStreamClass.checkObjFieldValueTypes(ObjectStreamClass.java:1293)
at java.base/java.io.ObjectInputStream.defaultCheckFieldValues(ObjectInputStream.java:2512)
+ 此问题已解决,CDI事件对象恢复无误。
在DB2 11.5.7+中,将DB2数据源的queryDataSize设置为有效值时,会出现以下错误:
[ERROR ] CWWKG0075E: The value 10452991 is not valid for attribute `queryDataSize` of configuration element dataSource. The validation message was: Value "10452991" is out of range..
此问题已得到解决,新的queryDataSize范围已被正确接受
在使用JAX-RS时一个 OpenJ9里的bug可能会导致Liberty服务器因死锁而挂起。例如:、、
2LKMONINUSE sys_mon_t:0x00007FCE3C16F258 infl_mon_t: 0x00007FCE3C16F2D8:
3LKMONOBJECT org/apache/cxf/jaxrs/interceptor/CachedTime@0x00000000FBF1D0C8: Flat locked by "Default Executor-thread-8" (J9VMThread:0x0000000001B4BF00), entry count 1
3LKWAITERQ Waiting to enter:
3LKWAITER "Default Executor-thread-1" (J9VMThread:0x00000000006EB200)
3LKWAITER "Default Executor-thread-3" (J9VMThread:0x0000000000718D00)
3LKWAITER "Default Executor-thread-17" (J9VMThread:0x0000000002644B00)
3LKWAITER "Default Executor-thread-19" (J9VMThread:0x0000000000346F00)
3LKWAITER "Default Executor-thread-20" (J9VMThread:0x0000000000618300)
3LKWAITER "Default Executor-thread-29" (J9VMThread:0x0000000002645700)
3LKWAITER "Default Executor-thread-30" (J9VMThread:0x0000000002643F00)
3LKWAITER "Default Executor-thread-39" (J9VMThread:0x00000000022FF900)
3LKWAITER "Default Executor-thread-40" (J9VMThread:0x00000000022DAA00)
3LKWAITER "Default Executor-thread-49" (J9VMThread:0x000000000216DE00)
3LKWAITER "Default Executor-thread-50" (J9VMThread:0x00000000022FED00)
3LKWAITER "Default Executor-thread-59" (J9VMThread:0x0000000001B74900)
3LKWAITER "Default Executor-thread-60" (J9VMThread:0x0000000002178F00)
3LKWAITER "Default Executor-thread-62" (J9VMThread:0x0000000001B72300)
"Default Executor-thread-8" J9VMThread:0x0000000001B4BF00, omrthread_t:0x00007FCE18012DF0, java/lang/Thread:0x00000000FBF99E78, state:B, prio=5
(java/lang/Thread getId:0x4C, isDaemon:true)
com/ibm/ws/classloading/internal/ThreadContextClassLoader(0x0000000086272FF8)
(native thread ID:0x1A4, native priority:0x5, native policy:UNKNOWN, vmstate:B, vm thread flags:0x00000281)
(native stack address range from:0x00007FCEA0FF6000, to:0x00007FCEA1036000, size:0x40000)
CPU usage total: 3.272702139 secs, current category="Application"
Blocked on: java/lang/StringBuffer@0x00000000FBF99F10 Owned by: "Default Executor-thread-1" (J9VMThread:0x00000000006EB200, java/lang/Thread:0x00000000804DA638)
Heap bytes allocated since last GC cycle=0 (0x0)
Java callstack:
at java/lang/StringBuffer.setLength(Bytecode PC:0(Compiled Code))
(entered lock: java/lang/StringBuffer@0x00000000FBF99F10, entry count: 1)
at org/apache/cxf/jaxrs/interceptor/CachedTime.updateTime(CachedTime.java:86)
at org/apache/cxf/jaxrs/interceptor/CachedTime.getTimeAsString(CachedTime.java:134)
在Liberty中,通过从CXF的CachedTime类中删除SimpleDateFormat的使用,这个问题得到了缓解,死锁不再发生。
翻译:王峰 (亚信安慧)
本文是在 Liberty InstantOn 仍处于测试阶段时发表的。 Liberty InstantOn 从以下链接开始退出测试版:Liberty 23.0.0.6 版本。有关 Liberty InstantOn 的最新信息,请参阅 Open Liberty 文档中的链接:使用 Open Liberty InstantOn 实现容器化应用程序的更快启动。
你想让云原生Java应用在几毫秒内启动,而不影响吞吐量、内存、开发和生产等价或Java语言新特性吗?Open Liberty 22.0.0.11-beta带来了InstantOn,一个为MicroProfile和Jakarta EE应用程序提供了快速启动的新功能。InstantOn 于23.0.0.6已经正式发布了。这个博客讲述了最新信息。
在无服务器环境中,应用程序的启动时间很重要。当应用程序不被使用时,InstantOn应用程序实例可以收缩到零。当没有持续的请求时,通过减少应用实例的总体数量,收缩到零有助于降低整体云成本。当应用程序的活动增加时,新的实例可以快速启动,不会给用户体验带来高延迟。
为了实现InstantOn,Open Liberty使用了OpenJ9 JVM的新特性和一种叫做Checkpoint/Restore In Userspace CRIU的Linux技术,在应用进程启动时对其设置checkpoint
。这个checkpoint
是正在运行的应用程序进程的快照,可以被持久化,也可以快速restore
,使应用程序进程回到checkpoint
时的状态。这个过程使Liberty实例,以及任何受保护的应用程序,可以多次restore
为应用程序的不同实例。
要了解InstantOn的启动速度有多快,请跳转到链接"有多快"。关于这种方法优势的更多细节,参考OpenJ9 CRIU下快速JVM启动博文和早期的Open Liberty博文,其中我们首次讨论了CRIU对快速启动Java应用程序的能力。
Open Liberty InstantOn功能提供了一个新的checkpoint
行为,可以针对你现有的Open Liberty服务器有效,你可以很容易地使用它。可以具体指定一个阶段作为checkpoint
,让Liberty在这个checkpoint
启动、停止、保存这个checkpoint
。当Liberty启动时,它将检测到保存的checkpoint
进程,并从保存的checkpoint
中restore
进程状态。
在我们讨论checkpoint
如何工作的细节之前,让我们建立一个实例。对于这个例子,我们将了解一下Open Liberty入门指南。从克隆一个样例代码的Git仓库开始:
git clone https://github.com/openliberty/guide-getting-started.git
cd guide-getting-started
在本演示中,我们将在finish/
目录下工作。请运行以下Maven goal,构建该应用程序并将其部署到Open Liberty:
cd finish
mvn liberty:run
以下信息说明你的应用服务器已经准备好了:
The defaultServer server is ready to run a smarter planet.
通过浏览器访问 http://localhost:9080/dev/system/properties ,查看服务。在查看完应用程序后,在运行服务器的命令行会话中按CTRL+C停止Open Liberty服务器。下一步为应用程序构建WAR,请运行以下命令:
mvn package
这个命令建立了一个target/guide-getting-started.war
文件。我们现在可以把这个WAR包含在使用InstantOn功能的容器镜像中。
要使用Open Liberty InstantOn支持的应用程序,它必须首先使用Open Liberty beta InstantOn镜像进行容器化。关于如何使用Open Liberty对应用程序进行容器化,请参见容器化微服务指南或用Podman容器化微服务指南。如果你不熟悉Podman,请先了解一下Podman指南,因为Open Liberty InstantOn目前需要使用到Podman。后期,一旦Docker支持CRIU所需的功能,InstantOn也会对Docker支持。
Liberty InstantOn测试版包含了构建带有checkpoint
进程的应用容器镜像功能。应用程序可以使用Liberty InstantOn测试版作为基础来构建自己的应用程序容器镜像。涉及到以下步骤:
checkpoint
/restore
容器化应用程序的先决条件目前,Open Liberty InstantOn的测试版只支持在x86-64/amd64架构上运行。为了构建和运行使用criu的容器镜像,主机操作系统需要安装一些先决条件。我们所有的测试都是在RHEL 8.6和RHEL 9.0上进行的。其他Linux发行版,如果具备必要的先决条件,也是可以的。需要具备以下条件:
内核必须支持Linux CAP_CHECKPOINT_RESTORE功能。该功能是在5.9版本的内核中引入的,但已经传回到RHEL 8.6中使用的RHEL内核版本。
必须安装Linux发行版的最新可用版本的Podman。
必须对Podman进行配置以使用crun
或runc
容器运行时。
如果你使用runc
容器运行时,那么需要1.1.3或更高的版本,以便你有最新的纠错到runc
。这个x可以使容器中的/proc/sys/kernel/ns_last_pid
成功挂载。
对于Open Liberty入门指南的例子,第一步是创建一个Dockerfile
,提供创建应用程序的容器化版本的说明。注意,podman
也支持使用Containerfile
格式来构建容器镜像。
在这个例子中,我们将使用IBM容器注册中心(ICR)的一个特殊镜像,icr.io/appcafe/open-liberty:beta
,作为父镜像。这个镜像被标记为beta
,意味着它包括了所有Liberty beta的功能以及完整镜像中的所有Liberty功能。这个镜像被标记为instanton
,意思是它包括所有产生checkpoint
进程镜像的先决条件,比如必要的criu
二进制文件。
getting-started应用程序的Dockerfile
已经存在于finish/Dockerfile
中。编辑现有的finish/Dockerfile
并修改FROM
指令,使用icr.io/appcafe/open-liberty:beta
父镜像。Dockerfile
看起来像这样:
FROM icr.io/appcafe/open-liberty:beta
ARG VERSION=1.
ARG REVISION=SNAPSHOT
LABEL \
org.opencontainers.image.authors="Your Name" \
org.opencontainers.image.vendor="IBM" \
org.opencontainers.image.url="local" \
org.opencontainers.image.source="https://github.com/OpenLiberty/guide-getting-started" \
org.opencontainers.image.version="$VERSION" \
org.opencontainers.image.revision="$REVISION" \
vendor="Open Liberty" \
name="system" \
version="$VERSION-$REVISION" \
summary="The system microservice from the Getting Started guide" \
description="This image contains the system microservice running with the Open Liberty runtime."
COPY --chown=1001:0 src/main/liberty/config/ /config/
COPY --chown=1001:0 target/*.war /config/apps/
RUN configure.sh
为了使 criu
能够对进程进行checkpoint
和restore
,criu
二进制文件必须被授予额外的Linux功能。特别是对于Open Liberty,它需要被授予cap_checkpoint_restore
、cap_net_admin
和cap_sys_ptrace
。Open Liberty InstantOn 测试版镜像包括 criu
二进制文件,以及criu
二进制文件所需要的功能。为了使criu
二进制文件在运行时被赋予访问权限,运行criu
的容器在启动时也必须被授予必要的权限。你可以通过以下两种方式之一授予容器这些权限:
使用--privileged
选项使用特权容器
使用--cap-add
选项分配特定的权限
当你使用Docker时,守护程序通常有根权限。这个权限允许它在启动容器时授予任何要求的能力。在Podman中,没有守护程序,所以启动容器的用户必须有必要的Linux权限。当你以root身份运行或使用sudo
来运行podman
命令时,就有这个权限。在这个例子中,我们以根用户的身份运行podman
命令。
有了这样的认识,我们现在可以通过使用podman build
命令来构建容器镜像。在finish/
目录下,运行以下命令来构建应用程序的容器镜像:
构建应用程序容器镜像
podman build -t getting-started.
这个命令创建了得到启动的容器镜像。然而,这个容器镜像并不包含任何可用于InstantOn启动的checkpoint
镜像文件。你可以用下面的命令来运行这个应用容器镜像。
运行应用程序容器
podman run --name getting-started --rm -p 9080:9080 getting-started
注意Liberty显示了应用启动所需的时间,并在 http://localhost:9080/dev/system/properties 上可以查看到容器中运行的服务。在检查完应用程序后,在运行podman run
的命令行会话中按CTRL+C停止运行中的容器。
checkpoint
在启动过程中,Open Liberty有三个阶段可以产生checkpoint
:
features
: 这是最早可以发生checkpoint
的阶段。checkpoint
发生在所有配置的Open Liberty功能启动之后,但在对已安装的应用程序进行任何处理之前。请注意,此检查点阶段已被删除,请参阅链接:/blog/2023/02/10/instant-on-beta-update.html[23.0.0.2-beta 中 Liberty InstantOn 的新增强功能]。
beforeAppStart
:checkpoint
发生在对配置的应用程序元数据处理之后。如果应用程序有任何组件作为应用程序启动的一部分被运行,checkpoint
将在执行应用程序任何代码之前进行。
afterAppStart
- 这是checkpoint
可以发生的最后一个阶段,在这个阶段做checkpoint
,可以在restore
应用实例时提供最快的启动时间。checkpoint
发生在所有被指导的应用程序状态为启动之后。这个阶段发生在打开任何用于监听应用程序传入请求的端口之前。
这个afterAppStart
阶段通常为应用程序提供最快的启动时间,但如果有一些应用程序在进程checkpoint
restore
之前运行,就会导致不可知的错误。另外如果checkpoint
的应用程序持有不应该被同步到多个应用程序实例的状态或数据,例如,在checkpoint
之前连接到外部资源(如数据库)会导致checkpoint
restore
到多实例进程时失败,原因是这会多次restore
相同的连接,造成资源冲突。但是,如果您的应用程序初始化不执行打开数据库连接等操作,您也许可以使用afterAppStart
阶段作为检查点。
在应用容器镜像构建完成后,它可以被用来在之前描述的checkpoint
阶段(features
, beforeAppStart
, afterAppStart
)之一对应用进程进行checkpoint
。你可以通过使用podman run
的--env
选项为你的checkpoint
指定一个阶段,将WLP_CHECKPOINT
的值设置为可用的checkpoint
。在下面例子中,通过运行podman
命令,制作一个afterAppStart
镜像。
在容器中确定一个checkpoint
podman run \
--name getting-started-checkpoint-container \
--privileged \
--env WLP_CHECKPOINT=afterAppStart \
getting-started
在容器中确定criu
checkpoint
时,需要使用--privileged
选项。
WLP_CHECKPOINT
环境变量用于指定checkpoint
阶段。对于需要启动快的场景,afterAppStart
checkpoint
阶段将是最好的选择。
这将启动在Open Liberty上运行应用程序的容器。在Open Liberty启动后,它会在WLP_CHECKPOINT
环境变量指定的阶段执行checkpoint
。在容器的进程数据被持久化之后,容器将停止,将产生一个包含checkpoint
进程数据的容器文件。输出将看起来像这样:
确定checkpoint
输出
Performing checkpoint --at=afterAppStart
Launching defaultServer (Open Liberty 22.0.0.11-beta/wlp-1.0.69.cl221020220912-1100) on Eclipse OpenJ9 VM, version 17.0.5-
ea+2 (en_US)
CWWKE0953W: This version of Open Liberty is an unsupported early release version.
[AUDIT ] CWWKE0001I: The server defaultServer has been launched.
[AUDIT ] CWWKG0093A: Processing configuration drop-ins resource:
/opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/checkpoint.xml
[AUDIT ] CWWKG0093A: Processing configuration drop-ins resource:
/opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/keystore.xml
[AUDIT ] CWWKG0093A: Processing configuration drop-ins resource:
/opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/open-default-port.xml
[AUDIT ] CWWKZ0058I: Monitoring dropins for applications.
[AUDIT ] CWWKT0016I: Web application available (default_host): http://f5edff273d9c:9080/ibm/api/
[AUDIT ] CWWKT0016I: Web application available (default_host): http://f5edff273d9c:9080/metrics/
[AUDIT ] CWWKT0016I: Web application available (default_host): http://f5edff273d9c:9080/health/
[AUDIT ] CWWKT0016I: Web application available (default_host): http://f5edff273d9c:9080/dev/
[AUDIT ] CWWKZ0001I: Application guide-getting-started started in 0.986 seconds.
[AUDIT ] CWWKC0451I: A server checkpoint was requested. When the checkpoint completes, the server stops.
这个过程目前不能作为podman构建步骤的一部分,因为Podman(和Docker)没有提供一种方法来授予构建容器镜像必要的Linux权限,以便criu
确定进程checkpoint
。
checkpoint
镜像到目前为止,我们已经为getting-started应用程序创建了checkpoint
进程数据,并将其存储在一个名为getting-started-checkpoint-container
的停止的容器中。最后一步是创建一个包含checkpoint
进程数据的新容器镜像。当这个容器镜像被启动时,它将从checkpoint
被创建的地方开始restore
应用进程,从而形成一个InstantOn应用。你可以通过运行下面的podman commit
操作来创建新的镜像:
将checkpoint
提交给一个图像
podman commit getting-started-checkpoint-container getting-started-instanton
现在我们有两个应用镜像,分别命名为getting-started
和getting-started-instanton
。用getting-started-instanton
容器镜像启动容器,会显示出比原来的getting-started
镜像快得多的启动时间。
通常情况下,一个应用容器可以通过如下命令从一个应用容器镜像中启动:
podman run --rm -p 9080:9080 getting-started-instanton
然而,这个命令会失败,因为criu
需要一些高级权限,以便能够restore
容器中的进程。当Liberty不能restore
checkpoint
进程时,它将通过启动没有checkpoint
镜像来restore
,并记录以下信息:
CWWKE0957I: Restoring the checkpoint server process failed. Check the /logs/checkpoint/restore.log log to determine why
the checkpoint process was not restored. Launching the server without using the checkpoint image.
为了授予所有可用的所需权限,你可以选择用以下命令来启动一个有特权的容器。
podman run --rm --privileged -p 9080:9080 getting-started-instanton
如果成功,你将看到如下输出:
[AUDIT ] CWWKZ0001I: Application guide-getting-started started in 0.059 seconds.
[AUDIT ] CWWKC0452I: The Liberty server process resumed operation from a checkpoint in 0.088 seconds.
[AUDIT ] CWWKF0012I: The server installed the following features: [cdi-3.0, checkpoint-1.0, concurrent-2.0,
distributedMap-1.0, jndi-1.0, json-1.0, jsonb-2.0, jsonp-2.0, monitor-1.0, mpConfig-3.0, mpHealth-4.0, mpMetrics-4.0,
restfulWS-3.0, restfulWSClient-3.0, servlet-5.0, ssl-1.0, transportSecurity-1.0].
[AUDIT ] CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in
0.098 seconds.
不建议使用root权限来运行容器。最好的做法是设置只有运行容器所需的权限。可以使用下面的命令来授予容器必要的权限,而不需要运行一个完全-特权的容器:
podman run with unconfined --security-opt options
podman run \
--rm \
--cap-add=CHECKPOINT_RESTORE \
--cap-add=NET_ADMIN \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--security-opt systempaths=unconfined \
--security-opt apparmor=unconfined \
-p 9080:9080 \
getting-started-instanton
--cap-add
选项授予容器 criu
所需的三种 Linux 权限。--security-opt
选项授予 criu
访问所需的系统调用和访问主机上的 /proc/sys/kernel/ns_last_pid
权限 。
可以通过减少--security-opt
选项来进一步简化checkpoint
的制作过程。默认情况下,podman
并没有授予criu所需要权限去做系统调用(默认值在/usr/share/containers/seccomp.json
文件中)。首先,你需要一个可以设置criu
做系统调用需要的权限配置文件,授予criu
所需要的所有系统和容器调用的权限。其次,主机需要挂载/proc/sys/kernel/ns_last_pid
。可以用下面的命令来完成这两个步骤:
podman run with limited --security-opt
podman run \
--rm \
--cap-add=CHECKPOINT_RESTORE \
--cap-add=NET_ADMIN \
--cap-add=SYS_PTRACE \
--security-opt seccomp=criuRequiredSysCalls.json \
-v /proc/sys/kernel/ns_last_pid:/proc/sys/kernel/ns_last_pid \
-p 9080:9080 \
getting-started-instanton
--security-opt seccomp=
选项指的是一个名为 criuRequiredSysCalls.json
的文件。这个文件是criu所需的系统调用权限。-v
选项在主机上挂载/proc/sys/kernel/ns_last_pid
,供容器访问。
根据你的Linux发行版,Podman可能默认使用runc
或crun
。要检查你的Podman安装的容器,请运行命令podman info
,查看ociRuntime
部分。如果使用的是runc
,请确保你使用的是1.1.3或更高版本。为了有效,你必须有一个1.1.3或更高版本的runc
。
根据你的RHEL 8.6或RHEL 9.0安装的最新情况,你可能会发现指定criuRequiredSysCalls.json
的--security-opt
是不必要的。在写这篇文章的时候,最新版本的RHEL 8.6和RHEL 9.0包括一个Podman,默认授予所需的系统调用给它启动的容器。这个默认值使得指定 --security-opt seccomp=criuRequiredSysCalls.json
不在需要了。
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
},
{
"architecture": "SCMP_ARCH_AARCH64",
"subArchitectures": [
"SCMP_ARCH_ARM"
]
},
{
"architecture": "SCMP_ARCH_MIPS64",
"subArchitectures": [
"SCMP_ARCH_MIPS",
"SCMP_ARCH_MIPS64N32"
]
},
{
"architecture": "SCMP_ARCH_MIPS64N32",
"subArchitectures": [
"SCMP_ARCH_MIPS",
"SCMP_ARCH_MIPS64"
]
},
{
"architecture": "SCMP_ARCH_MIPSEL64",
"subArchitectures": [
"SCMP_ARCH_MIPSEL",
"SCMP_ARCH_MIPSEL64N32"
]
},
{
"architecture": "SCMP_ARCH_MIPSEL64N32",
"subArchitectures": [
"SCMP_ARCH_MIPSEL",
"SCMP_ARCH_MIPSEL64"
]
},
{
"architecture": "SCMP_ARCH_S390X",
"subArchitectures": [
"SCMP_ARCH_S390"
]
},
{
"architecture": "SCMP_ARCH_RISCV64",
"subArchitectures": null
}
],
"syscalls": [
{
"names": [
"accept",
"accept4",
"access",
"adjtimex",
"alarm",
"bind",
"brk",
"capget",
"capset",
"chdir",
"chmod",
"chown",
"chown32",
"clock_adjtime",
"clock_adjtime64",
"clock_getres",
"clock_getres_time64",
"clock_gettime",
"clock_gettime64",
"clock_nanosleep",
"clock_nanosleep_time64",
"close",
"close_range",
"connect",
"copy_file_range",
"creat",
"dup",
"dup2",
"dup3",
"epoll_create",
"epoll_create1",
"epoll_ctl",
"epoll_ctl_old",
"epoll_pwait",
"epoll_pwait2",
"epoll_wait",
"epoll_wait_old",
"eventfd",
"eventfd2",
"execve",
"execveat",
"exit",
"exit_group",
"faccessat",
"faccessat2",
"fadvise64",
"fadvise64_64",
"fallocate",
"fanotify_mark",
"fchdir",
"fchmod",
"fchmodat",
"fchown",
"fchown32",
"fchownat",
"fcntl",
"fcntl64",
"fdatasync",
"fgetxattr",
"flistxattr",
"flock",
"fork",
"fremovexattr",
"fsetxattr",
"fstat",
"fstat64",
"fstatat64",
"fstatfs",
"fstatfs64",
"fsync",
"ftruncate",
"ftruncate64",
"futex",
"futex_time64",
"futex_waitv",
"futimesat",
"getcpu",
"getcwd",
"getdents",
"getdents64",
"getegid",
"getegid32",
"geteuid",
"geteuid32",
"getgid",
"getgid32",
"getgroups",
"getgroups32",
"getitimer",
"getpeername",
"getpgid",
"getpgrp",
"getpid",
"getppid",
"getpriority",
"getrandom",
"getresgid",
"getresgid32",
"getresuid",
"getresuid32",
"getrlimit",
"get_robust_list",
"getrusage",
"getsid",
"getsockname",
"getsockopt",
"get_thread_area",
"gettid",
"gettimeofday",
"getuid",
"getuid32",
"getxattr",
"inotify_add_watch",
"inotify_init",
"inotify_init1",
"inotify_rm_watch",
"io_cancel",
"ioctl",
"io_destroy",
"io_getevents",
"io_pgetevents",
"io_pgetevents_time64",
"ioprio_get",
"ioprio_set",
"io_setup",
"io_submit",
"io_uring_enter",
"io_uring_register",
"io_uring_setup",
"ipc",
"kill",
"landlock_add_rule",
"landlock_create_ruleset",
"landlock_restrict_self",
"lchown",
"lchown32",
"lgetxattr",
"link",
"linkat",
"listen",
"listxattr",
"llistxattr",
"_llseek",
"lremovexattr",
"lseek",
"lsetxattr",
"lstat",
"lstat64",
"madvise",
"membarrier",
"memfd_create",
"memfd_secret",
"mincore",
"mkdir",
"mkdirat",
"mknod",
"mknodat",
"mlock",
"mlock2",
"mlockall",
"mmap",
"mmap2",
"mprotect",
"mq_getsetattr",
"mq_notify",
"mq_open",
"mq_timedreceive",
"mq_timedreceive_time64",
"mq_timedsend",
"mq_timedsend_time64",
"mq_unlink",
"mremap",
"msgctl",
"msgget",
"msgrcv",
"msgsnd",
"msync",
"munlock",
"munlockall",
"munmap",
"nanosleep",
"newfstatat",
"_newselect",
"open",
"openat",
"openat2",
"pause",
"pidfd_open",
"pidfd_send_signal",
"pipe",
"pipe2",
"poll",
"ppoll",
"ppoll_time64",
"prctl",
"pread64",
"preadv",
"preadv2",
"prlimit64",
"process_mrelease",
"pselect6",
"pselect6_time64",
"pwrite64",
"pwritev",
"pwritev2",
"read",
"readahead",
"readlink",
"readlinkat",
"readv",
"recv",
"recvfrom",
"recvmmsg",
"recvmmsg_time64",
"recvmsg",
"remap_file_pages",
"removexattr",
"rename",
"renameat",
"renameat2",
"restart_syscall",
"rmdir",
"rseq",
"rt_sigaction",
"rt_sigpending",
"rt_sigprocmask",
"rt_sigqueueinfo",
"rt_sigreturn",
"rt_sigsuspend",
"rt_sigtimedwait",
"rt_sigtimedwait_time64",
"rt_tgsigqueueinfo",
"sched_getaffinity",
"sched_getattr",
"sched_getparam",
"sched_get_priority_max",
"sched_get_priority_min",
"sched_getscheduler",
"sched_rr_get_interval",
"sched_rr_get_interval_time64",
"sched_setaffinity",
"sched_setattr",
"sched_setparam",
"sched_setscheduler",
"sched_yield",
"seccomp",
"select",
"semctl",
"semget",
"semop",
"semtimedop",
"semtimedop_time64",
"send",
"sendfile",
"sendfile64",
"sendmmsg",
"sendmsg",
"sendto",
"setfsgid",
"setfsgid32",
"setfsuid",
"setfsuid32",
"setgid",
"setgid32",
"setgroups",
"setgroups32",
"setitimer",
"setpgid",
"setpriority",
"setregid",
"setregid32",
"setresgid",
"setresgid32",
"setresuid",
"setresuid32",
"setreuid",
"setreuid32",
"setrlimit",
"set_robust_list",
"setsid",
"setsockopt",
"set_thread_area",
"set_tid_address",
"setuid",
"setuid32",
"setxattr",
"shmat",
"shmctl",
"shmdt",
"shmget",
"shutdown",
"sigaltstack",
"signalfd",
"signalfd4",
"sigprocmask",
"sigreturn",
"socket",
"socketcall",
"socketpair",
"splice",
"stat",
"stat64",
"statfs",
"statfs64",
"statx",
"symlink",
"symlinkat",
"sync",
"sync_file_range",
"syncfs",
"sysinfo",
"tee",
"tgkill",
"time",
"timer_create",
"timer_delete",
"timer_getoverrun",
"timer_gettime",
"timer_gettime64",
"timer_settime",
"timer_settime64",
"timerfd_create",
"timerfd_gettime",
"timerfd_gettime64",
"timerfd_settime",
"timerfd_settime64",
"times",
"tkill",
"truncate",
"truncate64",
"ugetrlimit",
"umask",
"uname",
"unlink",
"unlinkat",
"utime",
"utimensat",
"utimensat_time64",
"utimes",
"vfork",
"vmsplice",
"wait4",
"waitid",
"waitpid",
"write",
"writev",
"arch_prctl",
"chroot",
"clone",
"clone3",
"fallocate",
"fanotify_init",
"fsconfig",
"fsmount",
"fsopen",
"guarded_storage",
"kcmp",
"lseek",
"mmap",
"mount",
"open",
"open_by_handle_at",
"openat",
"pivot_root",
"preadv",
"process_vm_readv",
"ptrace",
"readdir",
"s390_runtime_instr",
"setns",
"sigaction",
"signal",
"syscall",
"umount",
"umount2",
"unshare",
"userfaultfd",
"wait"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [
"process_vm_readv",
"process_vm_writev",
"ptrace"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"minKernel": "4.8"
}
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 0,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 8,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 131072,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 131080,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 4294967295,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"sync_file_range2",
"swapcontext"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"ppc64le"
]
}
},
{
"names": [
"arm_fadvise64_64",
"arm_sync_file_range",
"sync_file_range2",
"breakpoint",
"cacheflush",
"set_tls"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"arm",
"arm64"
]
}
},
{
"names": [
"arch_prctl"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"amd64",
"x32"
]
}
},
{
"names": [
"modify_ldt"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"amd64",
"x32",
"x86"
]
}
},
{
"names": [
"s390_pci_mmio_read",
"s390_pci_mmio_write",
"s390_runtime_instr"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"s390",
"s390x"
]
}
},
{
"names": [
"riscv_flush_icache"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"riscv64"
]
}
},
{
"names": [
"open_by_handle_at"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_DAC_READ_SEARCH"
]
}
},
{
"names": [
"bpf",
"clone",
"clone3",
"fanotify_init",
"fsconfig",
"fsmount",
"fsopen",
"fspick",
"lookup_dcookie",
"mount",
"mount_setattr",
"move_mount",
"name_to_handle_at",
"open_tree",
"perf_event_open",
"quotactl",
"quotactl_fd",
"setdomainname",
"sethostname",
"setns",
"syslog",
"umount",
"umount2",
"unshare"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"clone"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 2114060288,
"op": "SCMP_CMP_MASKED_EQ"
}
],
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
],
"arches": [
"s390",
"s390x"
]
}
},
{
"names": [
"clone"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 1,
"value": 2114060288,
"op": "SCMP_CMP_MASKED_EQ"
}
],
"comment": "s390 parameter ordering for clone is different",
"includes": {
"arches": [
"s390",
"s390x"
]
},
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"clone3"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 38,
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"reboot"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_BOOT"
]
}
},
{
"names": [
"chroot"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_CHROOT"
]
}
},
{
"names": [
"delete_module",
"init_module",
"finit_module"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_MODULE"
]
}
},
{
"names": [
"acct"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_PACCT"
]
}
},
{
"names": [
"kcmp",
"pidfd_getfd",
"process_madvise",
"process_vm_readv",
"process_vm_writev",
"ptrace"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_PTRACE"
]
}
},
{
"names": [
"iopl",
"ioperm"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_RAWIO"
]
}
},
{
"names": [
"settimeofday",
"stime",
"clock_settime"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_TIME"
]
}
},
{
"names": [
"vhangup"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_TTY_CONFIG"
]
}
},
{
"names": [
"get_mempolicy",
"mbind",
"set_mempolicy"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_NICE"
]
}
},
{
"names": [
"syslog"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYSLOG"
]
}
}
]
}
我们测试了多个应用程序,以显示使用InstantOn如何减少启动时间。
Pingperf是一个非常简单的ping类型的应用程序,涉及一个单一的REST接口。
Rest crud就比较复杂了,它涉及JPA和一个远程数据库。
AcmeAir Microservice Main使用了MicroProfile的功能。
这些实验是在一个24核的系统上运行的。我使用taskset -c
为运行在容器中的Liberty进程分配了4个CPU。InstantOn时间是使用afterAppStart
checkpoint
阶段消耗的时间。基础启动是从启动Liberty服务器到服务器准备接受请求的时间,不包括启动容器本身所需的时间,以messages.log
中信息显示“这个<server name>服务器已准备好运行更智能的星球“为准。这些应用程序的InstantOn与正常启动时间在此以毫秒为单位显示。你的结果可能会根据你的环境、你系统上安装的硬件和软件以及其他因素而有所不同。数据显示越低越好:
InstantOn提供了一个快速启动的能力,根据应用的不同,最高可达90%。所有的应用都是不一样的,所以你可能会看到你的应用有不同的结果。
这篇帖子描述了使用Open Liberty InstantOn测试版来制作具有InstantOn启动时间的应用程序容器镜像的细节。目前仅在Liberty webProfile-8.0、webProfile-9.1、microProfile-4.1和microProfile-5.0支持该功能。我们希望将其扩展到包括webProfile和microProfile的未来版本,并将支持扩展到Jakarta完整的profile功能(如jakarta-8.0、jakarta-9.1、jakarta-10.0)。
通过InstantOn,你可以建立非常快速的启动应用容器,这些容器可以在部署时选择收缩到零。我们期待着未来的一篇博文,描述如何在红帽OpenShift容器平台(OCP)和Kubernetes(k8s)等云环境中部署Open Liberty InstantOn,并采用Knative等能够自动将应用收缩到零的技术。
原出版: Eclipse 基金会2019年9月月报: https://www.eclipse.org/community/eclipse_newsletter/2019/september/microprofile.php
云,可见或不可见,无处不在。在这篇文章中,我谈论的是无形的云,云计算。云分为三种类型:私有云、公共云和混合云。越来越多的公司正在致力于应用程序现代化,以便他们可以创建云原生微服务,以节省成本并充分利用云资源。当微服务在云中运行时,它只会在运行时产生费用。根据负载,它可以弹性伸缩。在这种情况下,您只需为使用的内容付费,不会有闲置成本,您无需担心购买任何硬件只是为了偶尔使用,即用即付模式。
在应用程序现代化方面,实现目标有不同的途径。对于现有的单体应用程序,是时候考虑如何在云中部署它们了。您可以将它们容器化或将它们分解为微服务。对于新编写的应用程序,最好的选择是构建新型云原生微服务。本文重点介绍如何开发最高效的云原生微服务,可以在云端正常运行。我们先明确一下云原生是什么意思。
您可能首先想到的问题是:云原生是什么意思?一般来说,云原生是指为云构建的应用程序。云原生微服务充分利用了云基础设施,形成了一个很好的生态系统。无需重新打包即可轻松配置、具备安全、弹性、可监控、可追溯等特性。我们来总结一下云原生微服务的特点。
外部化配置
云原生微服务必须是可配置的。更改配置时,不需重新打包微服务。一次构建,到处配置。这也是12因子方法论强烈推荐的。
RESTful
云原生微服务最好是无状态的。状态应该存储在数据库中,而不是内存中。云原生微服务具有共性,不管几个同时运行,它们都是一模一样的。微服务本身应该被视为牲畜,而不是宠物。在这种情况下,当微服务容器没有响应时,底层云基础设施可以替换它而不会丢失任何信息。
容错
云原生微服务应该具有弹性。它应该在任何情况下发挥作用。比如,即使它的下游服务宕掉。
可发现
云原生微服务应该能够对外提供自身功能或能力,以便客户端可以调用服务。
安全性
安全性在云原生微服务中极为重要。在单体应用程序中,只有一小部分功能被公开,而大部分功能被隐藏。当迁移到云原生时,每一项服务都有一个唯一的身份。您需要确保只有经过身份验证和授权的用户才能调用相关的微服务。例如,如果你部署一个工资查询的微服务,你可能只希望具有“经理”和“所有者”访问权限的调用者访问该服务。
可观测性
一旦云原生微服务在云中运行,就必须发出一些指标,以便 DevOps 可以对其进行监控,以了解它的响应速度和负载量等。
可追溯性
云原生微服务主要需要与其他不同的微服务通信,然后再与其他不同的服务通信。如果您尝试绘制一个调用链,您可能会以 a→b→d→f→e 等结尾。一旦服务无法运行,您需要找出哪个服务出现故障。如果没有可视化调用链的能力,将很难识别出故障服务。为了构建调用链,微服务需要能够传播关联ID。有了这个,ZipKin 或 Jaeger 等一些工具可以构建一个跟踪范围来说明每个服务状态。
与云通信的能力
云原生微服务需要能够与云基础设施就其健康状态进行通信。它应该能够告诉基础设施它是否准备好接收请求或者它是否仍然存在。如果它不存在,云基础设施(例如 Kubernetes)可以轻松替换它。如果它没有准备好,例如它的数据库连接没有准备好,云基础设施将不会将请求路由到这个服务。
读到这里,你可能会感叹,别说你的业务逻辑了,还有很多 QoS 需要担心。如果您管理好所有这些,您可能不得不花费大部分时间来担心上述要求,而花更少的时间做与您的业务相关的事情。实在是太多了。我听到你的困难了。幸运的是,我是来帮助你的。我们继续。
无需从头开始构建,有一些框架可以帮助你;开发云原生微服务最流行的框架是MicroProfile和Spring框架。
Spring 框架已经发展了几年,因此我不会过多谈论它。MicroProfile 相对较新。它由 IBM、Red Hat、Payara、Tomitribe、LJC 和其他个人于 2016 年年中成立。后来,其他公司,如Microsoft, Oracle等也加入了社区。MicroProfile 的目标是发展用于开发云原生微服务的最佳编程模型。到目前为止,它已经发布了八个以上的版本,不久前发布了 MicroProfile 5.0。最近,它引起了广泛的关注。大多数 Jakarta EE 运行时现在都支持 MicroProfile,例如 Open Liberty、Quarkus、Payara、TomEE、Helidon、KumuluzEE 等。您可以在 此处找到实现细节。
Spring | MicroProfile | |
---|---|---|
REST APIs |
||
REST Service |
Spring MVC |
JAX-RS |
Dependency Injection |
Spring IoC & DI |
CDI |
API Documentation |
Spring REST Docs |
MicroProfile Open API |
REST Client |
Spring MVC Feign |
MicroProfile REST Client |
JSON Binding/Processing |
Bring Your Own Library |
JSON-B |
处理多个微服务Handling 100s of Services |
||
Configuration |
Spring Boot Config |
MicroProfile Config |
Fault Tolerance |
Netflix Hystrix |
MicroProfile Fault Tolerance |
Security |
Spring Security |
Jakarta EE Security |
Operation Focus |
||
Health |
Spring Boot Actuator |
MicroProfile Health |
Metrics |
Spring Boot Actuator |
MicroProfile Metrics |
Distributed Tracing |
Spring Cloud Sleuth |
MicroProfile Open Tracing |
让我们将进行详细介绍。
RESTful API 是一种被广泛采用的开发云原生微服务的方式。让我们关注如何使用 RESTful API 和相关技术来编写松耦合的微服务。
REST(Representational State Transfer)是一种架构风格,用于定义服务之间的通信标准,使系统之间的通信更容易。MicroProfile 和 Spring 都支持 REST。
MicroProfile 使用来自Jakarta EE的JAX-RS。在 JAX-RS中,您需要定义一个应用程序和 JAX-RS
资源。在以下示例中,定义了应用程序 CatalogApplication
和 JAX-RS资源 CatalogService
,详细信息如下。
@ApplicationPath("/rest")
public class CatalogApplication extends Application {
}
@Path("/items")
@Produces(MediaType.APPLICATION_JSON)
public class CatalogService {..}
@GET
public List<Item> getInventory() {...}
@GET
@Path("{id}")
public Response getById(@PathParam("id") long id) {...}
在上面提到的例子中,一个端点 http://${host}:${port}/rest/items 将被暴露。
请参阅此Open Liberty 以了解有关 JAX-RS 的更多信息。
在 Spring 框架中,您将需要创建一个 SpringBootApplication 和
Controller。在以下示例中,Application
并 CatalogController
相应地创建。
@SpringBootApplication
public class Application {
public static void main(String[] args)
SpringApplication.run(Application.class, args);}
}
@RestController
public class CatalogController {..}
@RequestMapping(value = "/items", method = RequestMethod.GET)
@ResponseBody
List<Item> getInventory() {..}
@RequestMapping(value = "/items/{id}", method = RequestMethod.GET)
ResponseEntity<?> getById(@PathVariable long id) {...}
在上面提到的例子中,一个端点 http://${host}:${port}/rest/items 将被暴露。
在设计云原生微服务时,最佳实践是创建松耦合的微服务。MicroProfile 采用 Jakarta EE 的上下文和依赖注入 (CDI),而 Spring 使用 Spring DI、IoC 来达到相同的效果。
下面展示如何使用CDI进行依赖注入
@ApplicationPath("/rest")
public class JaxrsApplication extends Application {
@Inject
private InventoryRefreshTask refreshTask;
上面的代码片段将注入 InventoryRefreshTask
到一个实例 refreshTask
。
CDI 是 Jakarta EE 和 MicroProfile 的核心部分。了解 CDI非常重要。请参阅此Open Liberty 指南以了解有关 CDI 的一些基础知识。
Spring使用依赖注入,控制反转来实现松耦合。以下代码片段说明了如何通过
@Autowired 使用 Spring
DI的一个实例 InventoryRefreshTask
将被注入到变量 refreshTask
中。顺便说一句,Spring
也支持 @Inject
,相当于 @Autowired
.
@SpringBootApplication
public class Application {
@Autowired
private InventoryRefreshTask refreshTask;
...
}
微服务需要宣传他们的能力,以便潜在客户可以使用他们的服务。在记录 API 时,MicroProfile 和 Spring 处理的方式不同。
MicroProfile 使用MicroProfile Open API来记录 API,它基于 Swagger API。在 MicroProfile Open API 中,任何 JAX-RS 资源都会自动选择生成其 API。它还可以在 META-INF 文件夹下获取文件名为 openapi.yaml 或 openapi.yml 或 openapi.json 的打开 API 的yaml文件。以下是如何记录API响应和操作的示例。
@GET
@Produces(MediaType.APPLICATION_JSON)
@APIResponse(
responseCode = "200",
description = "host:properties pairs stored in the inventory.",
content = @Content(mediaType = "application/json",
schema = @Schema(type = SchemaType.OBJECT,
implementation = InventoryList.class)))
@Operation(summary = "List inventory contents.",
description = "Returns the stored host:properties pairs.")
public InventoryList listContents() {
return manager.list();
}
在上述代码段中,端点 http://{host.name}:${port}/openapi 将通过以下输出公开。
openapi: 3.0.0
info:
title: Inventory App
description: App for storing JVM system properties of various hosts.
license:
name: Eclipse Public License - v 1.0
url: https://www.eclipse.org/legal/epl-v10.html
version: "1.0"
servers: - url: http://localhost:{port} description: Simple Open Liberty.
variables:
port:
description: Server HTTP port.
default: "9080"
paths:
/inventory/systems:
get:
summary: List inventory contents.
description: Returns the currently stored host:properties pairs in the
inventory.
operationId: listContents
responses:
200:
description: host:properties pairs stored in the inventory.
content:
application/json:
schema:
$ref: '#/components/schemas/InventoryList'
... .
如果您使用 Open Liberty,端点 http://{host.name}:${port.number}/openapi/ui 也将被公开,这允许最终用户直接调用各个端点。
如果您熟悉 Swagger API,您会发现这很熟悉。
请参阅此Open Liberty 指南以了解有关 MicroProfile Open API 的更多信息。
Spring 使用测试来记录 API,并能够生成 API 文档作为测试运行的一部分。这是生成 Spring 文档的方法。
1.定义依赖
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs </groupId>
<artifactId>spring-restdocs-core</artifactId>
<scope>test</scope>
</dependency>
2.定义你的 Rest 服务
@RestController
public class CatalogController {
@RequestMapping("/")
public @ResponseBody String index() {
return "Greetings from Catalog Service!";
}
}
3.定义所有必要的测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CatalogController.class)
@WebAppConfiguration
public class CatalogControllerTest {
@Rule public JUnitRestDocumentation restDocumentation = new
JUnitRestDocumentation("target/generated-snippets");
private MockMvc mockMvc;
@Autowired private WebApplicationContext context;
@Before public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(documentationConfiguration(restDocumentation)) .build();
}
}
4.alwaysDo()
, responseFileds()
, requestPayload()
, links()
, fieldWithPath()
, requestParameters()
, pathParameters()
用于记录
@Test
public void crudDeleteExample() throws Exception {
this.mockMvc.perform(delete("/crud/{id}",
10)).andExpect(status().isOk())
.andDo(document("crud-delete-example",
pathParameters(
parameterWithName("id").description("The id of the input to delete")
)));
}
运行测试时,将生成 API 文档。
云原生微服务不是独立的。微服务相互交互。一个微服务调用第二个微服务,然后第二个微服务调用第三个微服务,依此类推。通常,它是一种网状结构。例如,在微服务A调用微服务B的场景中,微服务 A 表现为客户端。如何建立从微服务A到微服务B的连接? Rest client 来解决!
JAX-RS 客户端可用于进行客户端服务器通信,详述如下。
Client client = ClientBuilder.newClient();
Response res = client.target("http://example.org/hello").request("text/plain").get();
但是,它不是类型安全的客户端,因此容易出错。传入错误参数的调用会导致运行时错误,这为时已晚。
MicroProfile Rest Client是一种类型安全的 Rest Client,它提供了一种更简单的方式来进行客户端服务器通信。它是如何工作的?以下是步骤。
步骤 1:注册一个 REST 客户端 API
@Dependent
@RegisterRestClient(baseUri=http://localhost:9080/system)
@RegisterProvider(InventoryResponseExceptionMapper.class)
public interface InventoryServiceClient {
@GET
@Produces(MediaType.APPLICATION_JSON)
List<Item> getAllItems() throws UnknownUrlException,
ServiceNotReadyException;
}
第 2 步:将客户端 API 注入客户端微服务 JAX-RS 资源
@Inject
@RestClient
private InventoryServiceClient invClient;
final List<Item> allItems = invClient.getAllItems();
第3步:重新绑定后端微服务
io.openliberty.guides.inventory.client.SystemClient/mp-rest/url=http://otherhost:8080/system
使用附加的完全限定类名 /mp-rest/url
作为键,使用后端服务端点作为值。在云端部署此微服务时,后端URL会与其他环境不同。通常,您需要通过 Kubernetes ConfigMap 在客户端的
deployment.yaml 中重新绑定后端服务。
请参阅此Open Liberty 指南以了解有关 MicroProfile Rest Client 的更多信息。
Spring 使用了与 MicroProfile Rest Client 类似的方法,并使用了 FeignClient 和 Injection 等相应技术。
第 1 步:定义客户端
@FeignClient(name="inventory-service", url="${inventoryService.url}")
public interface InventoryServiceClient {
@RequestMapping(method=RequestMethod.GET,
value="/micro/inventory", produces={MediaType.APPLICATION_JSON_VALUE})
List<Item> getAllItems();
}
第2步:启用客户端并注入客户端
@EnableFeignClients
public class Application {
@Autowired
private InventoryServiceClient invClient;
final List<Item> allItems = invClient.getAllItems();
...
}
JSON 格式是网络上常见的媒体类型。JSON-B 和 JSON-P 是帮助处理 JSON 媒体类型的流行技术。
MicroProfile 2.0 及更高版本同时支持JSON-B和JSON-P,这极大地简化了JSON 对象的序列化和反序列化。下面是使用 JSON-B 序列化 artists
对象的示例。
public class car {
private String make;
private String model;
private String reg;
...
}
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
Car car = new Car("VW", "TGUAN", "HN19MDZ");
Jsonb jsonb = JsonbBuilder.create();
String json = jsonb.toJson(car);
The toJson () 方法返回序列化的 car对象。
{
"make": "VW",
"model": "TGUAN",
"reg": "HN19MFZ"
}
使用 JSON-B 进行反序列化同样简单。
Car car = Jsonb.fromJson(json, Car.class);
为了在线传输 JSON 对象,您只需定义一个 POJO,例如
public class InventoryList {
private List<SystemData> systems;
public InventoryList(List<SystemData> systems) {
this.systems = systems;
}
public List<SystemData> getSystems() {
return systems;
}
public int getTotal() {
return systems.size();
}
}
在 JAX-RS 资源中,您可以直接将此类型作为 JSON 对象返回。
@GET
@Produces(MediaType.APPLICATION_JSON)
public InventoryList listContents() {
return manager.list();
}
请参阅本文以了解有关 JSON-B 的更多信息。
Spring 可以直接使用 Jackson 或 JSON-B。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
final ObjectMapper objMapper = new ObjectMapper();
jsonString = objMapper.writeValueAsString(car);
// or use JSON-B
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
Jsonb jsonb = JsonbBuilder.create();
String result = jsonb.toJson(car);
在您的云基础架构中通常有 100 个微服务。在处理 100个服务时,您将需要监控服务、配置服务、对服务进行安全防护等。
云原生微服务是可配置的,因此它们可以由 DevOps 更新。开发人员不必因为配置值更改而重新打包微服务。设计原则是这些配置可以存储在微服务外部的某个地方,并且这些配置可供微服务使用。这被称为外部化配置,这是12因素 APP强调的因素之一。下面我们来看看 MicroProfile 和 Spring 是如何帮助我们配置微服务的。
MicroProfile Config通过将配置值放在配置源中来启用外部化配置,然后微服务可以使用注入或以编程方式查找来获取相应的配置值。
第 1 步:在配置源中指定配置,可以是系统属性、环境变量、microprofile-config.properties 或自定义配置源。
# Elasticsearch
elasticsearch_url=http://es-catalog-elasticsearch:9200
elasticsearch_index=micro
elasticsearch_doc_type=items
第 2 步:使用编程查找或注入
Config config = ConfigProvider.getConfig();
private String url = config.getValue("elasticsearch_url",
String.class);
或者
@Inject @ConfigProperty(name="elasticsearch_url") String url;
请参阅此Open Liberty 指南以了解有关 MicroProfile Config 的更多信息。
让我们看看如何用 Spring 框架做同样的事情。
您可以使用 Spring config 通过以下步骤实现配置外部化。
第 1 步:在配置源中定义配置
elasticsearch:
url: http://localhost:9200
user:
password:
index: micro
doc_type: items
第 2 步:将配置属性注入 bean
@Component("ElasticConfig")
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticsearchConfig {
// Elasticsearch stuff
private String url;
private String user;
...
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
第 3 步:将配置 bean 注入其他类
@Autowired
private ElasticsearchConfig config;
String url = config.getUrl();
云原生微服务需要容错,因为不确定因素或移动部件太多。MicroProfile和Spring都提供了一个模型来实现容错。
MicroProfile Fault Tolerance通过使用@Timeout、@Retry、@Fallback、@Bulkhead、@CircuitBreaker 的注解提供以下能力:
超时:定义超时的持续时间
重试:定义何时重试的标准
回退:为失败的执行提供替代解决方案。
故障隔离:隔离部分系统的故障,而系统的其余部分仍能工作。
断路器:通过自动执行失败,提供一种快速故障方式,以防止系统过载和客户端无限期等待或超时。
以下代码片段描述了 getInventory()
2s 后超时的调用。如果操作失败,则在 2s
的总时长内最多重试 2 次。连续 20
次调用,如果发生一半故障,电路将被困开。如果重试后仍然失败,fallbackInventory
将调用回退操作方法。
@Timeout(value = 2, unit = ChronoUnit.SECONDS)
@Retry(maxRetries = 2, maxDuration = 2000)
@CircuitBreaker
@Fallback(fallbackMethod = "fallbackInventory")
@GET
public List<Item> getInventory() {
return items;
}
public List<Item> fallbackInventory() {
//Returns a default fallback
return fallbackitemslist;
}
请参阅此交互式 Open Liberty 指南以了解有关 MicroProfile 容错的更多信息。
Spring 使用 Hysterix 来实现容错,下文详述。
@Service
public class AppService {
@HystrixCommand(fallbackMethod = "fallback")
public List<Item> getInventory() {
return items;
}
public List<Item> fallback() {
//Returns a default fallback
return fallbackitemslist;
}
}
import
org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker
@SpringBootApplication
@RestController
@EnableCircuitBreaker
public class Application {
...
}
云原生微服务应该是安全的,因为它们是公开的,容易受到攻击。MicroProfile 将 MicroProfile JWT 与 Java EE Security 一起使用,而 Spring 使用 Spring 安全性。
MicroProfile JWT 构建在 JWT 之上,向 JWT 添加了一些声明以识别用户ID和用户规则。以下代码片段演示了端点 /orders
只能由具有“admin
” 角色的人访问。
@DeclareRoles({"Admin", "User"})
@RequestScoped
@Path("/orders")
public class OrderService {
@Inject private JsonWebToken jwt;
@GET
@RolesAllowed({ "admin" })
@Produces(MediaType.APPLICATION_JSON)
public InventoryList listContents() {
return manager.list();
}
...
}
请参阅此Open Liberty 指南以了解如何使用 MicroProfile JWT。
您可以通过配置 Spring Security 来保护 Spring 微服务。如果 Spring Security 在类路径上,则 Spring Boot 使用基本身份验证自动保护所有 HTTP端点。
首先,您需要指定对 spring-boot-starter-security
.
其次,在您的微服务中,指定以下注释 EnableWebSecurity
或 EnableResourceServer
保护微服务。请参见下面的示例
@Configuration
@EnableWebSecurity
@EnableResourceServer
public class OAuth2ResourceServerConfig extends
ResourceServerConfigurerAdapter {
@Autowired
Private JWTConfig securityConfig;
....
}
微服务性能
在云中部署微服务后,DevOps 负责监控微服务的性能。如果出现问题,DevOps需要一些监控数据来识别瓶颈或从指标数据中发现任何警告。智能云原生微服务应该能够与云基础设施就其健康状态进行通信,了解它是否准备好接收流量或服务请求等。让我们看看编程模型在这方面必须提供什么。
云原生微服务应该能够与云基础设施就其健康状态进行通信。MicroProfile和Spring都提供了这种能力。Kubernetes 是最流行的微服务编排器,可以检查容器(正在运行的微服务实例)的就绪或活跃状态。如果微服务不活跃,需要执行pod 重启,比如内存不足。未就绪是指微服务还没有为服务器请求做好准备,比如数据库连接异常等。
MicroProfile Health 2.0及更高版本提供就绪和在线端点。微服务可以提供 HealthCheck
带有注释的实现 @Readiness
以配置就绪检查过程。所有bean实 HealthCheck
和注解的聚合 @Readiness
配置了/ready的端点。
@Readiness
public class HealthEndpoint implements HealthCheck {
@Override
public HealthCheckResponse call() {...}
}
类似地,微服务可以提供带有注释的 HealthCheck
实现,@Liveness
以配置活动检查过程。HealthCheck
带有注解的所有 bean
实现的聚合 @Liveness
配置了 /live 的端点。
@Liveness
public class HealthEndpoint implements HealthCheck {
@Override
public HealthCheckResponse call() {...}
}
Kubernetes 可以根据下面的代码片段在其 liveness 或 readiness或startup探针中相应地查询 /health/live 或 /health/ready 或/health/started端点。
livenessProbe:
exec:
command:
- curl
- -f
- http://localhost:9080/health/live
initialDelaySeconds: 120
periodSeconds: 10
readinessProbe:
exec:
command:
- curl
- -f
- http://localhost:9080/health/ready
initialDelaySeconds: 120
periodSeconds: 10
请参阅此Open Liberty指南以了解如何使用 MicroProfile Health。
Spring Boot使用Actuator 提供应用程序的健康状态。SpringBoot Actuator
暴露 /health
端点来指示正在运行的应用程序的健康状态,例如数据库连接、磁盘空间不足等。应用程序通过 HealthIndicator
. 此健康信息是从所有实现 HealthIndicator
应用程序上下文中配置的接口的bean中收集的。下面是自定义运行状况实施的示例。
@Component
public class HealthCheck implements HealthIndicator {
@Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
public int check() {
// Our logic to check health
return 0;
}
}
对于正在运行的云原生微服务,了解它正在服务的流量、吞吐量是多少以及它可能很快停止工作的任何迹象都是很有用的。Metrics可以帮助解决这个问题。
MicroProfileMetrics提供了一个端点 /metrics
来公开所有指标信息,包括下划线运行时。/metrics
的端点显示一些基本指标。例如,Open Liberty
提供了以下开箱即用的指标类型。本文省略了每种类型的详细信息。
# TYPE base:classloader_total_loaded_class_count counter
# TYPE base:gc_global_count counter
# TYPE base:cpu_system_load_average gauge
# TYPE base:thread_count counter
# TYPE base:classloader_current_loaded_class_count counter
# TYPE base:gc_scavenge_time_seconds gauge
# TYPE base:jvm_uptime_seconds gauge
# TYPE base:memory_committed_heap_bytes gauge
# TYPE base:thread_max_count counter
# TYPE base:cpu_available_processors gauge
# TYPE base:thread_daemon_count counter
# TYPE base:gc_scavenge_count counter
# TYPE base:classloader_total_unloaded_class_count counter
# TYPE base:memory_max_heap_bytes gauge
# TYPE base:cpu_process_cpu_load_percent gauge
# TYPE base:memory_used_heap_bytes gauge
# TYPE base:gc_global_time_seconds gauge
...
您可以添加特定于应用程序的指标以收集更多指标。以下是如何收集关联端点的响应时间和调用次数等的示例。
@Timed(name = "Inventory.timer", absolute = true, displayName="Inventory
Timer", description = "Time taken by the Inventory", reusable=true)
@Counted(name="Inventory", displayName="Inventory Call count",
description="Number of times the Inventory call happened.",
monotonic=true, reusable=true)
@Metered(name="InventoryMeter", displayName="Inventory Call Frequency",
description="Rate of the calls made to Inventory", reusable=true)
// Get all rows from database
public List<Item> findAll(){ }
请参阅此Open Liberty 指南以了解如何使用 MicroProfile Metrics。
Spring 通过 Spring Actuator 提供度量指标。Spring Actuator公开一个端点 /metrics
以显示应用程序指标。在以下代码片段中,/metrics
显示有效列表的数量和无效列表的计数。下面是自定义
Metrics 实现的示例。
@Service
public class LoginServiceImpl {
private final CounterService counterService;
public List<Item> findAll (CounterService counterService) {
this.counterService = counterService;
if(list.size()>1)
counterService.increment("counter.list.valid ");
else
counterService.increment("counter.list.invalid");
}
在微服务架构中,一个微服务调用另一个微服务是很常见的。对于 DevOps,查看调用链很重要。当出现问题时,应立即将故障服务固定下来。为了支持这一点,我们需要一种方法来创建调用链。幸运的是,这就是分布式跟踪发挥作用的地方。分布式跟踪的实现细节是将关联 id 沿调用链传播,以便 Zipkin 或 Jaeger 可以使用此公共关联 id 形成一条链。MicroProfile 和 Spring 都具有分布式跟踪支持。
MicroProfile Open Tracing定义了用于访问 JAX-RS 应用程序中符合 OpenTracing的Tracer对象的行为和 API。这些行为指定传入和传出请求将如何自动创建OpenTracing Span。
当从被跟踪的客户端发送请求时,会创建一个新的 Span,并将其 SpanContext注入到出站请求中以向下游传播。如果存在活动 Span,则新 Span 将是活动 Span 的孩子。当出站请求完成时,新的 Span 将完成。所有 JAX-RS 和 Rest Client调用都会自动传播相关 ID。
您可以指定非JAX-RS操作以通过传播相关ID @Traced
,详情如下。
自定义跟踪实现
import org.eclipse.microprofile.opentracing.Traced;
import io.opentracing.ActiveSpan;
import io.opentracing.Tracer;
@Traced(value = true, operationName ="getCatalog.list")
public List<Item> getInventory() {
try (ActiveSpan childSpan = tracer.buildSpan("Grabbing messages from Messaging System").startActive()) {...}
}
访问此 Open Liberty 指南,了解有关 MicroProfile Open Tracing 的更多信息。
Spring 使用 Spring Cloud Sleuth来提供分布式跟踪支持。如果在类路径中配置了Spring cloud sleuth,则会自动生成trace信息。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
至此,您应该对 MicroProfile 和 Spring 的功能有了一些了解,让我们开始创建您的云原生微服务。
MicroProfile和Spring 都有一个起始页。
MicroProfile starter ( https://start.microprofile.io/ ) 为您提供了一种使用 MicroProfile 创建微服务的好方法,您可以选择自己喜欢的运行容器,例如Open Liberty、Thorntail、Payara、TomEE、KumuluzEE、Helidon等.
您还可以使用命令行使用 MicroProfile 创建微服务。有关如何使用命令行工具,请参阅Karm 的博客我们提供 VS Code 和 Intellij扩展插件,以允许您直接从您的 IDE 创建微服务。我们计划为其他 IDE 创建扩展,例如Eclipse IDE 等。敬请期待!
从功能的角度来看,MicroProfile 和 Spring 具有可比性。但是,它们确实存在差异,总结如下。
Spring | MicroProfile | |
---|---|---|
APIs |
开源 WMware 驱动 |
开源 |
代码行 |
多代码 做你想做/需要的事情 |
少代码 自定义服务器配置 |
库/依赖项 |
查找、混合和匹配您喜欢的内容 管理您自己的依赖项 |
服务器提供每个规范所需的内容 |
应用程序包装 |
Fat JARs |
Thin/Skinny JARs |
Spring 和 Eclipse MicroProfile 都为开发人员提供了构建下一代云原生微服务的工具,并具有以下观察结果。它们有相似之处,也有不同之处(有时是重要的)
Spring已经存在了好几年,并获得了很多人气。MicroProfile 和 Jakarta EE作为社区驱动和基于标准的企业 Java 微服务和云原生应用程序开发工作正在迅速发展(并获得动力)。
开发人员现在可以选择他们喜欢的东西,这很棒。公司应该为开发人员提供能够实现创新和灵活性并为企业和生产做好准备的平台。Open Liberty ( https://openliberty.io/ ) 是一个快速、小型和轻量级的运行时,同时支持MicroProfile/Jakarta EE 和 Spring。
本文深受将 IBM BlueCompute 微服务从 Spring 迁移到 Eclipse MicroProfile 实践的影响。可以在此处找到描述迁移的系列博客。非常感谢我的同事 YK Chang 对本文的贡献。