SmallRye Fault Tolerance
微服务的分布式特性带来的一个挑战是与外部系统的通信本质上不可靠。这增加了对应用程序弹性的需求。为了简化构建更具弹性的应用程序,Quarkus 提供了 SmallRye Fault Tolerance,它是 MicroProfile Fault Tolerance 规范的实现。
One of the challenges brought by the distributed nature of microservices is that communication with external systems is inherently unreliable. This increases demand on resiliency of applications. To simplify making more resilient applications, Quarkus provides SmallRye Fault Tolerance, an implementation of the MicroProfile Fault Tolerance specification.
在本文档中,我们演示了 MicroProfile 容错注解(例如 @Timeout
、@Fallback
、@Retry
、@CircuitBreaker
和 @RateLimit
)的用法。
In this guide, we demonstrate usage of MicroProfile Fault Tolerance annotations such as @Timeout
, @Fallback
,
@Retry
, @CircuitBreaker
, and @RateLimit
.
Prerequisites
Unresolved directive in smallrye-fault-tolerance.adoc - include::{includes}/prerequisites.adoc[]
The Scenario
本文档中构建的应用程序模拟了一个美食咖啡电子商店的简单后端。它实现了一个 REST 端点,提供我们商店中咖啡样品的相关信息。
The application built in this guide simulates a simple backend for a gourmet coffee e-shop. It implements a REST endpoint providing information about coffee samples we have on store.
让我们想象一下,虽然没有像这样实现,但我们端点中的一些方法需要与外部服务(如数据库或外部微服务)进行通信,这引入了不可靠性因素。
Let’s imagine, although it’s not implemented as such, that some methods in our endpoint require communication to external services like a database or an external microservice, which introduces a factor of unreliability.
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
克隆 Git 存储库: git clone {quickstarts-clone-url}
,或下载 {quickstarts-archive-url}[存档]。
Clone the Git repository: git clone {quickstarts-clone-url}
, or download an {quickstarts-archive-url}[archive].
解决方案位于 microprofile-fault-tolerance-quickstart
directory 中。
The solution is located in the microprofile-fault-tolerance-quickstart
directory.
Creating the Maven Project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
First, we need a new project. Create a new project with the following command:
Unresolved directive in smallrye-fault-tolerance.adoc - include::{includes}/devtools/create-app.adoc[]
此命令会生成一个项目,导入 Quarkus REST(以前称为 RESTEasy Reactive)/Jakarta REST 和 SmallRye Fault Tolerance 的扩展。
This command generates a project, importing the extensions for Quarkus REST (formerly RESTEasy Reactive)/Jakarta REST and SmallRye Fault Tolerance.
如果您已经配置了 Quarkus 项目,则可以通过在项目基础目录中运行以下命令,将 smallrye-fault-tolerance
扩展添加到您的项目中:
If you already have your Quarkus project configured, you can add the smallrye-fault-tolerance
extension
to your project by running the following command in your project base directory:
Unresolved directive in smallrye-fault-tolerance.adoc - include::{includes}/devtools/extension-add.adoc[]
这会将以下内容添加到构建文件中:
This will add the following to your build file:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-fault-tolerance</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-fault-tolerance")
Preparing an Application: REST Endpoint and CDI Bean
在本节中,我们将创建应用程序的框架,以便我们能够扩展它并在以后向其添加容错功能。
In this section we create a skeleton of our application, so that we have something that we can extend and to which we can add fault tolerance features later on.
首先,创建表示商店中咖啡样品的简单实体:
First, create a simple entity representing a coffee sample in our store:
package org.acme.microprofile.faulttolerance;
public class Coffee {
public Integer id;
public String name;
public String countryOfOrigin;
public Integer price;
public Coffee() {
}
public Coffee(Integer id, String name, String countryOfOrigin, Integer price) {
this.id = id;
this.name = name;
this.countryOfOrigin = countryOfOrigin;
this.price = price;
}
}
让我们继续使用一个简单的 CDI bean,它可以充当我们咖啡样品的存储库。
Let’s continue with a simple CDI bean, that would work as a repository of our coffee samples.
package org.acme.microprofile.faulttolerance;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CoffeeRepositoryService {
private Map<Integer, Coffee> coffeeList = new HashMap<>();
public CoffeeRepositoryService() {
coffeeList.put(1, new Coffee(1, "Fernandez Espresso", "Colombia", 23));
coffeeList.put(2, new Coffee(2, "La Scala Whole Beans", "Bolivia", 18));
coffeeList.put(3, new Coffee(3, "Dak Lak Filter", "Vietnam", 25));
}
public List<Coffee> getAllCoffees() {
return new ArrayList<>(coffeeList.values());
}
public Coffee getCoffeeById(Integer id) {
return coffeeList.get(id);
}
public List<Coffee> getRecommendations(Integer id) {
if (id == null) {
return Collections.emptyList();
}
return coffeeList.values().stream()
.filter(coffee -> !id.equals(coffee.id))
.limit(2)
.collect(Collectors.toList());
}
}
最后,按照以下方式创建 org.acme.microprofile.faulttolerance.CoffeeResource
类:
Finally, create the org.acme.microprofile.faulttolerance.CoffeeResource
class as follows:
package org.acme.microprofile.faulttolerance;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.jboss.logging.Logger;
@Path("/coffee")
public class CoffeeResource {
private static final Logger LOGGER = Logger.getLogger(CoffeeResource.class);
@Inject
CoffeeRepositoryService coffeeRepository;
private AtomicLong counter = new AtomicLong(0);
@GET
public List<Coffee> coffees() {
final Long invocationNumber = counter.getAndIncrement();
maybeFail(String.format("CoffeeResource#coffees() invocation #%d failed", invocationNumber));
LOGGER.infof("CoffeeResource#coffees() invocation #%d returning successfully", invocationNumber);
return coffeeRepository.getAllCoffees();
}
private void maybeFail(String failureLogMessage) {
if (new Random().nextBoolean()) {
LOGGER.error(failureLogMessage);
throw new RuntimeException("Resource failure.");
}
}
}
此时,我们公开了单个 REST 方法,该方法将以 JSON 格式显示咖啡样品列表。请注意,我们在 CoffeeResource#maybeFail()
方法中引入了一些产生错误的代码,这将导致大约 50% 的请求在 CoffeeResource#coffees()
端点方法中发生故障。
At this point, we expose a single REST method that will show a list of coffee samples in a JSON format. Note
that we introduced some fault making code in our CoffeeResource#maybeFail()
method, which is going to cause failures
in the CoffeeResource#coffees()
endpoint method in about 50 % of requests.
何不检查一下我们的应用程序是否工作?使用以下命令运行 Quarkus 开发服务器:
Why not check that our application works? Run the Quarkus development server with:
Unresolved directive in smallrye-fault-tolerance.adoc - include::{includes}/devtools/dev.adoc[]
并在浏览器中打开 http://localhost:8080/coffee
。进行几个请求(请记住,其中一些请求可能会失败)。至少有些请求应该会向我们展示 JSON 中的咖啡样本列表,而其余的会以抛出的 RuntimeException
为 CoffeeResource#maybeFail()
失败。
and open http://localhost:8080/coffee
in your browser. Make a couple of requests (remember, some of them are expected
to fail). At least some requests should show us the list of our coffee samples in JSON, the rest will fail
with a RuntimeException
thrown in CoffeeResource#maybeFail()
.
恭喜,你刚刚制作了一个有效的(尽管有点不可靠)Quarkus 应用程序!
Congratulations, you’ve just made a working (although somewhat unreliable) Quarkus application!
Adding Resiliency: Retries
保持 Quarkus 开发服务器运行,并在 IDE 中将 @Retry
注释添加到 `CoffeeResource#coffees()`方法中,如下所示,然后保存文件:
Let the Quarkus development server running and in your IDE add the @Retry
annotation to the CoffeeResource#coffees()
method as follows and save the file:
import org.eclipse.microprofile.faulttolerance.Retry;
...
public class CoffeeResource {
...
@GET
@Retry(maxRetries = 4)
public List<Coffee> coffees() {
...
}
...
}
在浏览器中点击刷新。Quarkus 开发服务器将自动检测这些更改,并自动为你重新编译应用程序,因此无需重新启动它。
Hit refresh in your browser. The Quarkus development server will automatically detect the changes and recompile the app for you, so there’s no need to restart it.
你可以再点击刷新几次。实际上,几乎所有的请求现在都应该成功了。CoffeeResource#coffees()
方法实际上在 50% 的时间内仍然失败,但每次发生这种情况时,该平台都会自动重试调用!
You can hit refresh couple more times. Practically all requests should now be succeeding. The CoffeeResource#coffees()
method is still in fact failing in about 50 % of time, but every time it happens, the platform will automatically retry
the call!
若要查看仍然出现的故障,请查看开发服务器的输出。日志消息应类似于以下内容:
To see that the failures still happen, check the output of the development server. The log messages should be similar to these:
2019-03-06 12:17:41,725 INFO [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #5 returning successfully
2019-03-06 12:17:44,187 INFO [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #6 returning successfully
2019-03-06 12:17:45,166 ERROR [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #7 failed
2019-03-06 12:17:45,172 ERROR [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #8 failed
2019-03-06 12:17:45,176 INFO [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #9 returning successfully
你可以看到,每次调用失败后,会立即接收到另一个调用,直到成功。由于我们允许 4 次重试,因此为了实际向用户显示故障,它需要连续失败 5 次调用。这不太可能发生。
You can see that every time an invocation fails, it’s immediately followed by another invocation, until one succeeds. Since we allowed 4 retries, it would require 5 invocations to fail in a row, in order for the user to be actually exposed to a failure. Which is fairly unlikely to happen.
Adding Resiliency: Timeouts
那么,MicroProfile 容错中还有什么?我们来看看超时。
So what else have we got in MicroProfile Fault Tolerance? Let’s look into timeouts.
将以下两个方法添加到我们的 CoffeeResource
端点。同样,无需重新启动服务器,只需粘贴代码并保存文件即可。
Add following two methods to our CoffeeResource
endpoint. Again, no need to restart the server, just paste the code
and save the file.
import org.eclipse.microprofile.faulttolerance.Timeout;
...
public class CoffeeResource {
...
@GET
@Path("/{id}/recommendations")
@Timeout(250)
public List<Coffee> recommendations(int id) {
long started = System.currentTimeMillis();
final long invocationNumber = counter.getAndIncrement();
try {
randomDelay();
LOGGER.infof("CoffeeResource#recommendations() invocation #%d returning successfully", invocationNumber);
return coffeeRepository.getRecommendations(id);
} catch (InterruptedException e) {
LOGGER.errorf("CoffeeResource#recommendations() invocation #%d timed out after %d ms",
invocationNumber, System.currentTimeMillis() - started);
return null;
}
}
private void randomDelay() throws InterruptedException {
Thread.sleep(new Random().nextInt(500));
}
}
我们添加了一些新功能。我们希望能够根据用户当前正在查看的咖啡推荐一些相关的咖啡。这不是关键功能,而是锦上添花。当系统超载并且执行获取建议背后的逻辑花费的时间过长时,我们宁愿超时并渲染没有建议的 UI。
We added some new functionality. We want to be able to recommend some related coffees based on a coffee that a user is currently looking at. It’s not a critical functionality, it’s a nice-to-have. When the system is overloaded and the logic behind obtaining recommendations takes too long to execute, we would rather time out and render the UI without recommendations.
请注意,超时配置为 250 毫秒,并且在 CoffeeResource#recommendations()
方法中引入了 0 到 500 毫秒之间的随机人工延迟。
Note that the timeout was configured to 250 ms, and a random artificial delay between 0 and 500 ms was introduced
into the CoffeeResource#recommendations()
method.
在浏览器中,转到 http://localhost:8080/coffee/2/recommendations
并点击刷新几次。
In your browser, go to http://localhost:8080/coffee/2/recommendations
and hit refresh a couple of times.
你应该会看到一些请求以 org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException
超时。未超时的请求应以 JSON 格式显示两个推荐的咖啡样本。
You should see some requests time out with org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException
.
Requests that do not time out should show two recommended coffee samples in JSON.
Adding Resiliency: Fallbacks
让我们通过提供获取相关咖啡的备用(并且可能更快的)方式来让我们的推荐功能变得更好。
Let’s make our recommendations feature even better by providing a fallback (and presumably faster) way of getting related coffees.
向 CoffeeResource
添加一个备用方法,并在 CoffeeResource#recommendations()
方法中添加一个 @Fallback
注释,如下所示:
Add a fallback method to CoffeeResource
and a @Fallback
annotation to CoffeeResource#recommendations()
method
as follows:
import java.util.Collections;
import org.eclipse.microprofile.faulttolerance.Fallback;
...
public class CoffeeResource {
...
@Fallback(fallbackMethod = "fallbackRecommendations")
public List<Coffee> recommendations(int id) {
...
}
public List<Coffee> fallbackRecommendations(int id) {
LOGGER.info("Falling back to RecommendationResource#fallbackRecommendations()");
// safe bet, return something that everybody likes
return Collections.singletonList(coffeeRepository.getCoffeeById(1));
}
...
}
在 http://localhost:8080/coffee/2/recommendations
上点击刷新几次。TimeoutException
应该不再出现了。相反,在超时的情况下,页面将显示我们在备用方法 fallbackRecommendations()
中硬编码的单个建议,而不是原始方法返回的两个建议。
Hit refresh several times on http://localhost:8080/coffee/2/recommendations
.
The TimeoutException
should not appear anymore. Instead, in case of a timeout, the page will
display a single recommendation that we hardcoded in our fallback method fallbackRecommendations()
, rather than
two recommendations returned by the original method.
检查服务器输出以查看是否真的发生了备用:
Check the server output to see that fallback is really happening:
2020-01-09 13:21:34,250 INFO [org.acm.fau.CoffeeResource] (executor-thread-1) CoffeeResource#recommendations() invocation #1 returning successfully
2020-01-09 13:21:36,354 ERROR [org.acm.fau.CoffeeResource] (executor-thread-1) CoffeeResource#recommendations() invocation #2 timed out after 250 ms
2020-01-09 13:21:36,355 INFO [org.acm.fau.CoffeeResource] (executor-thread-1) Falling back to RecommendationResource#fallbackRecommendations()
备用方法需要具备与原始方法相同的参数。 |
The fallback method is required to have the same parameters as the original method. |
Adding Resiliency: Circuit Breaker
当系统的一部分暂时不稳定时,断路器对于限制系统中发生的故障数量非常有用。断路器会记录方法的成功调用和失败调用,当失败调用的比率达到指定阈值时,断路器会 opens 并阻止所有进一步调用该方法一段时间。
A circuit breaker is useful for limiting number of failures happening in the system, when part of the system becomes temporarily unstable. The circuit breaker records successful and failed invocations of a method, and when the ratio of failed invocations reaches the specified threshold, the circuit breaker opens and blocks all further invocations of that method for a given time.
将以下代码添加到 CoffeeRepositoryService
中,以便演示断路器在操作中的情况:
Add the following code into the CoffeeRepositoryService
bean, so that we can demonstrate a circuit breaker in action:
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
...
public class CoffeeRepositoryService {
...
private AtomicLong counter = new AtomicLong(0);
@CircuitBreaker(requestVolumeThreshold = 4)
public Integer getAvailability(Coffee coffee) {
maybeFail();
return new Random().nextInt(30);
}
private void maybeFail() {
// introduce some artificial failures
final Long invocationNumber = counter.getAndIncrement();
if (invocationNumber % 4 > 1) { // alternate 2 successful and 2 failing invocations
throw new RuntimeException("Service failed.");
}
}
}
并将下面的代码插入到 CoffeeResource
终端:
And inject the code below into the CoffeeResource
endpoint:
public class CoffeeResource {
...
@Path("/{id}/availability")
@GET
public Response availability(int id) {
final Long invocationNumber = counter.getAndIncrement();
Coffee coffee = coffeeRepository.getCoffeeById(id);
// check that coffee with given id exists, return 404 if not
if (coffee == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
try {
Integer availability = coffeeRepository.getAvailability(coffee);
LOGGER.infof("CoffeeResource#availability() invocation #%d returning successfully", invocationNumber);
return Response.ok(availability).build();
} catch (RuntimeException e) {
String message = e.getClass().getSimpleName() + ": " + e.getMessage();
LOGGER.errorf("CoffeeResource#availability() invocation #%d failed: %s", invocationNumber, message);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(message)
.type(MediaType.TEXT_PLAIN_TYPE)
.build();
}
}
...
}
我们添加了另一项功能——应用程序可以在我们的商店中返回指定咖啡的剩余包装数量(只是一个随机数)。
We added another functionality - the application can return the amount of remaining packages of given coffee on our store (just a random number).
这次在 CDI bean 中引入了一个人为的故障: CoffeeRepositoryService#getAvailability()
方法将在两次成功的调用和两次失败的调用之间进行交替。
This time an artificial failure was introduced in the CDI bean: the CoffeeRepositoryService#getAvailability()
method is
going to alternate between two successful and two failed invocations.
我们还添加了一个带有 requestVolumeThreshold = 4
的 @CircuitBreaker
注解。 CircuitBreaker.failureRatio
默认为 0.5,而 CircuitBreaker.delay
默认为 5 秒。这意味着当最近 4 次调用中 2 次失败时,断路器将打开,且将保持打开状态 5 秒钟。
We also added a @CircuitBreaker
annotation with requestVolumeThreshold = 4
. CircuitBreaker.failureRatio
is
by default 0.5, and CircuitBreaker.delay
is by default 5 seconds. That means that a circuit breaker will open
when 2 of the last 4 invocations failed, and it will stay open for 5 seconds.
要进行测试,请执行以下操作:
To test this out, do the following:
-
Go to
http://localhost:8080/coffee/2/availability
in your browser. You should see a number being returned. -
Hit refresh, this second request should again be successful and return a number.
-
Refresh two more times. Both times you should see text "RuntimeException: Service failed.", which is the exception thrown by
CoffeeRepositoryService#getAvailability()
. -
Refresh a couple more times. Unless you waited too long, you should again see exception, but this time it’s "CircuitBreakerOpenException: getAvailability". This exception indicates that the circuit breaker opened and the
CoffeeRepositoryService#getAvailability()
method is not being called anymore. -
Give it 5 seconds during which circuit breaker should close, and you should be able to make two successful requests again.
Adding Resiliency: Rate Limits
这是 SmallRye Fault Tolerance 的一个附加功能,并未由 MicroProfile 容错机制指定。
This is an additional feature of SmallRye Fault Tolerance and is not specified by MicroProfile Fault Tolerance.
可以使用 rate limit 来防止某个操作被执行得太频繁。速率限制会在一段时间内强制执行允许调用的最大次数。例如,通过速率限制,你可以确保某个方法每分钟只可以调用 50 次。超过此限制的调用将被拒绝,并抛出 RateLimitException
类型的异常。
It is possible to prevent an operation from being executed too often using a rate limit. Rate limits enforce a maximum number of permitted invocations in a time window of some length. For example, with a rate limit, one can make sure that a method may only be called 50 times per minute. Invocations that would exceed the limit are rejected with an exception of type RateLimitException
.
此外,可以在调用之间定义最小间隔。例如,对于 1 秒的最小间隔,如果在第一个调用后 500 毫秒发生第二个调用,则会拒绝该调用,即使尚未超过限制。
Additionally, it is possible to define minimum spacing between invocations. For example, with a minimum spacing of 1 second, if a second invocation happens 500 millis after the first, it is rejected even if the limit would not be exceeded yet.
速率限制表面上与隔离仓(并发限制)类似,但实际上有很大不同。隔离仓限制在任何时间点并发发生的执行次数。速率限制限制一段时间内执行的次数,而不考虑并发性。
Rate limit is superficially similar to a bulkhead (concurrency limit), but is in fact quite different. Bulkhead limits the number of executions happening concurrently at any point in time. Rate limit limits the number of executions in a time window of some length, without considering concurrency.
速率限制需要在调用之间保持某种状态:最近调用的次数、上次调用的时间戳,等等。此状态是一个单例,与使用 @RateLimit
注解的 bean 的生命周期无关。
Rate limits need to maintain some state between invocations: the number of recent invocations, the time stamp of last invocation, and so on. This state is a singleton, irrespective of the lifecycle of the bean that uses the @RateLimit
annotation.
更具体地说,速率限制状态由 bean 类 (java.lang.Class
) 和表示受保护方法的方法对象 (java.lang.reflect.Method
) 的组合唯一标识。
More specifically, the rate limit state is uniquely identified by the combination of the bean class (java.lang.Class
) and the method object (java.lang.reflect.Method
) representing the guarded method.
让 Quarkus 开发模式运行,并在你的 IDE 中将 @RateLimit
注解添加到 CoffeeResource#coffees()
方法,如下所示并保存文件:
Let the Quarkus development mode run and in your IDE add the @RateLimit
annotation to the CoffeeResource#coffees()
method as follows and save the file:
import java.time.temporal.ChronoUnit;
import io.smallrye.faulttolerance.api.RateLimit;
...
public class CoffeeResource {
...
@GET
@RateLimit(value = 2, window = 10, windowUnit = ChronoUnit.SECONDS)
public List<Coffee> coffees() {
...
}
...
}
点击浏览器中的刷新。 Quarkus 开发服务器将自动检测更改并重新编译应用程序,因此无需重新启动它。
Hit refresh in your browser. The Quarkus development server will automatically detect the changes and recompile the app for you, so there’s no need to restart it.
可以再点击几次刷新。在 10 秒间隔内发出 2 个请求后,你应该会开始在日志中看到错误,类似于以下错误:
You can hit refresh a couple more times. After 2 requests within a 10 second interval you should start seeing errors in the logs, similar to these:
INFO [org.acm.mic.fau.CoffeeResource] (executor-thread-1) CoffeeResource#coffees() invocation #1 returning successfully
ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /coffee failed, error id: d3e59090-fd45-4c67-844e-80a8f7fa6ee0-4: io.smallrye.faulttolerance.api.RateLimitException: org.acme.microprofile.faulttolerance.CoffeeResource#coffees rate limit exceeded
at io.smallrye.faulttolerance.core.rate.limit.RateLimit.doApply(RateLimit.java:58)
at io.smallrye.faulttolerance.core.rate.limit.RateLimit.apply(RateLimit.java:44)
at io.smallrye.faulttolerance.FaultToleranceInterceptor.syncFlow(FaultToleranceInterceptor.java:255)
at io.smallrye.faulttolerance.FaultToleranceInterceptor.intercept(FaultToleranceInterceptor.java:182)
at io.smallrye.faulttolerance.FaultToleranceInterceptor_Bean.intercept(Unknown Source)
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:42)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:30)
at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:27)
at org.acme.microprofile.faulttolerance.CoffeeResource_Subclass.coffees(Unknown Source)
at org.acme.microprofile.faulttolerance.CoffeeResource$quarkusrestinvoker$coffees_73d7590ab944adfa130e4ad57c30282f825b2d18.invoke(Unknown Source)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:599)
at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2516)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2495)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1521)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:1583)
如果 @Fallback
与 @RateLimit
结合使用,则当抛出 RateLimitException
时,可能会调用后备方法或处理程序,具体取决于后备配置。
If @Fallback
is used with @RateLimit
, the fallback method or handler may be invoked if a RateLimitException
is thrown, depending on the fallback configuration.
如果 @Retry
与 @RateLimit
结合使用,那么限速会将每次重试尝试视为独立的调用来处理。如果抛出 RateLimitException
,则可能会重试执行,具体取决于如何配置重试。
If @Retry
is used with @RateLimit
, each retry attempt is processed by the rate limit as an independent invocation. If RateLimitException
is thrown, the execution may be retried, depending on how the retry is configured.
如果 @CircuitBreaker
与 @RateLimit
结合使用,那么在强制执行限速前会检查熔断器。如果限速导致 RateLimitException
,这可能会被计算为失败,具体取决于如何配置熔断器。
If @CircuitBreaker
is used with @RateLimit
, the circuit breaker is checked before enforcing the rate limit. If rate limiting results in RateLimitException
, this may be counted as a failure, depending on how the circuit breaker is configured.
Runtime configuration
可以在 application.properties
文件内在运行时覆盖注释参数。
You can override the annotations parameters at runtime inside your application.properties
file.
如果采用我们已经看过的重试示例:
If we take the retry example that we already saw:
package org.acme;
import org.eclipse.microprofile.faulttolerance.Retry;
...
public class CoffeeResource {
...
@GET
@Retry(maxRetries = 4)
public List<Coffee> coffees() {
...
}
...
}
可以通过以下配置项,将 maxRetries
参数从 4 次重试覆盖为 6 次重试:
We can override the maxRetries
parameter with 6 retries instead of 4 by the following configuration item:
org.acme.CoffeeResource/coffees/Retry/maxRetries=6
格式为 |
The format is |
Conclusion
SmallRye Fault Tolerance 允许改善应用程序的弹性,而不会影响业务逻辑的复杂性。
SmallRye Fault Tolerance allows to improve resiliency of your application, without having an impact on the complexity of our business logic.
在 Quarkus 中启用容错功能所需的一切是:
All that is needed to enable the fault tolerance features in Quarkus is:
-
adding the
smallrye-fault-tolerance
Quarkus extension to your project using thequarkus-maven-plugin
:include::{includes}/devtools/extension-add.adoc[] -
or simply adding the following Maven dependency:[source, xml] .pom.xml
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-fault-tolerance</artifactId> </dependency>
implementation("io.quarkus:quarkus-smallrye-fault-tolerance")
Additional resources
SmallRye Fault Tolerance 具有此处显示的更多功能。请查看 SmallRye Fault Tolerance documentation 以了解它们。
SmallRye Fault Tolerance has more features than shown here. Please check the SmallRye Fault Tolerance documentation to learn about them.
在 Quarkus 中,你可以开箱即用地使用 SmallRye Fault Tolerance 可选功能。
In Quarkus, you can use the SmallRye Fault Tolerance optional features out of the box.
支持 Mutiny,因此除了 CompletionStage
之外异步方法还可以返回 Uni
。
Support for Mutiny is present, so your asynchronous methods can return Uni
in addition to CompletionStage
.
MicroProfile Context Propagation 已与 Fault Tolerance 集成,因此现有上下文会自动传播到异步方法。
MicroProfile Context Propagation is integrated with Fault Tolerance, so existing contexts are automatically propagated to your asynchronous methods.
这也适用于 CDI 请求上下文:如果它在原始线程上处于活动状态,则它将传播到新线程,但如果不是,那么新线程也不会拥有它。这与 MicroProfile Fault Tolerance 规范相反,该规范指出请求上下文必须在 This also applies to the CDI request context: if it is active on the original thread, it is propagated to the new thread, but if it’s not, then the new thread won’t have it either.
This is contrary to MicroProfile Fault Tolerance specification, which states that the request context must be active during the 我们认为,在存在 MicroProfile Context Propagation 的情况下,不应适用此要求。上下文传播的全部目的是确保新线程具有与原始线程相同的上下文。 We believe that in presence of MicroProfile Context Propagation, this requirement should not apply. The entire point of context propagation is to make sure the new thread has the same contexts as the original thread. |
默认情况下启用非兼容模式。这意味着,返回 CompletionStage
(或 Uni
)的方法已应用了无任何 @Asynchronous
、 @AsynchronousNonBlocking
、 @Blocking
或 @NonBlocking
注解的异步容错。这也意味着断路器、后备和重试功能会自动检查异常原因链,如果异常本身不足以决策后续操作。
Non-compatible mode is enabled by default.
This means that methods that return CompletionStage
(or Uni
) have asynchronous fault tolerance applied without any @Asynchronous
, @AsynchronousNonBlocking
, @Blocking
or @NonBlocking
annotation.
It also means that circuit breaker, fallback and retry automatically inspect the exception cause chain if the exception itself is insufficient to decide what should happen.
此模式与 MicroProfile 容错规范不兼容,但这种不兼容性很小。若要恢复完全兼容性,请添加此配置属性: This mode is not compatible with the MicroProfile Fault Tolerance specification, albeit the incompatibility is very small. To restore full compatibility, add this configuration property:
|
存在 programmatic API ,包括 Mutiny 支持,并且与基于声明和基于注释的 API 相集成。你可以开箱即用地使用 FaultTolerance
和 MutinyFaultTolerance
API。
The programmatic API is present, including Mutiny support, and integrated with the declarative, annotation-based API.
You can use the FaultTolerance
and MutinyFaultTolerance
APIs out of the box.
目前支持 Kotlin(假定你使用 Kotlin 的 Quarkus 扩展),因此你可以使用容错注解保护 suspend
函数。
Support for Kotlin is present (assuming you use the Quarkus extension for Kotlin), so you can guard your suspend
functions with fault tolerance annotations.
度量标准会自动发现并集成。如果你的应用程序使用 Micrometer 的 Quarkus 扩展,则 SmallRye 容错会向 Micrometer 发送度量标准。如果你的应用程序使用 SmallRye 度量标准的 Quarkus 扩展,则 SmallRye 容错会向 MicroProfile 度量标准发送度量标准。否则,将禁用 SmallRye 容错度量标准。
Metrics are automatically discovered and integrated. If your application uses the Quarkus extension for Micrometer, SmallRye Fault Tolerance will emit metrics to Micrometer. If your application uses the Quarkus extension for SmallRye Metrics, SmallRye Fault Tolerance will emit metrics to MicroProfile Metrics. Otherwise, SmallRye Fault Tolerance metrics will be disabled.