“This is the 16th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

1, the introduction of

In microservices, there are often two uncertainties in the invocation between services:

  1. Network latency
  2. Service exceptions

Latency is a very important performance indicator in microservices. With the increase of services, the call chain becomes more and more complex. At this time, low latency is often the primary goal of microservice system architecture. High network latency may drag down the entire microservice, which should not happen. In addition, an unknown or uncaught exception may occur within the service, and if the exception is not handled correctly, it will be thrown up the call chain, which is also fatal to the upload call chain, because the upper caller does not know how to handle the unknown exception.

For service exceptions, we should architecture the system to meet the Vegas Rule: what happens in the microservice stays in the microservice. Generally speaking, exceptions occurred in microservices should be handled by themselves, and no information should be returned to other microservices other than non-agreed interaction messages.

For network latency, this is inevitable. CAP theory also talks about the inevitability of network partitioning in distributed architecture, which may occur. Therefore, we can only set timeout and duplicate processing after timeout where network latency is likely to occur.

Hystrix solves both of these problems. (Note that it does not prevent errors from occurring or network latencies from occurring, but provides back-up behavior and self-tuning capabilities for gracefully handling errors and network latencies.) Hystrix works by simply setting a failure threshold for a protected method, within which a method fails (exception/delay) and returns a pre-prepared data message by calling a pre-prepared backup method (still essentially implemented by slice). Hystrix is available in three states: closed, open, and half-open.

  1. Hystrix is closed by default
  2. When the failure threshold is exceeded, the circuit breaker is turned on and Hystrix enters the open state. In this case, all requests directly request the circuit breaker and no normal service is requested
  1. Ajar (half open), Hystrix into the open, after more than circuitBreaker, sleepWindowInMilliseconds time cycle, Hystrix enter a state of half open, try to call the normal service right now, The failed state is reset if the service invocation fails

\

2, the body

2.1 Hystrix Application Scenarios

Hystrix is mostly used in scenarios with network latency, so it is also used in scenarios where network latency is common, such as:

  1. Remote service invocation, REST request
  2. Database access
  1. Complex and time-consuming computing scenarios

2.2 Hystrix Processing Exception

Hystrix is used in microservices, so before using Hystrix, you need to prepare a simple microservice environment, specify the Spring Cloud version and Spring Boot version, and introduce Web dependencies to simulate inter-microservice calls.

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.4.RELEASE</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Hoxton.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
Copy the code

Hystrix relies on imports

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  </dependency>
</dependencies>
Copy the code

Configure the service startup port

server:
  port: 18888
Copy the code

The startup class adds the @enablehystrix annotation

@SpringBootApplication @EnableHystrix public class ServiceApplication { public static void main(String[] args) { SpringApplication.run(ServiceApplication.class, args); }}Copy the code

Method one:

Write Hystrix protected methods using the @hystrixCommand annotation for Hystrix protected methods and specify fallbackMethod as fallback, which is a preset method. This method returns the same value as the protected method and is used for backup when the service circuit breaker is turned on. In the demo method, I throw a RuntimeException directly to simulate a service call failure.

