Skip to content

Commit

Permalink
Issue ReactiveX#89: Ratelimiter SprigBoot integration
Browse files Browse the repository at this point in the history
* Issue ReactiveX#89: initial support of all registries and auto configuration

* Issue ReactiveX#89: rate limiter health check and registry endpoint

* Issue ReactiveX#89: events end points + tests + documentation + other improvements

* Issue ReactiveX#89: after merge fixes

* Issue ReactiveX#89: codacy issues fixes

* Issue ReactiveX#89: additional codacy issues fixes

* Issue ReactiveX#89: test fix

* Issue ReactiveX#89: travis.yml debug off
  • Loading branch information
storozhukBM authored and RobWin committed May 6, 2017
1 parent a8407ac commit 4472d49
Show file tree
Hide file tree
Showing 26 changed files with 1,164 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ public int getLimitForPeriod() {
return limitForPeriod;
}

@Override public String toString() {
return "RateLimiterConfig{" +
"timeoutDuration=" + timeoutDuration +
", limitRefreshPeriod=" + limitRefreshPeriod +
", limitForPeriod=" + limitForPeriod +
'}';
}

public static class Builder {

private RateLimiterConfig config = new RateLimiterConfig();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,13 @@ public Flowable<RateLimiterEvent> getEventStream() {
return eventPublisher;
}

@Override public String toString() {
return "AtomicRateLimiter{" +
"name='" + name + '\'' +
", rateLimiterConfig=" + rateLimiterConfig +
'}';
}

/**
* Get the enhanced Metrics with some implementation specific details.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ public RateLimiterConfig getRateLimiterConfig() {
return this.rateLimiterConfig;
}

@Override public String toString() {
return "SemaphoreBasedRateLimiter{" +
"name='" + name + '\'' +
", rateLimiterConfig=" + rateLimiterConfig +
'}';
}

/**
* {@inheritDoc}
*/
Expand Down
97 changes: 93 additions & 4 deletions resilience4j-spring-boot/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ dependencies {

== Spring AOP

The demo shows how to use the `CircuitBreaker` annotation to make your Spring Boot application more fault tolerant. You can either annotate a class in order to protect all public methods or just some specific methods.
The demo shows how to use the `CircuitBreaker` and `RateLimiter` annotations
to make your Spring Boot application more fault tolerant.
You can either annotate a class in order to protect all public methods or just some specific methods.
For example:

[source,java]
----
@CircuitBreaker(backend = "backendA")
@RateLimiter("backendA")
@Component(value = "backendAConnector")
public class BackendAConnector implements Connector {
...
Expand Down Expand Up @@ -68,7 +71,13 @@ public Observable<String> methodWhichReturnsAStream() {

== Monitoring

Spring Boot Actuator health information can be used to check the status of your running application. It is often used by monitoring software to alert someone if a production system has serious issues. This demo publishes the status and metrics of all CircuitBreakers via a custom `CircuitBreakerHealthIndicator`. A closed CircuitBreaker state is mapped to UP, an open state to DOWN and a half-open state to UNKNOWN.
Spring Boot Actuator health information can be used to check the status of your running application.
It is often used by monitoring software to alert someone if a production system has serious issues.

=== CircuitBreaker
This demo publishes the status and metrics of all CircuitBreakers via a custom `CircuitBreakerHealthIndicator`.
A closed CircuitBreaker state is mapped to UP, an open state to DOWN and a half-open state to UNKNOWN.

For example:

[source,json]
Expand Down Expand Up @@ -142,8 +151,28 @@ resilience4j_circuitbreaker_states{name="backendA",state="open",} 0.0
resilience4j_circuitbreaker_states{name="backendA",state="half_open",} 0.0
----

=== RateLimiter
This demo publishes the status and metrics of all RateLimiter via a custom `RateLimiterHealthIndicator`.
RateLimiterHealthIndicator changes its state DOWN only if there is some permission waiting threads
and they won't be able to unblock until timeout.

For example:

[source,json]
----
{
"status": "UP",
"backendA": {
"status": "UP",
"availablePermissions": 10,
"numberOfWaitingThreads": 0
}
}
----

== Configuration

=== CircuitBreaker
You can configure your CircuitBreakers in Spring Boot's `application.yml` config file.
For example

Expand All @@ -164,7 +193,28 @@ resilience4j.circuitbreaker:
eventConsumerBufferSize: 10
----

== CircuitBreaker Event Monitoring
=== RateLimiter
You can configure your CircuitBreakers in Spring Boot's `application.yml` config file.
For example

----
resilience4j.ratelimiter:
limiters:
backendA:
limitForPeriod: 10
limitRefreshPeriodInMillis: 1000
timeoutInMillis: 0
subscribeForEvents: true
registerHealthIndicator: true
backendB:
limitForPeriod: 6
limitRefreshPeriodInMillis: 500
timeoutInMillis: 3000
----

== Event Monitoring

=== CircuitBreaker

The emitted CircuitBreaker events are stored in a separate circular event consumer buffers. The size of a event consumer buffer can be configured per CircuitBreaker in the application.yml file (eventConsumerBufferSize).
The demo adds a custom Spring Boot Actuator endpoint which can be used to monitor the emitted events of your CircuitBreakers.
Expand Down Expand Up @@ -284,9 +334,48 @@ For example `/management/circuitbreaker/events/backendA/ERROR`:
}
----

=== RateLimiter
WARNING: Unlike the CircuitBreaker events, RateLimiter events require explicit subscription.
Use property resilience4j.ratelimiter.limiters.{yourBackendName}.registerHealthIndicator=true

There are literally the same endpoints implemented for RateLimiter,
so for detailed documentation please refer to previous section:

List of available endpoints:

* `/ratelimiter/events`
* `/ratelimiter/stream/events`
* `/ratelimiter/events/{rateLimiterName}`
* `/ratelimiter/stream/events/{rateLimiterName}`
* `/ratelimiter/events/{rateLimiterName}/{eventType}`
* `/ratelimiter/stream/events/{rateLimiterName}/{eventType}`

Example of response:
----
{
"eventsList": [
{
"rateLimiterName": "backendA",
"rateLimiterEventType": "SUCCESSFUL_ACQUIRE",
"rateLimiterCreationTime": "2017-05-05T21:29:40.463+03:00[Europe/Uzhgorod]"
},
{
"rateLimiterName": "backendA",
"rateLimiterEventType": "SUCCESSFUL_ACQUIRE",
"rateLimiterCreationTime": "2017-05-05T21:29:40.469+03:00[Europe/Uzhgorod]"
},
{
"rateLimiterName": "backendA",
"rateLimiterEventType": "FAILED_ACQUIRE",
"rateLimiterCreationTime": "2017-05-05T21:29:41.268+03:00[Europe/Uzhgorod]"
}
]
}
----

== License

Copyright 2017 Robert Winkler
Copyright 2017 Robert Winkler and Bohdan Storozhuk

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Expand Down
1 change: 1 addition & 0 deletions resilience4j-spring-boot/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dependencies {
compile ( libraries.spring_boot_actuator )
compile ( libraries.spring_boot_web )
compile project(':resilience4j-circuitbreaker')
compile project(':resilience4j-ratelimiter')
compile project(':resilience4j-consumer')
compileOnly project(':resilience4j-prometheus')
compileOnly project(':resilience4j-metrics')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
@Aspect
public class CircuitBreakerAspect {

private static Logger logger = LoggerFactory.getLogger(CircuitBreakerAspect.class);
private static final Logger logger = LoggerFactory.getLogger(CircuitBreakerAspect.class);

private final CircuitBreakerProperties circuitBreakerProperties;
private final CircuitBreakerRegistry circuitBreakerRegistry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ public CircuitBreakerEndpoint circuitBreakerEndpoint(CircuitBreakerRegistry circ
}

@Bean
public CircuitBreakerEventsEndpoint circuitBreakerEventsEndpoint(CircuitBreakerEndpoint circuitBreakerEndpoint,
EventConsumerRegistry<CircuitBreakerEvent> eventConsumerRegistry,
public CircuitBreakerEventsEndpoint circuitBreakerEventsEndpoint(EventConsumerRegistry<CircuitBreakerEvent> eventConsumerRegistry,
CircuitBreakerRegistry circuitBreakerRegistry) {
return new CircuitBreakerEventsEndpoint(circuitBreakerEndpoint, eventConsumerRegistry, circuitBreakerRegistry);
return new CircuitBreakerEventsEndpoint(eventConsumerRegistry, circuitBreakerRegistry);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@
package io.github.resilience4j.circuitbreaker.monitoring.endpoint;


import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Comparator;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent;
Expand All @@ -34,18 +32,19 @@
import io.reactivex.Flowable;
import javaslang.collection.Seq;

import java.util.Comparator;


@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public class CircuitBreakerEventsEndpoint extends EndpointMvcAdapter {
@Controller
@RequestMapping(value = "circuitbreaker/", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public class CircuitBreakerEventsEndpoint {

private static final String MEDIA_TYPE_TEXT_EVENT_STREAM = "text/event-stream";
private final EventConsumerRegistry<CircuitBreakerEvent> eventConsumerRegistry;
private final CircuitBreakerRegistry circuitBreakerRegistry;

public CircuitBreakerEventsEndpoint(CircuitBreakerEndpoint circuitBreakerEndpoint,
EventConsumerRegistry<CircuitBreakerEvent> eventConsumerRegistry,
public CircuitBreakerEventsEndpoint(EventConsumerRegistry<CircuitBreakerEvent> eventConsumerRegistry,
CircuitBreakerRegistry circuitBreakerRegistry) {
super(circuitBreakerEndpoint);
this.eventConsumerRegistry = eventConsumerRegistry;
this.circuitBreakerRegistry = circuitBreakerRegistry;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2017 Bohdan Storozhuk
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.resilience4j.ratelimiter.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation can be applied to a class or a specific method.
* Applying it on a class is equivalent to applying it on all its public methods.
* The annotation enables throttling for all methods where it is applied.
* Throttling monitoring is performed via a rate limiter.
* See {@link io.github.resilience4j.ratelimiter.RateLimiter} for details.
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface RateLimiter {
/**
* Name of the rate limiter
* @return
*/
String name();
}
Loading

0 comments on commit 4472d49

Please sign in to comment.