Service fault tolerance protects Spring Cloud Hystrix

In microservice architecture, the system is divided into many service units, which depend on each other through service registration and subscription. Because each unit runs in a different process, dependencies are executed by remote invocation, which can lead to invocation failures or delays due to network or dependent service problems, as well as delays in the caller’s external service. If the caller’s requests continue to increase, the task backlog will be formed due to waiting for the response of the dependent party with faults, which will eventually lead to the breakdown of its own service and the breakdown of the whole system. In order to solve this problem, a series of service protection mechanisms such as circuit breakers are produced.

In distributed architecture, when a service unit fails, an error response is returned to the caller through the fault monitoring of the circuit breaker instead of waiting for a long time. In this way, threads will not be held for a long time due to the invocation of faulty services, preventing the spread of faults in the distributed system. Spring Cloud Hystrix implements a series of service protection features such as circuit breakers, thread isolation and so on. It is based on Hystrix, Netflix’s open source framework, and provides greater fault tolerance for delays and failures by controlling which nodes access remote systems, services, and third-party libraries. Hystrix offers powerful features such as service degradation, service meltdown, thread and signal isolation, request caching, request consolidation, and service monitoring.

1. Fast integration with Spring Cloud Hystrix

1.1 Basic Services

  • Eureka-server Project: Service registry
  • Hello-service project: Multiple service provider service units
  • Ribbon-service Project: Service consumers using Spring Cloud Ribbon

1.2 Integration with Spring Cloud Hystrix

  1. Add a dependency to the ribbon Service project: Spring-cloud-starter-hystrix

  2. Enable the breaker function with the @enablecircuitbreaker annotation on the ribbon- Service project main class

  3. Add the @hystrixCommand annotation to the service function corresponding to the ribbon- Service consumption service to specify the callback method

    public String helloFallback(a) {
        // fallback method
    	return "error";
    }
    
    @Override
    @HystrixCommand(fallbackMethod = "helloFallback")
    public void test(a) {
        // do something
        return "success";
    }
    Copy the code

2. Spring Cloud Hystrix usage details

2.1 Creating a Request Command

The @hystrixCommand annotation enables synchronous and asynchronous execution of the request, making the Hystrix command definition more elegant.

  1. Synchronous execution

    @Autowired
    private RestTemplate restTemplate;
    
    @HystrixCommand
    public User getUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    Copy the code
  2. Asynchronous execution

    @HystrixCommand
    public Future<User> getUserByIdAsync(final Long id) {
        return new AsyncResult<User>() {
            @Override
            public User invoke(a) {
                return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id); }}; }Copy the code

In addition to synchronous and asynchronous execution, HystrixCommand can also be implemented as an Observable. The annotation implementation still uses @hystrixCommand. When implementing a responsive command using the @hystrixCommand annotation, the observableExecutionMode parameter controls how to execute observe() or toObservable().

@HystrixCommand
public Observable<User> getUserById(final Long id) {
    return Observable.create(new Observable.OnSubscribe<User>() {
        @Override
        public void call(Subscriber<? super User> observer) {
            try {
                if(! observer.isUnsubscribed()) { User user = restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id); observer.onNext(user); observer.onCompleted(); }}catch(Exception exception) { observer.onError(exception); }}}); }Copy the code

ObservableExecutionMode Specifies the parameter setting

  • @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER)

    EAGER indicates that observe() is used

  • @HystrixCommand(observableExecutionMode = ObservableExecutionMode.LAZY)

    LAZY indicates the toObservable() execution

2.2 Service Degradation

The annotation implementation specifies the specific service degradation method by using the fallbackMethod parameter in the @HystrixCommand annotation

