What is the Hystrix
Now that we’ve covered Feign and the Ribbon, today we’ll take a look at Hystrix, another class library developed by the Netflix team.
At an abstract level, Hystrix is a protector. It protects our application from the failure of a dependency.
Currently, officials have stopped iterating on Hystrix because they believe it is stable enough and have moved to more resilient protectors (rather than enabling protection based on pre-configuration), such as Resilience4J. Of course, stopping iteration is not to say that Hystrix has lost its value; many of its ideas are still worth learning from.
As before, the Hystrix studied in this article is native, rather than wrapped in layers of Spring.
What problem does Hystrix solve
A detailed answer to this question has been provided (see the official wiki linked at the end of this article). Here I combine some of my own understanding (the figure below is also borrowed from the official).
Our applications often need to invoke dependencies. When I’m talking about dependencies, I’m talking about remote services, so why not just say remote services? Because Hystrix applies to a wider range of scenarios, even ordinary methods called in an application can count as dependencies once we’re done with Hystrix.
When you call these dependencies, you may encounter exceptions: the call fails or the call times out.
Let’s start with call failures. When a dependency goes down, our application fails to call it. In this case, we consider fast failures to reduce the overhead of a large number of failed calls.
Call timeout again. Unlike the call failure, the dependency is still available, but it just takes more time to get what we want. When traffic is high, the thread pool is quickly exhausted. In large projects, the effects of a dependent timeout can be magnified and can even cause the entire system to collapse. So, call failure also needs to fail fast.
For the above exception, Hystrix can isolate the failed dependency in a timely manner, and subsequent calls will fail quickly until the dependency is restored.
How to implement
When the call fails or times out to a certain threshold, the Hystrix protector is triggered to start.
Before invoking the dependency, Hystrix will check whether the protector is enabled. If it is enabled, it will directly go to fall back. If it is not enabled, the call operation will be performed.
In addition, Hystrix periodically checks to see if the dependency has been restored. When the dependency is restored, the protector is turned off and the entire invocation link is restored.
Of course, the actual process is more complex, involving caches, thread pools, and so on. Officials have provided a picture and a more detailed description.
How to use
Here I use specific examples to illustrate the logic of each node, the project code see the link at the end of the article.
Packaging for the command
First, to use Hystrix, we need to wrap the invocation request for a dependency as a command, specifically by inheriting HystrixCommand or HystrixObservableCommand. After succession, we need to do three things:
- Specify commandKey and commandGroupKey in the construct. CommandGroupKey commands share a thread pool and commandKey commands share a protector and cache. For example, to obtain a user object from the UC service based on the user ID, you can use a commandGroupKey for all UC interfaces and a different commandKey for different interfaces.
- Override the run or construct method. In this method is the code that we call a dependency on. I could put in the code that calls the remote service, or I could just print out a sentence, so as I said earlier, the definition of a dependency could be broader than just a remote service.
- Override the getFallback method. This is what happens when you fail fast.
public class CommandGetUserByIdFromUserService extends HystrixCommand<DataResponse<User>> {
private final String userId;
public CommandGetUserByIdFromUserService(String userId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserService")) // The same command group shares a ThreadPool
.andCommandKey(HystrixCommandKey.Factory.asKey("UserService_GetUserById"))// The same command key shares a CircuitBreaker and requestCache
);
this.userId = userId;
}
Construct (HystrixObservableCommand)/construct(HystrixObservableCommand
@Override
protected DataResponse<User> run(a) {
return userService.getUserById(userId);
}
/** * This method is called in the following scenarios * 1. The final task execution throws an exception; * 2. Execution of the final task timed out; * 3. Request short circuit when the circuit breaker is opened; * 4. Connection pool, queue, or semaphore depletion */
@Override
protected DataResponse<User> getFallback(a) {
return DataResponse.buildFailure("fail or timeout"); }}Copy the code
Execute the command
Then, just by executing command, the diagram above “moves”. There are four ways to command. Calls to execute() or observe() are immediately executed, and calls to queue() or toObservable() are not immediately executed. Wait for future.get() or observable.subscribe() to be executed.
@Test
public void testExecuteWays(a) throws Exception {
DataResponse<User> response = new CommandGetUserByIdFromUserService("1").execute();/ / the execute () = queue (). The get () synchronization
LOG.info("command.execute():{}", response);
Future<DataResponse<User>> future = new CommandGetUserByIdFromUserService("1").queue();/ / the queue () = toObservable () toBlocking () toFuture synchronization ()
LOG.info("command.queue().get():{}", future.get());
Observable<DataResponse<User>> observable = new CommandGetUserByIdFromUserService("1").observe();/ / hot observables asynchronous
observable.subscribe(x -> LOG.info("command.observe():{}", x));
Observable<DataResponse<User>> observable2 = new CommandGetUserByIdFromUserService("1").toObservable();/ / cold observables asynchronous
observable2.subscribe(x -> LOG.info("command.toObservable():{}", x));
}
Copy the code
Whether to use caching
Then, after entering the logic of command, Hystrix determines whether to use caching.
Caching is disabled by default and can be turned on by overriding the command’s getCacheKey() (it is enabled whenever a non-empty return is returned).
@Override
protected String getCacheKey(a) {
return userId;
}
Copy the code
HystrixRequestContext = HystrixCollapser HystrixRequestContext = HystrixRequestContext = HystrixRequestContext = HystrixRequestContext Finally format call:
@Test
public void testCache(a) {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
CommandGetUserByIdFromUserService command1 = new CommandGetUserByIdFromUserService("1");
command1.execute();
// It was not in the cache the first time
assertFalse(command1.isResponseFromCache());
CommandGetUserByIdFromUserService command2 = new CommandGetUserByIdFromUserService("1");
command2.execute();
// The second call takes the result directly from the cache
assertTrue(command2.isResponseFromCache());
} finally {
context.shutdown();
}
// zzs001
}
Copy the code
Whether the protector is enabled
Hystrix then determines whether the protector is on.
Here I manually make fail or time out in the run method of command. Alternatively, we can adjust the threshold at which the protector is turned on via HystrixCommandProperties.
public CommandGetUserByIdFromUserService(String userId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserService")) // The same command group shares a ThreadPool
.andCommandKey(HystrixCommandKey.Factory.asKey("UserService_GetUserById"))// The same command key shares a CircuitBreaker and requestCache
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(10)
.withCircuitBreakerErrorThresholdPercentage(50)
.withMetricsHealthSnapshotIntervalInMilliseconds(1000)
.withExecutionTimeoutInMilliseconds(1000)));this.userId = userId;
}
@Override
protected DataResponse<User> run(a) {
LOG.info("Execute final task on thread: {}", Thread.currentThread());
// Manual manufacturing timeout
/*try { Thread.sleep(1200); } catch(InterruptedException e) { e.printStackTrace(); } * /
// Manually create an exception
throw new RuntimeException("");
//return UserService.instance().getUserById(userId);
}
Copy the code
At this time, when the call failure reaches a certain threshold, the protector will be triggered to start, and the subsequent requests will directly fall back.
@Test
public void testCircuitBreaker(a) {
CommandGetUserByIdFromUserService command;
int count = 1;
do {
command = new CommandGetUserByIdFromUserService("1");
command.execute();
count++;
} while(! command.isCircuitBreakerOpen()); LOG.info("Circuit breaker open after {} calls", count);
// If you call it at this time, it will fall back directly
command = new CommandGetUserByIdFromUserService("1");
command.execute();
assertTrue(command.isCircuitBreakerOpen());
}
Copy the code
Whether the connection pool, queue, or semaphore is exhausted
Even if the protector is off, we can’t call the dependency immediately and need to check whether the connection pool or semaphore is exhausted first (you can configure using thread pool or semaphore through HystrixCommandProperties).
Because the default thread pool is larger, so, here I pass HystrixThreadPoolProperties down the thread pool.
public CommandGetUserByIdFromUserService(String userId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserService")) // The same command group shares a ThreadPool
.andCommandKey(HystrixCommandKey.Factory.asKey("UserService_GetUserById"))// The same command key shares a CircuitBreaker and requestCache
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(2)
.withMaxQueueSize(5)
.withQueueSizeRejectionThreshold(5)));this.userId = userId;
}
Copy the code
At this point, when the thread pool is exhausted, subsequent requests will fall back and the protector will not be turned on.
@Test
public void testThreadPoolFull(a) throws InterruptedException {
int maxRequest = 100;
int i = 0;
do {
CommandGetUserByIdFromUserService command = new CommandGetUserByIdFromUserService("1");
command.toObservable().subscribe(v -> LOG.info("non-blocking command.toObservable():{}", v));
LOG.info("Is the thread pool, queue, or semaphore exhausted: {}", command.isResponseRejected());
} while(i++ < maxRequest - 1);
// If you call it at this time, it will fall back directly
CommandGetUserByIdFromUserService command = new CommandGetUserByIdFromUserService("1");
command.execute();
// The thread pool, queue or semaphore is exhausted
assertTrue(command.isResponseRejected());
assertFalse(command.isCircuitBreakerOpen());
Thread.sleep(10000);
// zzs001
}
Copy the code
conclusion
So that’s Hystrix. Read the official Wiki, combined with the examples above, and you can get a better understanding of Hystrix.
Finally, thank you for reading, welcome private communication.
The resources
Home · Netflix/Hystrix Wiki · GitHub
Related source code please move: github.com/ZhangZiShen…
This article original articles, reproduced please attach the original source link: www.cnblogs.com/ZhangZiShen…