When bad things happen: client resiliency patterns with Spring
149Thread context and Hystrix
Note a couple of things in the class implementation in listing 5.10. First, because Spring Cloud already defines a HystrixConcurrencyStrategy, every method that could be overridden needs to check whether an existing concurrency strategy is pres-ent and then either call the existing concurrency strategy’s method or the base Hys-trix concurrency strategy method. You have to do this as a convention to ensure that you properly invoke the already-existing Spring Cloud’s HystrixConcurrency-Strategy that deals with security. Otherwise, you can have nasty behavior when try-ing to use Sprtry-ing security context in your Hystrix protected code.
The second thing to note is the wrapCallable() method in listing 5.11. In this method, you pass in Callable implementation, DelegatingUserContext-Callable, that will be used to set the UserContext from the parent thread execut-ing the user’s REST service call to the Hystrix command thread protecting the method that’s doing the work within.
DEFINEA JAVA CALLABLECLASSTOINJECTTHE USERCONTEXTINTOTHE HYSTRIXCOMMAND
The next step in propagating the thread context of the parent thread to your Hystrix command is to implement the Callable class that will do the propagation. For this example, this call is in the hystrix package and is called DelegatingUser-ContextCallable.java. The following listing shows the code from this class.
package com.thoughtmechanix.licenses.hystrix;
//import remove concisesness
public final class DelegatingUserContextCallable<V>
implements Callable<V> {
private final Callable<V> delegate;
private UserContext originalUserContext;
public DelegatingUserContextCallable(
Callable<V> delegate, UserContextHolder.setContext( originalUserContext );
try {
public static <V> Callable<V> create(Callable<V> delegate, UserContext userContext) {
return new DelegatingUserContextCallable<V>(delegate, userContext);
} }
Listing 5.11 Propagating the UserContext with DelegatingUserContextCallable.java
Custom Callable class will be passed the original Callable class that will invoke your Hystrix protected code and
The UserContext is set. The ThreadLocal variable that stores the UserContext is associated with the thread running the Hystrix protected method.
Once the UserContext is set invoke the call() method on the Hystrix protected method; for instance, your
LicenseServer.getLicenseByOrg() method.
When a call is made to a Hystrix protected method, Hystrix and Spring Cloud will instantiate an instance of the DelegatingUserContextCallable class, passing in the Callable class that would normally be invoked by a thread managed by a Hystrix command pool. In the previous listing, this Callable class is stored in a Java property called delegate. Conceptually, you can think of the delegate property as being the handle to the method protected by a @HystrixCommand annotation.
In addition to the delegated Callable class, Spring Cloud is also passing along the UserContext object off the parent thread that initiated the call. With these two values set at the time the DelegatingUserContextCallable instance is created, the real action will occur in the call() method of your class.
The first thing to do in the call() method is set the UserContext via the User-ContextHolder.setContext() method. Remember, the setContext() method stores a UserContext object in a ThreadLocal variable specific to the thread being run. Once the UserContext is set, you then invoke the call() method of the dele-gated Callable class. This call to delegate.call() invokes the method protected by the @HystrixCommand annotation.
CONFIGURE SPRING CLOUDTOUSEYOURCUSTOM HYSTRIXCONCURRENCYSTRATEGY
Now that you have your HystrixConcurrencyStrategy via the ThreadLocal-AwareStrategy class and your Callable class defined via the DelegatingUser-ContextCallable class, you need to hook them in Spring Cloud and Hystrix. To do this, you’re going to define a new configuration class. This configuration, called ThreadLocalConfiguration, is shown in the following listing.
package com.thoughtmechanix.licenses.hystrix;
//Imports removed for conciseness
@Configuration
public class ThreadLocalConfiguration { @Autowired(required = false)
private HystrixConcurrencyStrategy existingConcurrencyStrategy;
@PostConstruct public void init() {
// Keeps references of existing Hystrix plugins.
HystrixEventNotifier eventNotifier = ➥ HystrixPlugins
.getInstance() .getEventNotifier();
HystrixMetricsPublisher metricsPublisher = ➥ HystrixPlugins
.getInstance()
.getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy = ➥ HystrixPlugins
.getInstance()
.getPropertiesStrategy();
Listing 5.12 Hooking custom HystrixConcurrencyStrategy class into Spring Cloud
When the configuration object is constructed it will autowire in the existing HystrixConcurrencyStrategy.
Because you’re registering a new concurrency strategy, you’re going to grab all the other Hystrix components and then reset the Hystrix plugin.
151 Summary
HystrixCommandExecutionHook commandExecutionHook = ➥ HystrixPlugins
.getInstance()
.getCommandExecutionHook();
HystrixPlugins.reset();
HystrixPlugins.getInstance() .registerConcurrencyStrategy(
new ThreadLocalAwareStrategy(existingConcurrencyStrategy));
HystrixPlugins.getInstance()
.registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance()
.registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance()
.registerPropertiesStrategy(propertiesStrategy);
HystrixPlugins.getInstance()
.registerCommandExecutionHook(commandExecutionHook);
} }
This Spring configuration class basically rebuilds the Hystrix plugin that manages all the different components running within your service. In the init() method, you’re grabbing references to all the Hystrix components used by the plugin. You then regis-ter your custom HystrixConcurrencyStrategy (ThreadLocalAwareStrategy).
HystrixPlugins.getInstance().registerConcurrencyStrategy(
new ThreadLocalAwareStrategy(existingConcurrencyStrategy));
Remember, Hystrix allows only one HystrixConcurrencyStrategy. Spring will attempt to autowire in any existing HystrixConcurrencyStrategy (if it exists).
Finally, when you’re all done, you re-register the original Hystrix components that you grabbed at the beginning of the init() method back with the Hystrix plugin.
With these pieces in place, you can now rebuild and restart your licensing service and call it via the GET (http://localhost:8080/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/)shown earlier in figure 5.10. Now, when this call is com-pleted, you should see the following output in your console window:
UserContext Correlation id: TEST-CORRELATION-ID
LicenseServiceController Correlation id: TEST-CORRELATION-ID LicenseService.getLicenseByOrg Correlation: TEST-CORRELATION-ID
It’s a lot of work to produce one little result, but it’s unfortunately necessary when you use Hystrix with THREAD-level isolation.
5.10 Summary
When designing highly distributed applications such as a microservice-based application, client resiliency must be taken into account.
Outright failures of a service (for example, the server crashes) are easy to detect and deal with.
You now register your HystrixConcurrencyStrategy (ThreadLocalAwareStrategy) with the Hystrix plugin.
Then reregister all the Hystrix components used by the Hystrix plugin
A single poorly performing service can trigger a cascading effect of resource exhaustion as threads in the calling client are blocked waiting for a service to complete.
Three core client resiliency patterns are the circuit-breaker pattern, the fallback pattern, and the bulkhead pattern.
The circuit breaker pattern seeks to kill slow-running and degraded system calls so that the calls fail fast and prevent resource exhaustion.
The fallback pattern allows you as the developer to define alternative code paths in the event that a remote service call fails or the circuit breaker for the call fails.
The bulk head pattern segregates remote resource calls away from each other, isolating calls to a remote service into their own thread pool. If one set of ser-vice calls is failing, its failures shouldn’t be allowed to eat up all the resources in the application container.
Spring Cloud and the Netflix Hystrix libraries provide implementations for the circuit breaker, fallback, and bulkhead patterns.
The Hystrix libraries are highly configurable and can be set at global, class, and thread pool levels.
Hystrix supports two isolation models: THREAD and SEMAPHORE.
Hystrix’s default isolation model, THREAD, completely isolates a Hystrix pro-tected call, but doesn’t propagate the parent thread’s context to the Hystrix managed thread.
Hystrix’s other isolation model, SEMAPHORE, doesn’t use a separate thread to make a Hystrix call. While this is more efficient, it also exposes the service to unpredictable behavior if Hystrix interrupts the call.
Hystrix does allow you to inject the parent thread context into a Hystrix managed Thread through a custom HystrixConcurrencyStrategy implementation.
153