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 扩展:

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

这会将以下内容添加到构建文件中:

pom.xml
<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>
build.gradle
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-jacksonquarkus-resteasy-client-jackson 或者 quarkus-rest-jacksonquarkus-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 元素在另一个资源方法中关闭它的示例。它还展示了如何在业务方法返回 cancelOncancelOnFamily 元素中标识的特定 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 documentMicroprofile LRA TCK 中还有更多的示例。