Narayana LRA Participant Support
Introduction
LRA(全称 Long Running Action,持久化操作)参与者扩展在基于微服务的设计中很有用,在该设计中可以通过轻松实现分布式一致性,让不同服务获益。
想法是多个服务协同执行不同的计算/操作,同时保留补偿在计算期间执行的任何操作的选项。这种服务松耦合填补了强一致性模型(例如 JTA/XA)和“自制”特设一致性解决方案之间的空白。
该模型基于 Eclipse MicroProfile LRA specification。方法是让开发者使用 Java 注解( @LRA
)为业务方法添加批注。调用此类方法时,将创建一个 LRA 上下文(如果尚不存在),并将此上下文随后的 Jakarta REST 调用一起传递,直到达到方法包含 @LRA
注解以及指示应当关闭或取消 LRA 的属性。默认情况下,将在启动 LRA 的相同方法中关闭 LRA(方法本身可能在方法执行期间传播上下文)。Jakarta REST 资源表明它希望通过最少使用 @Compensate
注解标记其中一个方法的方式参与交互。如果稍后取消上下文,那么此 @Compensate
操作即使在出现故障的情况下也必定会被调用,并且对于资源根据 LRA 上下文执行的任何活动进行补偿,它是触发器。此保证使服务能够可靠地运行并确保最终一致性(在所有补偿活动都运行到完成后)。参与者可以通过使用 @Complete
注解标记其中一个方法来要求在它参与的 LRA 关闭时收到可靠通知。以这种方式,取消 LRA 会导致所有参与者通过其补偿回调收到通知,而关闭 LRA 会导致所有参与者通过其完成回调(如果具有)收到通知。控制参与者的其他注解在 MicroProfile LRA API v1.0 javadoc 中进行了说明。
Configuration
在配置好 Quarkus Maven 项目后,你可以在项目基本目录中运行以下命令来添加 narayana-lra
扩展:
quarkus extension add {add-extension-extensions}
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
./gradlew addExtension --extensions='{add-extension-extensions}'
这会将以下内容添加到构建文件中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-lra</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-client-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-narayana-lra")
implementation("io.quarkus:quarkus-resteasy-jackson")
implementation("io.quarkus:quarkus-resteasy-client-jackson")
quarkus-narayana-lra
为了正常工作,需要有一个用于服务器的 Jakarta REST 实现和一个用于 REST 客户端的实现。这意味着用户还需要在他们的应用程序中具有 quarkus-resteasy-jackson
和 quarkus-resteasy-client-jackson
或者 quarkus-rest-jackson
和 quarkus-rest-client-jackson
依赖项。
如果存在一个运行中的协调程序,那么以上内容就是创建新的 LRA 和让参与者加入其中的全部所需。
通过更新 src/main/resources
目录中的 application.properties
文件,可以配置 LRA 扩展。唯一特定于 LRA 的属性是 quarkus.lra.coordinator-url=<url>
,它指定了一个外部协调程序的 HTTP 端点,例如:
quarkus.lra.coordinator-url=http://localhost:8080/lra-coordinator
对于 Narayana 协调程序,url 的路径组件通常是 lra-coordinator
。可以从 [role="bare"][role="bare"]https://quay.io/repository/jbosstm/lra-coordinator 获得协调程序或者你可以使用包含适当依赖项的 Maven pom 来构建自己的协调程序。将提供一个 Quarkus 的快速入门来展示如何执行此操作,或者你可以查看 Narayana quickstarts 中的其中一个。另一个选择是在 WildFly 应用程序服务器内部管理地运行它。
Handling failures
当告知一个 LRA 完成时,即当调用使用 @LRA(end = true, …)
注释的方法时,协调程序将指示参与交互的所有服务完成。如果服务不可用(或仍在完成),那么协调程序将定期重试。用户负责在它们首次加入 LRA 时使用的相同端点上重新启动失败的服务,或者告诉协调程序希望在新的端点上收到通知。在我们明确说明有多少 all 参与者已确认完成之前,LRA 不会被视为完成状态。
协调程序负责可靠地创建和结束 LRA,并管理参与者的加入,因此必须保证协调程序可用(例如,如果协调程序或网络发生故障,那么环境中的某个事物将负责分别重新启动协调程序或修复网络)。为完成此任务,协调程序必须有权访问用于其日志的持久存储(通过文件系统或数据库访问)。在撰写本文时,管理协调程序是用户的责任。不久将提供一项“开箱即用”的解决方案。
Examples
以下是一个简单的示例,展示如何启动一个 LRA,以及如何在稍后取消 LRA(调用使用 @Compensate
注释的方法)或关闭 LRA(调用 @Complete
)时接收通知:
@Path("/")
@ApplicationScoped
public class SimpleLRAParticipant
{
@LRA(LRA.Type.REQUIRES_NEW) // a new LRA is created on method entry
@Path("/work")
@PUT
public void doInNewLongRunningAction(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId)
{
/*
* Perform business actions in the context of the LRA identified by the
* value in the injected Jakarta REST header. This LRA was started just before
* the method was entered (REQUIRES_NEW) and will be closed when the
* method finishes at which point the completeWork method below will be
* invoked.
*/
}
@org.eclipse.microprofile.lra.annotation.Complete
@Path("/complete")
@PUT
public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
String userData)
{
/*
* Free up resources allocated in the context of the LRA identified by the
* value in the injected Jakarta REST header.
*
* Since there is no @Status method in this class, completeWork MUST be
* idempotent and MUST return the status.
*/
return Response.ok(ParticipantStatus.Completed.name()).build();
}
@org.eclipse.microprofile.lra.annotation.Compensate
@Path("/compensate")
@PUT
public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
String userData)
{
/*
* The LRA identified by the value in the injected Jakarta REST header was
* cancelled so the business logic should compensate for any actions
* that have been performed while running in its context.
*
* Since there is no @Status method in this class, compensateWork MUST be
* idempotent and MUST return the status
*/
return Response.ok(ParticipantStatus.Compensated.name()).build();
}
}
示例还表明,当存在一个 LRA 时,可以通过使用 @HeaderParam
Jakarta REST 注释类型读取请求头来获取其标识符。
以下是如何在一个资源方法中启动一个 LRA 并使用 LRA
注释的 end
元素在另一个资源方法中关闭它的示例。它还展示了如何在业务方法返回 cancelOn
和 cancelOnFamily
元素中标识的特定 HTTP 状态码时将 LRA 配置为自动取消:
@LRA(value = LRA.Type.REQUIRED, // if there is no incoming context a new one is created
cancelOn = {
Response.Status.INTERNAL_SERVER_ERROR // cancel on a 500 code
},
cancelOnFamily = {
Response.Status.Family.CLIENT_ERROR // cancel on any 4xx code
},
end = false) // the LRA will continue to run when the method finishes
@Path("/book")
@POST
public Response bookTrip(...) { ... }
@LRA(value = LRA.Type.MANDATORY, // requires an active context before method can be executed
end = true) // end the LRA started by the bookTrip method
@Path("/confirm")
@PUT
public Booking confirmTrip(Booking booking) throws BookingException { ... }
bookTrip 方法上的 end = false
元素强制 LRA 在方法完成时继续运行,而 confirmTrip 方法上的 end = true
元素强制 LRA(由 bookTrip 方法启动)在方法完成时关闭。请注意,此结束元素可以放置在任何 Jakarta REST 资源上(即,一个服务可以启动 LRA,而另一个服务结束 LRA)。在 Microprofile LRA specification document 和 Microprofile LRA TCK 中还有更多的示例。