Shaking hands and pushing back

Another approach of communicating in a resilient way is handshaking and backpressure. The idea is that the communication partner being under load notifies the other side, which then backs off and eases the load. Handshaking here means that the calling side has a way of asking the service whether it can handle more requests. Backpressure reduces the load on a system by notifying clients when the limit is reached or pushing back requests.

The two approaches combined form a resilient and effective form of communication.

Information about the current load state of the application can be provided in HTTP resources or via header fields. The clients then take this information into account.

A more direct way is to simply reject a client request when the server's resources are fully utilized. Developers are advised to pay attention to the behavior of pooling such as in executor services, and how they handle situations with full queues. Exceptionally, it's advisable to abort the client request to not unnecessarily run into timeouts.

The following example shows a scenario using the Porcupine library. A business functionality is executed using a dedicated executor service, which will be configured to abort rejected executions. The clients will immediately receive a 503 Service Unavailable response, indicating that currently the service is not able to serve requests.

The JAX-RS resource is similar to the previous example. The custom-name executor is configured to abort rejected executions via a specialized configurator. The ExecutorConfigurator is part of the Porcupine library. The following shows the custom configuration:

import com.airhacks.porcupine.configuration.control.ExecutorConfigurator;
import com.airhacks.porcupine.execution.control.ExecutorConfiguration;

@Specializes public class CustomExecutorConfigurator extends ExecutorConfigurator { @Override public ExecutorConfiguration defaultConfigurator() { return super.defaultConfigurator(); } @Override public ExecutorConfiguration forPipeline(String name) { if ("custom-name".equals(name)) { return new ExecutorConfiguration.Builder(). abortPolicy(). build(); } return super.forPipeline(name); } }

Executions that are rejected due to full queues will then result in a RejectedExecutionException. This exception is mapped via JAX-RS functionality:

import java.util.concurrent.RejectedExecutionException;

@Provider
public class RejectedExecutionHandler
implements ExceptionMapper<RejectedExecutionException> { @Override public Response toResponse(RejectedExecutionException exception) { return Response.status(Response.Status.SERVICE_UNAVAILABLE) .build(); } }

Client requests that would exceed the server limits immediately result in an error response. The client invocation can take this into account and act appropriately. For example, a circuit breaker pattern-like functionality can prevent the client from immediate subsequent invocations.

Backpressure is helpful when crafting scenarios with multiple services that need to meet service level agreements (SLA). Chapter 9, Monitoring, Performance, and Logging will cover this topic.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.163.238