Skip to content

Commit

Permalink
Merge pull request #71 from Ladicek/improvements
Browse files Browse the repository at this point in the history
Improvements
  • Loading branch information
vietj authored Sep 6, 2023
2 parents 9a99a8a + 50ffaf4 commit 0ecd7e1
Show file tree
Hide file tree
Showing 28 changed files with 392 additions and 1,459 deletions.
62 changes: 20 additions & 42 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
<version>5.0.0-SNAPSHOT</version>

<properties>
<hystrix.version>1.5.2</hystrix.version>
<codegen.rxjava.deprecated>true</codegen.rxjava.deprecated>
<jar.manifest>${project.basedir}/src/main/resources/META-INF/MANIFEST.MF</jar.manifest>
</properties>

Expand All @@ -44,24 +42,6 @@
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-hazelcast</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<!-- for metrics -->
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
<version>2.1.12</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
Expand All @@ -77,55 +57,53 @@
<optional>true</optional>
</dependency>

<!-- For tests and examples -->
<!-- for metrics -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>${hystrix.version}</version>
<scope>provided</scope>
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
<version>2.1.12</version>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>rest-assured</artifactId>
<version>2.8.0</version>
<scope>test</scope>
</dependency>
<!-- for tests -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.3.0</version>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.awaitility</groupId>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>1.7.0</version>
<version>4.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.2.1</version>
<version>2.27.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-hazelcast</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-unit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
133 changes: 47 additions & 86 deletions src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
= Vert.x Circuit Breaker

Vert.x Circuit Breaker is an implementation of the Circuit Breaker _pattern_ for Vert.x. It keeps track of the
number of failures and _opens the circuit_ when a threshold is reached. Optionally, a fallback is executed.
Vert.x Circuit Breaker is an implementation of the _circuit breaker_ pattern for Vert.x. It keeps track of the
number of recent failures and prevents further executions when a threshold is reached. Optionally, a fallback is executed.

Supported failures are:

Expand All @@ -12,9 +12,9 @@ Supported failures are:
Operations guarded by a circuit breaker are intended to be non-blocking and asynchronous in order to benefit from
the Vert.x execution model.

== Using the vert.x circuit breaker
== Using Vert.x Circuit Breaker

To use the Vert.x Circuit Breaker, add the following dependency to the _dependencies_ section of your build
To use Vert.x Circuit Breaker, add the following dependency to the _dependencies_ section of your build
descriptor:

* Maven (in your `pom.xml`):
Expand All @@ -39,8 +39,8 @@ compile 'io.vertx:vertx-circuit-breaker:${maven.version}'

To use the circuit breaker you need to:

1. Create a circuit breaker, with the configuration you want (timeout, number of failure before opening the circuit)
2. Execute some code using the breaker
1. Create a circuit breaker, with the configuration you want (timeout, failure threshold)
2. Execute some code using the circuit breaker

