diff --git a/docs/src/main/asciidoc/smallrye-fault-tolerance.adoc b/docs/src/main/asciidoc/smallrye-fault-tolerance.adoc index ea0c8b6dc7aa2..bec770f84c8a0 100644 --- a/docs/src/main/asciidoc/smallrye-fault-tolerance.adoc +++ b/docs/src/main/asciidoc/smallrye-fault-tolerance.adoc @@ -17,7 +17,7 @@ implementation of the https://github.com/eclipse/microprofile-fault-tolerance/[M specification. In this guide, we demonstrate usage of MicroProfile Fault Tolerance annotations such as `@Timeout`, `@Fallback`, -`@Retry` and `@CircuitBreaker`. +`@Retry`, `@CircuitBreaker`, and `@RateLimit`. == Prerequisites @@ -438,6 +438,76 @@ To test this out, do the following: 5. 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 + +IMPORTANT: This is an additional feature of https://github.com/smallrye/smallrye-fault-tolerance/[SmallRye Fault Tolerance] and is not specified by MicroProfile Fault Tolerance. + +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`. + +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. + +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. + +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. + +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: + +[source,java] +---- +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 coffees() { + ... + } + ... +} +---- + +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. + +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: + +[source] +---- +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) +---- + +If `@Fallback` is used with `@RateLimit`, the fallback method or handler may be invoked if a `RateLimitException` is thrown, depending on the fallback configuration. + +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. + +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 You can override the annotations parameters at runtime inside your `application.properties` file.