Lab 2: Resilience - Retry, Circuit Breaking and Rate Limiting
Last updated
Last updated
In the second lab we will add to the gateway.
Info: See , and for all details on how to configure the corresponding resilience patterns.
Productive software has to deliver quality attributes, i.e. it has to be functional correct, reliable, performant, secure, and available (see for more details on software quality).
When it comes to resilience in software design, the main goal is build robust components that can tolerate faults within their scope, but also failures of other components they depend on. In this lab we take a look at some resilience design patterns:
Retry
Circuit Breaker with fallback strategy
Rate Limiting
In lab 2 you will learn how to:
Add the Retry pattern to a request, that is calling a special endpoint in the customer service which is implemented to fail randomly
In the lab 2 folder you find 2 applications:
initial: This is the gateway application we will use as starting point for this lab
solution: This is the completed reference solution of the gateway application for this lab including all configured resilience patterns
Now, let's start with this lab.
Please navigate your Java IDE to the lab2/initial/api-gateway project and explore this project a bit. Then start the application by running the class com.example.apigateway.ApiGatewayApplication
inside your IDE
or by issuing a mvnw[.sh|.cmd] spring-boot:run
command.
For this lab we will also need the two provided sample backend services that you can find in the microservices root folder:
product-service: Provides a REST API for products
customer-service: Provides a REST API for customers
To test if the backend microservice applications work as expected, please run the corresponding spring boot starter classes and check if you can access the following REST API endpoints via the browser or the provided postman collection in /setup/postman:
You may also use a command-line client as well. Here are example requests using httpie and curl.
Httpie:
Curl:
Whenever we assume that an unexpected response (i.e. for a non-reliable service) can be fixed by sending the request again, using the retry pattern can help. It is a very simple pattern where failed requests are retried a configurable number of times in case of a failure before the operation is marked as a failure.
200 (OK)
400 (Bad Request)
408 (Request Timeout)
500 (Internal Server Error)
503 (Service Unavailable)
Next, we will add a new route including the Retry filter.
Please open the file src/main/resources/application.yml
in the /lab2/initial/api-gateway project and add the following entries at the end of the routes
path:
application.yml:
The retry filter is configured as follows:
retries: The number of retries that should be attempted. In our scenario the request is tried to execute 5 times.
statuses: The HTTP status codes that should be retried, in our sample only 408 (REQUEST_TIMEOUT)
and 503 SERVICE_UNAVAILABLE)
is retried
methods: The HTTP methods that should be retried, we only want GET
requests to be retried.
backoff: The configured exponential backoff for the retries. Retries are performed after a backoff interval of firstBackoff * (factor ^ n)
, where n is the iteration. If maxBackoff is configured, the maximum backoff applied is limited to maxBackoff. If basedOnPreviousValue is true
, the backoff is calculated by using prevBackoff * factor
.
In the logs of the api-gateway you will notice that it might have some entries for retrying to call the customer service because of error results. But the service call should be successful in the end.
We will now configure a circuit breaker for the routing to the customer service, including both API versions:
pom.xml:
After ensuring all prerequisites are now met we can configure the circuit breaker. Open the file src/main/resources/application.yml
in the /lab2/initial/api-gateway project and add the following entries in the filters list of the route entry with the customers id:
application.yml:
Now a circuit breaker is configured as filter for these routes:
Create the new class FallbackApi
in the package com.example.apigateway.resilience
with the following implementation:
FallbackApi.java:
Her we have implemented a very simple fallback just informing consumers of the application of a temporary service outage. Other possible solutions would be to cache a previous result.
Now (re-)start the api-gateway application and make sure you also have started the customer-service microservice located in /microservices/customer-service. Next try to call the route at http://localhost:9090/api/v1/customers or http://localhost:9090/api/v2/customers using either the web browser or the provided postman collection (corresponding requests in folder routing). This should still work fine as before with returning the list of customers.
Now please stop the customer service and try again to call the same requests above. This time the circuit breaker kicks in and the fallback API gets called. So you should see the message of the implemented fallback API endpoint above instead. After restarting the customer service again the call should work fine again, and you should see the customer list.
Next up: Rate limiting.
Mitigate web attacks
Brute forcing
DoS and DDoS attacks
Limit the number of requests according to a pricing model
If a request is blocked by the limiter then the response HTTP 429 - Too Many Requests
is sent instead of a HTTP 200 - OK
status.
First, we have to add another dependency to the maven pom.xml
file:
pom.xml:
Now open a terminal window and navigate to the lab2/initial folder and issue this command:
If this does not work as expected, please re-check that there is the docker-compose.yml file in the current directory.
The final step is the configuration of the rate limiter.
So let's do this (again in the application.yml
file):
application.yml:
redis-rate-limiter.replenishRate: Defines how many requests per second to allow (without any dropped requests). This is the rate at which the token bucket is filled.
redis-rate-limiter.burstCapacity: The maximum number of requests a user is allowed in a single second (without any dropped requests). This is the number of tokens the token bucket can hold. Setting this value to zero blocks all requests.
redis-rate-limiter.requestedTokens: Defines how many tokens a request costs. This is the number of tokens taken from the bucket for each request and defaults to 1.
rate-limiter: You can also define your own (optional) implementation of the rate limiter as spring bean.
key-resolver: An (optional) key resolver defines the key for limiting requests.
As we don't have a Principal (authenticated user) yet (see next lab) it is not possible to use the default PrincipalNameKeyResolver
implementation of spring cloud gateway.
Instead, we provide our own simple implementation, limiting the requests based on the request origin address. Our custom resolver is set as bean reference in the configuration above.
Here you find the implementation. Please create a new class ProductKeyResolver
in the package com.example.apigateway.resilience
.
ProductKeyResolver.java:
To test the rate limiter you need a mechanism to create multiple requests in short time. You won't reach the maximum number of possible requests by manually executing requests in the browser or from postman. You may use one of these client tools:
Rate Limiter Client: This workshop also provides a simple client to issue multiple requests to the product service. You find the project for the Rate Limiter Client in the directory /rate-limiter-client.
With Apache Bench you can try to perform this command:
This executes 2 concurrent requests at a time (-c 2), uses the GET HTTP method (-m GET), issues 10 requests (-n 10) and sets a verbose level of 3
to receive HTTP status results.
If you cannot install the Apache Bench tool then you may use the rate-limiter-client as an alternative.
Navigate to the directory /rate-limiter-client and start the class com.example.client.RateLimiterClientApplication
.
This provides one simple API endpoint:
The API requires the following request parameters:
requests: The number of requests to be triggered
delay: The delay to wait between requests (in milliseconds), when 0
is given then no delay is configured
You may configure the base URI and a JWT bearer token in the class WebClientConfiguration
:
Independent of the client you are using, if you increase the number of requests, then at some point you will recognize that you get the HTTP status of 429(Too Many Requests)
instead of the 200(OK)
HTTP status.
Important Note: If you could not finish this lab, then just use the project lab3/initial/api-gateway as new starting point.
Configure a for calling the customer-service including a Fallback when the call is failing
Configure a to restrict the amount of requests that can be executed in a certain amount of time.
If you have not yet seen the sample application architecture we will be building then please have a look into the .
In this step we will use the retry feature with a special API endpoint at of the customer service that is randomly returning one of the following http status values:
This defines a route from to .
Please see the for full parameters description.
Now (re-)start the api-gateway application and make sure you also have started the customer-service microservice located in /microservices/customer-service. Next, try to call the new route at using either the web browser or the provided postman collection (corresponding request in folder Resilience)
A circuit breaker protects your services from being spammed while already being partly unavailable due to high load. It also protects a service just coming back online from breaking down again when it is flooded with lots of pending requests. The circuit breaker pattern was described by . It can be implemented as a stateful software component that switches between three states: closed (requests are performed without any restriction), open (requests are rejected without being submitted to the remote resource), and half-open (one probe request is allowed to decide whether to close the circuit again). Circuit breakers may also be combined with retries, timeouts and fallbacks.
The circuit breaker in the spring cloud gateway is implemented using the .
So we have to add the corresponding dependencies to the maven pom.xml
file:
We also have added a dependency that introduces a corresponding metric for the Resilience4J circuit breaker. With this all circuit breaker events can be monitored at .
From to
From to
Before we can try the circuit breaker we still have to implement the endpoint for the configured circuit breaker fallback in the api gateway at .
Finally, you may check all circuit breaker events at .
In this last step we will introduce a for the product service call. A rate limiter can support in achieving the following features:
The standard rate limiter implementation of the spring cloud gateway is the rate limiter. That is why a database is required.
Second, we have to start a local database. For this you need to have installed and .
The rate limiting algorithm used is the . The rate limiter defines the following properties:
See for more details.
Now (re-)start the api-gateway application and make sure you still have started the product-service microservice located in /microservices/product-service. Next try to call the new route at using either the web browser or the provided postman collection (corresponding request in folder Routing).
Apache Bench: On Linux and macOS operating systems you may use . This is a tool from the Apache organization for benchmarking a Hypertext Transfer Protocol (HTTP) web server. With this tool, you can quickly know how many requests per second your web server is capable of serving.
You could also use for this. If you already have installed python3 then you just have to perform a pip install locust
to install locust.
Then use the provided file locustfile.py
and run locust -f locustfile.py
In the specify the target server http://localhost:9090 of the gateway.
This ends lab 2. In the next you will learn how to configure authentication using bearer tokens.
To continue please head over to .