2.3 Exception Handling

  1. Exception propagation

    In HystrixCommand implement the run () method throws an exception, besides HystrixBadRequestException, other abnormalities are considered by Hustrix command execution failure and fine processing logic service degradation.

    HystrixCommand supports the ability to ignore the specified exception type by using the ignoreException parameter in the @hystrixCommand annotation

    @HystrixCommand(ignoreException = {BadRequestException.class})
    public User getUserById(Long id) {
    	return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    Copy the code
  2. Abnormal access

    After an exception enters the service degradation logic, the Hystrix command needs to handle different exceptions and obtain the current exception.

    Get the currently thrown exception object by adding Throwable e to the fallbackMethod implementation method argument in the @HystrixCommand annotation.

    public User helloFallback(Long id, Throwable e) {
        // fallback method
    	return null;
    }
    
    @Override
    @HystrixCommand(fallbackMethod = "helloFallback")
    public User getUserById(Long id) {
        throw new RuntimeException("get user failed");
    }
    Copy the code

2.4 Command name, grouping, and thread pool division

By setting command groups, Hystrix organizes and collects statistics on command alarms and dashboards. An annotation implementation simply sets the commandKey, groupKey, and threadPoolKey attributes of the @hystrixCommand annotation, which represent command grouping, group, and thread pool partitioning, respectively.

@HystrixCommand(commandKey="getUserById", groupKey="UserGroup", threadPoolKey="getUserByIdThread")
public User getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
}
Copy the code

2.5 Request Caching

Hystrix provides the request cache function, which can be easily enabled and used to optimize the system, reducing the consumption of request threads and reducing the response time in case of high concurrency.

The annotation implements the request cache. The annotation for the request cache:

annotation description attributes
@CacheResult Identification request command results should be cached

Must be connected to@HystrixCommandUse a combination of
cacheKeyMethod
@CacheRemove Invalidate the cache of the request command

The invalidated cache depends on the Key
commandKey

cacheKeyMethod
@CacheKey Mark the parameters of the request command as the cache Key

If no flag is used, all arguments are used, if both are used@CacheResultand@CacheRemoveThe annotation is invalidated if its cacheKeyMethod method specifies the generation of the cacheKey
value
  • Setting up cache requests

    Use the @Cacheresult annotation to enable caching for request commands. When the dependent service is called and the instance object is returned, Hystrix puts the result into the request cache because the method is modified by the @Cacheresult annotation, and its cache Key uses all parameters.

    @CacheResult
    @HystrixCommand
    public User getUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    Copy the code
  • Defining cache keys

    To specify specific cacheKey generation rules for request commands when defining cache requests using annotations, you can specify specific generation functions using @Cacheresult and @Cacheremove annotations’ cacheKeyMethod methods. The element used to assemble the CacheKey can also be specified in a method parameter using the @cachekey annotation. The @cachekey annotation also allows access to the internal attributes of the parameter object as the CacheKey.

    @CacheResult(cacheKeyMethod = "getUserByIdCacheKey")
    @HystrixCommand
    public User getUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    
    private Long getUserByIdCacheKey(Long id) {
        return id;
    }
    Copy the code
    @CacheResult
    @HystrixCommand
    public User getUserById(@CacheKey("id") Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    
    @CacheResult
    @HystrixCommand
    public User getUserByPhone(@CacheKey("phone") User user) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    Copy the code
  • Cache clearing

    The @Cacheresult annotation is used to put the request result into the Hystrix request cache. If the request result is updated by the Update operation, the request cache is inconsistent with the actual result. Therefore, the invalid cache must be cleared during the update operation. You can use the @Cacheremove attribute to clean up the invalid cache. Note that the @Cacheremove attribute commandKey must be specified to specify the request command to use the cache. Only with this attribute can Hystrix find the correct cache location for the request command.

    @CacheResult
    @HystrixCommand
    public User getUserById(@CacheKey("id") Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    
    @CacheRemove(commandKey = "getUserById")
    @HystrixCommand
    public void update(@CacheKey("id") User user) {
        return restTemplate.postForObject("http://USER-SERVICE/users", user, User.class);
    }
    Copy the code