@RestController @RequestMapping("/fallback") public class FallbackMethodController { @GetMapping @hystrixCommand (fallbackMethod = "fallback") public ResponseEntity<String> demo() {// Throw new RuntimeException("Error."); } private ResponseEntity<String> fallback() { return new ResponseEntity<>("Hello World!" , HttpStatus.OK); }}Copy the code

Make a request to the REST interface and get Hello World! No matter how many times you request it. The return value.

Method 2:

In addition to specifying backup methods directly in a method, an alternative is to define default backup methods directly on the Controller class, so that the entire Controller needs protected methods without specifying backup methods explicitly for each one. (Difference: @hystrixCommand no longer needs to specify fallbackMethod)

@restController@requestMapping ("/defaultFallback") @defaultProperties (defaultFallback = "defaultFallback") public class DefaultFallbackMethodController { @GetMapping @HystrixCommand public ResponseEntity<String> demo() { throw new RuntimeException("Error."); } public ResponseEntity<String> defaultFallback() { return new ResponseEntity<>("Hello World.", HttpStatus.OK); }}Copy the code

2.3 Hystrix Processing Times out

In addition to gracefully handling unknown exceptions, Hystrix also has the ability to defer method execution. The @hystrixCommand annotation sets a timeout of one second by default, and if the method does not return within one second, the preset backup method will be executed. The one-second timeout may not be sufficient for all business scenarios, or some methods simply do not set the timeout. Hystrix provides configuration items for these requirements.

The commandProperties property is provided in the @HystrixCommand annotation, which is an array of HystrixProperty, so @hystrixProperty can define more than one; Name specifies the item to be configured, and value specifies the value of the configuration item.

@RestController @RequestMapping("/timeout") public class TimeoutController { @GetMapping @HystrixCommand( fallbackMethod  = "fallback", commandProperties = { @HystrixProperty( name = "execution.isolation.thread.timeoutInMilliseconds", Value = "2000")}) public ResponseEntity<String> demo() {try {timeunit.seconds.sleep (3); } catch (InterruptedException e) { e.printStackTrace(); } return new ResponseEntity<>("Hello!" , HttpStatus.OK); } private ResponseEntity<String> fallback() { return new ResponseEntity<>("Timeout!" , HttpStatus.OK); }}Copy the code

The interface does not return Hello! Instead, the return value of the backup method Timeout! Timeunit.seconds.sleep (3) sleeps for 3 SECONDS, causing the fuse to turn on and return to the backup method.

Hystrix’s method timeout can also be turned off, and @hystrixProperty provides the switch to turn it off as follows:

@RestController @RequestMapping("/closeTimeout") public class TimeoutDisableController { @GetMapping @HystrixCommand( fallbackMethod = "fallback", commandProperties = { @HystrixProperty( name = "execution.timeout.enabled", Value = "false")}) public ResponseEntity<String> demo() {try {timeunit.seconds.sleep (3); } catch (InterruptedException e) { e.printStackTrace(); } return new ResponseEntity<>("Hello!" , HttpStatus.OK); } private ResponseEntity<String> fallback() { return new ResponseEntity<>("Timeout!" , HttpStatus.OK); }}Copy the code

The /closeTimeout interface will not trigger timeout protection for any length of time.

2.4 Hystrix Circuit Breaker Threshold Settings

By default, Hystrix protects a method that has 20 or more requests within 10 seconds. If 50% or more requests fail, the circuit breaker will enter the open state. After 5 seconds, the circuit breaker will enter the half-open state. The circuit breaker turns directly on.

Hystrix breaker threshold, default configuration: the number of times a method should be called in a given time range

circuitBreaker.requestVolumeThreshold = 20


The percentage of method calls that failed in a given time frame

circuitBreaker.errorThresholdPercentage = 50%

Rolling time period for volume of requests and percentage of errors

metrics.rollingStats.timeInMilliseconds = 10000

How long will it take for a circuit breaker in the open state to enter the half-open state, after which the original method will be tried again

circuitBreaker.sleepWindowInMilliseconds = 5000

If the number of requests exceeds 4 and 50% of the requests fail within 60 seconds, the circuit breaker enters the open state. After 60 seconds, the circuit breaker enters the half-open state and attempts to invoke the original method. I set it up like this for testing purposes.

@RestController @RequestMapping("/circuitBreaker") public class CircuitBreakConfigController { @GetMapping @HystrixCommand( fallbackMethod = "fallback", commandProperties = { @HystrixProperty( name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" ), @HystrixProperty( name = "circuitBreaker.requestVolumeThreshold", value = "4" ), @HystrixProperty( name = "circuitBreaker.errorThresholdPercentage", value = "50" ), @HystrixProperty( name = "metrics.rollingStats.timeInMilliseconds", value = "60000" ), @HystrixProperty( name = "circuitBreaker.sleepWindowInMilliseconds", Value = "60000")}) public ResponseEntity<String> demo() {try {timeunit.seconds.sleep (2); } catch (InterruptedException e) { e.printStackTrace(); } return new ResponseEntity<>("Hello!" , HttpStatus.OK); } private ResponseEntity<String> fallback() { return new ResponseEntity<>("Timeout!" , HttpStatus.OK); }}Copy the code

Set the timeout to 1 second, timeunit.seconds.sleep (2); If the thread blocks for 2 seconds, each call will obviously fail, so requests after the fourth (within 60 seconds) will execute the backup method directly.

2.5 Hystrix integration with Fegin

Most of the time, we will use Open Fegin to request data from the server. In this case, we can use Hystrix to include Fegin Client, and the integration is very simple.

Hystrix is already integrated with OpenFeign, so there is no need to import Hystrix’s dependencies separately

<! - has been dependent on Hystrix in openfeign - > < the dependency > < groupId > org. Springframework. Cloud < / groupId > <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>Copy the code

Add the @enableFeignClients annotation to the startup class, which supports Hystrix by default

@springBootApplication @enableFeignClients // Hystrix public class ConsumerApplication {public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); }}Copy the code

Enable Feign support for Hystix. In addition, the Ribbon has a default timeout due to Feign’s Ribbon integration. Therefore, in order to properly use Hystrix’s circuit breaker protection, we need to set the timeout of the Ribbon to be larger than Hystrix’s timeout. (The default timeout for both is 1 second)

Feign: hystrix: enabled: true Client: config: default: readTimeout: 3000 connectTimeout: 3000Copy the code

Define a Feign Client and specify the Fallback class, which needs to implement Feign Client in order to provide the circuit breaker service. Note that the Feign Client implementation class needs to be added to the container.

@FeignClient(value = "service", url = "http://localhost:18888/fallback", fallback = ServerClientFallback.class)
public interface ServerClient {

    @GetMapping
    ResponseEntity<String> demo();

}

@Component
class ServerClientFallback implements ServerClient {

    @Override
    public ResponseEntity<String> demo() {
        return new ResponseEntity<>("Client FallBack!", HttpStatus.OK);
    }
}
Copy the code

Define a test controller, we do not start 1888 service, simulate the server is not available!

@RestController @RequestMapping("/feign") public class FeignController { @Autowired private ServerClient serverClient; @GetMapping public ResponseEntity<String> demo() { return serverClient.demo(); }}Copy the code

Service degradation is triggered, and the Client FallBack is returned.