Narayana LRA Participant Support
Introduction
LRA(全称 Long Running Action,持久化操作)参与者扩展在基于微服务的设计中很有用,在该设计中可以通过轻松实现分布式一致性,让不同服务获益。
The LRA (short for Long Running Action) participant extension is useful in microservice based designs where different services can benefit from a relaxed notion of distributed consistency.
想法是多个服务协同执行不同的计算/操作,同时保留补偿在计算期间执行的任何操作的选项。这种服务松耦合填补了强一致性模型(例如 JTA/XA)和“自制”特设一致性解决方案之间的空白。
The idea is for multiple services to perform different computations/actions in concert, whilst retaining the option to compensate for any actions performed during the computation. This kind of loose coupling of services bridges the gap between strong consistency models such as JTA/XA and "home-grown" ad hoc consistency solutions.
该模型基于 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 中进行了说明。
The model is based on the Eclipse MicroProfile LRA specification.
The approach is for the developer to annotate a business method with a Java annotation
(@LRA
).
When such a method is called, an LRA context is created (if one is not already present) which is passed
along with subsequent Jakarta REST invocations until a method is reached
which also contains an @LRA
annotation with an attribute that indicates that the LRA should be
closed or cancelled. The default is for the LRA to be closed in the same method that started the
LRA (which itself may have propagated the context during method execution).
The Jakarta REST resource indicates that it wishes to participate in the interaction by, minimally,
marking one of the methods with an
@Compensate
annotation. If the context is later cancelled, then this @Compensate
action is guaranteed to be
called even in the presence of failures and is the trigger for the resource to compensate for any
activities it performed in the context of the LRA. This guarantee enables services to operate
reliably with the assurance of eventual consistency (when all compensation activities have
ran to completion). The participant can ask to be reliably notified when the LRA it is participating
in is closed by marking one of the methods with an
@Complete
annotation. In this way cancelling an LRA causes all participants to be notified via their Compensate callback
and closing an LRA causes all participants to be notified via their Complete callback (if they have one).
Other annotations for controlling participants are documented in the
MicroProfile LRA API v1.0 javadoc.
Configuration
在配置好 Quarkus Maven 项目后,你可以在项目基本目录中运行以下命令来添加 narayana-lra
扩展:
Once you have your Quarkus Maven project configured you can add the narayana-lra
extension
by running the following command in your project base directory:
Unresolved directive in lra.adoc - include::{includes}/devtools/extension-add.adoc[]
这会将以下内容添加到构建文件中:
This will add the following to your build file:
<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
依赖项。
quarkus-narayana-lra
needs to be complemented with a server Jakarta REST implementation and a REST Client implementation in order to work.
This means that users should also have either quarkus-resteasy-jackson
and quarkus-resteasy-client-jackson
or quarkus-rest-jackson
and quarkus-rest-client-jackson
dependencies in their application.
如果存在一个运行中的协调程序,那么以上内容就是创建新的 LRA 和让参与者加入其中的全部所需。
If there is a running coordinator then this is all you need in order to create new LRAs and to enlist participants with them.
通过更新 src/main/resources
目录中的 application.properties
文件,可以配置 LRA 扩展。唯一特定于 LRA 的属性是 quarkus.lra.coordinator-url=<url>
,它指定了一个外部协调程序的 HTTP 端点,例如:
The LRA extension can be configured by updating an application.properties
file
in the src/main/resources
directory. The only LRA specific property is
quarkus.lra.coordinator-url=<url>
which specifies the HTTP endpoint of an external
coordinator, for example:
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 应用程序服务器内部管理地运行它。
For a Narayana coordinator the path component of the url is normally lra-coordinator
.
Coordinators can be obtained from [role="bare"]https://quay.io/repository/jbosstm/lra-coordinator
or you can build your own coordinator using a maven pom that includes the appropriate
dependencies. A Quarkus quickstart will be provided to show how to do this, or you can
take a look at one of the Narayana quickstarts.
Another option would be to run it managed inside a WildFly application server.
Handling failures
当告知一个 LRA 完成时,即当调用使用 @LRA(end = true, …)
注释的方法时,协调程序将指示参与交互的所有服务完成。如果服务不可用(或仍在完成),那么协调程序将定期重试。用户负责在它们首次加入 LRA 时使用的相同端点上重新启动失败的服务,或者告诉协调程序希望在新的端点上收到通知。在我们明确说明有多少 all 参与者已确认完成之前,LRA 不会被视为完成状态。
When an LRA is told to finish, i.e. when a method annotated with @LRA(end = true, …)
is invoked, the coordinator will instruct all services involved in the interaction to
finish. If a service is unavailable (or still finishing) then the coordinator will retry
periodically. It is the user’s responsibility to restart failed services on the same
endpoint that they used when they first joined the LRA, or to tell the coordinator that
they wish to be notified on new endpoints. An LRA is not deemed finished until all
participants have acknowledged that they have finished.
协调程序负责可靠地创建和结束 LRA,并管理参与者的加入,因此必须保证协调程序可用(例如,如果协调程序或网络发生故障,那么环境中的某个事物将负责分别重新启动协调程序或修复网络)。为完成此任务,协调程序必须有权访问用于其日志的持久存储(通过文件系统或数据库访问)。在撰写本文时,管理协调程序是用户的责任。不久将提供一项“开箱即用”的解决方案。
The coordinator is responsible for reliably creating and ending LRAs and for managing participant enlistment, and it therefore must be available (for example if it or the network fail then something in the environment is responsible for restarting the coordinator or for repairing the network, respectively). To fulfill this task the coordinator must have access to durable storage for its logs (via a filesystem or in a database). At the time of writing, managing coordinators is the responsibility of the user. An "out-of-the-box" solution will be forthcoming.
Examples
以下是一个简单的示例,展示如何启动一个 LRA,以及如何在稍后取消 LRA(调用使用 @Compensate
注释的方法)或关闭 LRA(调用 @Complete
)时接收通知:
The following is a simple example of how to start an LRA and how to receive a notification
when the LRA is later cancelled (the @Compensate
annotated method is called) or closed
(@Complete
is called):
@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 注释类型读取请求头来获取其标识符。
The example also shows that when an LRA is present its identifier can be obtained
by reading the request headers via the @HeaderParam
Jakarta REST annotation type.
以下是如何在一个资源方法中启动一个 LRA 并使用 LRA
注释的 end
元素在另一个资源方法中关闭它的示例。它还展示了如何在业务方法返回 cancelOn
和 cancelOnFamily
元素中标识的特定 HTTP 状态码时将 LRA 配置为自动取消:
And here’s an example of how to start an LRA in one resource method and close it in
a different resource method using the end
element of the LRA
annotation. It also
shows how to configure the LRA to be automatically cancelled if the business method
returns the particular HTTP status codes identified in the cancelOn
and
cancelOnFamily
elements:
@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 中还有更多的示例。
The end = false
element on the bookTrip method forces the LRA to continue running when
the method finishes and the end = true
element on the confirmTrip method forces the LRA
(started by the bookTrip method) to be closed when the method finishes. Note that this
end element can be placed on any Jakarta REST resource (ie one service can start the LRA whilst
a different service ends it). There are many more examples in the
Microprofile LRA specification document and in the Microprofile LRA TCK.