**Important**: Don't recreate a circuit breaker on every call. A circuit breaker is a stateful entity. It is recommended
to store the circuit breaker instance in a field.
Expand All @@ -52,8 +52,8 @@ Here is an example:
{@link examples.CircuitBreakerExamples#example1(io.vertx.core.Vertx)}
----

The executed block receives a {@link io.vertx.core.Future} object as parameter, to denote the
success or failure of the operation as well as the result. For example in the following example, the result is the
The executed block receives a {@link io.vertx.core.Promise} object as parameter, to denote the
success or failure of the operation as well as the result. In the following example, the result is the
output of a REST endpoint invocation:

[source,$lang]
Expand All @@ -64,18 +64,18 @@ output of a REST endpoint invocation:
The result of the operation is provided using the:

* returned {@link io.vertx.core.Future} when calling `execute` methods
* provided {@link io.vertx.core.Future} when calling the `executeAndReport` methods
* provided {@link io.vertx.core.Promise} when calling the `executeAndReport` methods

Optionally, you can provide a fallback which is executed when the circuit is open:
Optionally, you can provide a fallback which is executed when the circuit breaker is open:

[source,$lang]
----
{@link examples.CircuitBreakerExamples#example3(io.vertx.core.Vertx)}
----

The fallback is called whenever the circuit is open, or if the
{@link io.vertx.circuitbreaker.CircuitBreakerOptions#isFallbackOnFailure()} is enabled. When a fallback is
set, the result is using the output of the fallback function. The fallback function takes as parameter a
The fallback is called when the circuit breaker is open, or when
{@link io.vertx.circuitbreaker.CircuitBreakerOptions#isFallbackOnFailure()} is enabled. When fallback is
set, the overall result is obtained by calling the fallback function. The fallback function takes as parameter a
{@link java.lang.Throwable} object and returns an object of the expected type.

The fallback can also be set on the {@link io.vertx.circuitbreaker.CircuitBreaker} object directly:
Expand All @@ -85,24 +85,31 @@ The fallback can also be set on the {@link io.vertx.circuitbreaker.CircuitBreake
{@link examples.CircuitBreakerExamples#example4(io.vertx.core.Vertx)}
----

=== Reported exceptions

The fallback receives:

* {@link io.vertx.circuitbreaker.OpenCircuitException} when the circuit breaker is open
* {@link io.vertx.circuitbreaker.TimeoutException} when the operation timed out

== Retries

You can also specify how often the circuit breaker should try your code before failing with {@link io.vertx.circuitbreaker.CircuitBreakerOptions#setMaxRetries(int)}.
You can also specify how often the circuit breaker should execute your code before failing with {@link io.vertx.circuitbreaker.CircuitBreakerOptions#setMaxRetries(int)}.
If you set this to something higher than 0, your code gets executed several times before finally failing in the last execution.
If the code succeeded in one of the retries your handler gets notified and any retries left are skipped.
Retries are only supported when the circuit is closed.
If the code succeeds in one of the retries, your handler gets notified and no more retries occur.
Retries are only supported when the circuit breaker is closed.

NOTE: If you set `maxRetries` to 2, your operation may be called 3 times: the initial attempt and 2 retries.

By default, the timeout between retries is set to 0, which means that retries will be executed one after another without any delay.
By default, the delay between retries is set to 0, which means that retries will be executed one after another immediately.
This, however, will result in increased load on the called service and may delay its recovery.
In order to mitigate this problem, it is recommended to execute retries with a delay.

The {@link io.vertx.circuitbreaker.CircuitBreaker#retryPolicy(io.vertx.circuitbreaker.RetryPolicy)} method can be used to specify a retry policy.
A retry policy is a function which receives the operation failure and retry count as arguments and returns a timeout in milliseconds before retry is executed.
A retry policy is a function which receives the operation failure and retry count as arguments and returns a delay in milliseconds before retry should be executed.

It allows to implement complex policies, e.g. using the value of the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After[`Retry-After`] header sent by an unavailable service.
But a few common policies are provided: {@link io.vertx.circuitbreaker.RetryPolicy#constantDelay}, {@link io.vertx.circuitbreaker.RetryPolicy#linearDelay} and {@link io.vertx.circuitbreaker.RetryPolicy#exponentialDelayWithJitter}
Some common policies are provided out of the box: {@link io.vertx.circuitbreaker.RetryPolicy#constantDelay}, {@link io.vertx.circuitbreaker.RetryPolicy#linearDelay} and {@link io.vertx.circuitbreaker.RetryPolicy#exponentialDelayWithJitter}

Below is an example of exponential delay with jitter:

Expand All @@ -113,19 +120,19 @@ Below is an example of exponential delay with jitter:

== Callbacks

You can also configures callbacks invoked when the circuit is opened or closed:
You can also configure callbacks invoked when the circuit breaker is opened or closed:

[source,$lang]
----
{@link examples.CircuitBreakerExamples#example5(io.vertx.core.Vertx)}
----

You can also be notified when the circuit breaker decides to attempt to reset (half-open state). You can register
You can also be notified when the circuit breaker moves to the half-open state, in an attempt to reset. You can register
such a callback with {@link io.vertx.circuitbreaker.CircuitBreaker#halfOpenHandler(io.vertx.core.Handler)}.

== Event bus notification

Every time the circuit state changes, an event can be published on the event bus.
Every time the circuit breaker state changes, an event can be published on the event bus.

To enable this feature, set the {@link io.vertx.circuitbreaker.CircuitBreakerOptions#setNotificationAddress(java.lang.String) notification address} to a value that is not `null`:

Expand All @@ -134,7 +141,8 @@ To enable this feature, set the {@link io.vertx.circuitbreaker.CircuitBreakerOpt
{@link examples.CircuitBreakerExamples#enableNotifications}
----

The event contains circuit breaker metrics which computation requires the following dependency to be added the _dependencies_ section of your build descriptor:
The event contains circuit breaker metrics.
Computing these metrics requires the following dependency to be added the _dependencies_ section of your build descriptor:

* Maven (in your `pom.xml`):

Expand All @@ -156,7 +164,7 @@ compile 'org.hdrhistogram:HdrHistogram:2.1.12'

[NOTE]
====
When enabled, notifications are, by default, delivered only to local consumers.
When enabled, notifications are delivered only to local consumers by default.
If the notification must be sent to all consumers in a cluster, you can change this behavior with {@link io.vertx.circuitbreaker.CircuitBreakerOptions#setNotificationLocalOnly}.
====

Expand All @@ -170,73 +178,26 @@ Each event contains a Json Object with:

== The half-open state

When the circuit is "open", calls to the circuit breaker fail immediately, without any attempt to execute the real
operation. After a suitable amount of time (configured from
{@link io.vertx.circuitbreaker.CircuitBreakerOptions#setResetTimeout(long)}, the circuit breaker decides that the
When the circuit breaker is `open`, calls to the circuit breaker fail immediately, without any attempt to execute the real
operation. After a suitable amount of time (configured by
{@link io.vertx.circuitbreaker.CircuitBreakerOptions#setResetTimeout(long)}), the circuit breaker decides that the
operation has a chance of succeeding, so it goes into the `half-open` state. In this state, the next call to the
circuit breaker is allowed to execute the dangerous operation. Should the call succeed, the circuit breaker resets
circuit breaker is allowed to execute the guarded operation. Should the call succeed, the circuit breaker resets
and returns to the `closed` state, ready for more routine operation. If this trial call fails, however, the circuit
breaker returns to the `open` state until another timeout elapses.

== Reported exceptions

The fallback receives a:

* {@link io.vertx.circuitbreaker.OpenCircuitException} when the circuit breaker is opened
* {@link io.vertx.circuitbreaker.TimeoutException} when the operation timed out

== Pushing circuit breaker metrics to the Hystrix Dashboard

Netflix Hystrix comes with a dashboard to present the current state of the circuit breakers. The Vert.x circuit
breakers can publish their metrics in order to be consumed by this Hystrix Dashboard. The Hystrix dashboard requires
a SSE stream sending the metrics. This stream is provided by the
{@link io.vertx.circuitbreaker.HystrixMetricHandler} Vert.x Web Handler:


[source,$lang]
----
{@link examples.CircuitBreakerExamples#example7(io.vertx.core.Vertx)}
----
== Using Resilience4j

In the Hystrix Dashboard, configure the stream url like: `http://localhost:8080/metrics`. The dashboard now consumes
the metrics from the Vert.x circuit breakers.

IMPORTANT: The metrics are collected by the Vert.x Web handler using <<Event bus notification>>.
The feature must be enabled and, if you don't use the default notification address, you need to pass it when creating the metrics handler.

[language, java]
----
== Using Netflix Hystrix
link:https://resilience4j.readme.io/[Resilience4j] is a popular library that implements common fault tolerance strategies:

https://github.com/Netflix/Hystrix[Hystrix] provides an implementation of the circuit breaker pattern. You can use
Hystrix with Vert.x instead of this circuit breaker or in combination of. This section describes the tricks
to use Hystrix in a vert.x application.
* bulkhead (concurrency limiter)
* circuit breaker
* rate limiter
* retry
* time limiter (timeout)

First you would need to add the Hystrix dependency to your classpath or build descriptor. Refer to the Hystrix
page for details. Then, you need to isolate the "protected" call in a `Command`. Once you have your command, you
can execute it:
A link:https://how-to.vertx.io/resilience4j-howto/[how-to] has been published that demonstrates the usage of Resilience4j with Vert.x.
The link:https://github.com/vertx-howtos/resilience4j-howto[repository] of that how-to contains Vert.x adapters for all the fault tolerance strategies listed above.
These adapters glue together the Resilience4j API and Vert.x ``Future``s.

[source, $lang]
\----
{@link examples.hystrix.HystrixExamples#exampleHystrix1()}
\----
However, the command execution is blocking, so have to call the command execution either in an `executeBlocking`
block or in a worker verticle:
[source, $lang]
\----
{@link examples.hystrix.HystrixExamples#exampleHystrix2(io.vertx.core.Vertx)}
\----
If you use the async support of Hystrix, be careful that callbacks are not called in a vert.x thread and you have
to keep a reference on the context before the execution (with {@link io.vertx.core.Vertx#getOrCreateContext()},
and in the callback, switch back to the event loop using
{@link io.vertx.core.Vertx#runOnContext(io.vertx.core.Handler)}. Without this, you are loosing the Vert.x
concurrency model and have to manage the synchronization and ordering yourself:
[source, $lang]
\----
{@link examples.hystrix.HystrixExamples#exampleHystrix3(io.vertx.core.Vertx)}
\----
----
WARNING: Resilience4j 2.0 requires Java 17.
Loading

0 comments on commit 0ecd7e1

Please sign in to comment.