Hystrix Circuit Breaker (Hoxton version)

Keywords: Programming Spring Java Attribute xml

Getting Started with Spring Cloud - Hystrix Circuit Breaker (Hoxton version)

Original ThinkWon Last released from December 29-27 15:25:21 Reading 680 Collections
Released from December 29-27 15:25:21
Category Column: Spring Cloud
Copyright Statement: This is an original blogger article that follows CC 4.0 BY-SA Copyright Agreement, reproduced with a link to the original source and this statement.

 

Article Directory

 

The Spring Cloud used by the project is the Hoxton version and the Spring Boot is the 2.2.2.RELEASE version

abstract

Spring Cloud Netflix Hystrix is one of the core components of the Spring Cloud Netflix subproject. It has a series of service protection functions such as service fault tolerance and thread isolation, and its usage is described in detail in this article.

Introduction to Hystrix

In the micro-service architecture, services communicate with each other through remote calls. Once a called service fails, its dependent services also fail, and then the failure spreads, resulting in system paralysis.Hystrix implements a breaker mode that returns an error response to the caller through the monitoring of the breaker when a service fails, instead of waiting for a long time. This prevents the caller from taking up threads due to a long period of unresponsiveness, thereby preventing the spread of the failure.Hystrix has powerful features such as service demotion, service corruption, thread isolation, request caching, request merging, and service monitoring.

Create a hystrix-service module

Here we create a hystrix-service module to demonstrate the common functions of hystrix.

Adding dependencies to pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
Configuring in application.yml

The port, registry address, and user-service call paths are configured.

server:
  port: 8401

spring:
  application:
    name: hystrix-service

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8001/eureka/

service-url:
  user-service: http://user-service/

Add @EnableCircuitBreaker to the boot class to turn on Hystrix's circuit breaker functionality

@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class HystrixServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(HystrixServiceApplication.class, args);
    }

}
Create UserHystrixController interface to call user-service service service

Creation of user class User and response class Result, RestTemplate configuration, UserService interface are omitted here, specific project source code can be referenced

@RestController
@RequestMapping("/user")
public class UserHystrixController {

    @Autowired
    private UserService userService;

    @GetMapping("/testFallback/{id}")
    public Result testFallback(@PathVariable Long id) {
        return userService.getUser(id);
    }

    @GetMapping("/testException/{id}")
    public Result testException(@PathVariable Long id) {
        return userService.getUserException(id);
    }

    @GetMapping("/testCommand/{id}")
    public Result getUserCommand(@PathVariable Long id) {
        return userService.getUserCommand(id);
    }

    @GetMapping("/testCache/{id}")
    public Result testCache(@PathVariable Long id) {
        userService.getUserCache(id);
        userService.getUserCache(id);
        userService.getUserCache(id);
        return new Result("Operation Successful", 200);
    }

    @GetMapping("/testRemoveCache/{id}")
    public Result testRemoveCache(@PathVariable Long id) {
        userService.getUserCache(id);
        userService.removeCache(id);
        userService.getUserCache(id);
        return new Result("Operation Successful", 200);
    }

    @GetMapping("/testCollapser")
    public Result testCollapser() throws ExecutionException, InterruptedException {
        Future<User> future1 = userService.getUserFuture(1L);
        Future<User> future2 = userService.getUserFuture(2L);
        future1.get();
        future2.get();
        ThreadUtil.safeSleep(200);
        Future<User> future3 = userService.getUserFuture(3L);
        future3.get();
        return new Result("Operation Successful", 200);
    }


}
Demonstration of Service Demotion

Add an interface to the UserHystrixController to test service degradation:

@GetMapping("/testFallback/{id}")
public Result testFallback(@PathVariable Long id) {
	return userService.getUser(id);
}

Add call methods and service demotion methods to UserService with the @HystrixCommand annotation:

@HystrixCommand(fallbackMethod = "fallbackMethod1")
public Result getUser(Long id) {
	return restTemplate.getForObject(userServiceUrl + "/user/{1}", Result.class, id);
}

/**
* Declared parameters need declared parameters that contain controller s
*
* @param id
* @return
*/
public Result fallbackMethod1(@PathVariable Long id) {
	return new Result("Service call failed", 500);
}
Start eureka-server, user-service, hystrix-service services

Call the interface for testing: http://localhost:8401/user/testFallback/1

Close the user-service service service and retest the interface. A service downgrade has occurred:

@HystrixCommand Details

Common parameters in @HystrixCommand

  • fallbackMethod: Specify the service degradation process;
  • ignoreExceptions: Ignore some exceptions without service degradation;
  • commandKey: The name of the command used to distinguish between different commands;
  • groupKey: Group name, Hystrix counts alerts and dashboard information for commands based on different groups;
  • threadPoolKey: The name of the thread pool used to divide the thread pool.

Set command, grouping, and thread pool name

Add a test interface to the UserHystrixController:

@GetMapping("/testCommand/{id}")
public Result getUserCommand(@PathVariable Long id) {
	return userService.getUserCommand(id);
}
Add ways to implement functionality in UserService:
@HystrixCommand(fallbackMethod = "fallbackMethod1",
            commandKey = "getUserCommand",
            groupKey = "getUserGroup",
            threadPoolKey = "getUserThreadPool")
public Result getUserCommand(Long id) {
    return restTemplate.getForObject(userServiceUrl + "/user/{1}", Result.class, id);
}

Ignore some abnormal downgrades using ignoreExceptions

Add a test interface to the UserHystrixController:

@GetMapping("/testException/{id}")
public Result testException(@PathVariable Long id) {
	return userService.getUserException(id);
}

Add implementation methods to UserService, ignoring NullPointerException here, throwing IndexOutOfBoundsException when id is 1 and NullPointerException when id is 2:

@HystrixCommand(fallbackMethod = "fallbackMethod2", ignoreExceptions = {NullPointerException.class})
public Result getUserException(Long id) {
	if (id == 1) {
		throw new IndexOutOfBoundsException();
	} else if (id == 2) {
		throw new NullPointerException();
	}

	return restTemplate.getForObject(userServiceUrl + "/user/{1}", Result.class, id);
}

public Result fallbackMethod2(@PathVariable Long id, Throwable e) {
	LOGGER.error("id {},throwable class:{}", id, e.getClass());
	return new Result("Service call failed", 500);
}
Call the interface for testing: http://localhost:8401/user/tesException/1

Call the interface for testing: http://localhost:8401/user/tesException/2

Request Cache for Hystrix

As the concurrency of the system increases, we need to use the cache to optimize the system to reduce the number of concurrent request threads and provide response speed.

Relevant Notes

  • @CacheResult: Opens the cache, defaults to all parameters as the cache key, and cacheKeyMethod can specify the key by returning a String type method;
  • @CacheKey: Specifies the key of the cache, either by specifying the parameter or by specifying the attribute value in the parameter as the cache key, or by returning a String type method.
  • @CacheRemove: To remove the cache, you need to specify a commandKey.

Test Use Cache

Add a test interface using the cache to the UserHystrixController and call the getUserCache method three times directly:

@GetMapping("/testCache/{id}")
public Result testCache(@PathVariable Long id) {
	userService.getUserCache(id);
	userService.getUserCache(id);
	userService.getUserCache(id);
	return new Result("Operation Successful", 200);
}
Add a cached getUserCache method to UserService:
@CacheResult(cacheKeyMethod = "getCacheKey")
@HystrixCommand(fallbackMethod = "fallbackMethod1", commandKey = "getUserCache")
public Result getUserCache(Long id) {
	LOGGER.info("getUserCache id:{}", id);
	return restTemplate.getForObject(userServiceUrl + "/user/{1}", Result.class, id);
}

/**
 * Method of generating key for cache
 *
 * @return
 */
public String getCacheKey(Long id) {
	return String.valueOf(id);
}
Call Interface Test http://localhost:8401/user/testCache/1 , the getUserCache method was called three times in this interface, but the log was printed only once, indicating that the cache was taken twice:

Test Remove Cache

Add a test interface to the UserHystrixController to remove the cache and call the removeCache method once:

@GetMapping("/testRemoveCache/{id}")
public Result testRemoveCache(@PathVariable Long id) {
	userService.getUserCache(id);
	userService.removeCache(id);
	userService.getUserCache(id);
	return new Result("Operation Successful", 200);
}
Add a removeCache method with the ability to remove caches in UserService:
@HystrixCommand
@CacheRemove(commandKey = "getUserCache", cacheKeyMethod = "getCacheKey")
public Result removeCache(Long id) {
	LOGGER.info("removeCache id:{}", id);
	return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}", null, Result.class, id);
}
Call Interface Test http://localhost:8401/user/testRemoveCache/1 You can see that both queries follow the interface:

Problems in Cache Usage

During cache usage, we need to initialize and close HystrixRequestContext before and after each request that uses the cache, otherwise the following exceptions will occur:

java.lang.IllegalStateException: Request caching is not available. Maybe you need to initialize the HystrixRequestContext?
	at com.netflix.hystrix.HystrixRequestCache.get(HystrixRequestCache.java:104) ~[hystrix-core-1.5.18.jar:1.5.18]
	at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:478) ~[hystrix-core-1.5.18.jar:1.5.18]
	at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:454) ~[hystrix-core-1.5.18.jar:1.5.18]
Here we solve this problem by using filters to initialize and close HystrixRequestContext before and after each request:
@Component
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class HystrixRequestContextFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();

        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            context.close();
        }
    }

}
Request merge

Inter-service communication in micro-service systems needs to be achieved through remote calls. As the number of calls increases, more and more thread resources will be consumed.@HystrixCollapser is provided in Hystrix for merging requests to reduce communication consumption and the number of threads.

Common properties of @HystrixCollapser

  • batchMethod: The method used to set the request merge;
  • collapserProperties: Request merge properties to control instance properties, there are many;
  • Property in timerDelayInMilliseconds:collapserProperties that controls how often requests are merged;

Functional Demo

Add the testCollapser method to UserHystrixController, where we make two service calls before making a third service call after an interval of 200 ms:

@GetMapping("/testCollapser")
public Result testCollapser() throws ExecutionException, InterruptedException {
	Future<User> future1 = userService.getUserFuture(1L);
	Future<User> future2 = userService.getUserFuture(2L);
	future1.get();
	future2.get();
	ThreadUtil.safeSleep(200);
	Future<User> future3 = userService.getUserFuture(3L);
	future3.get();
	return new Result("Operation Successful", 200);
}
Using @HystrixCollapser for request merging, all multiple calls to getUserFuture are converted to a single call to getUserByIds:
@HystrixCollapser(batchMethod = "listUsersByIds",collapserProperties = {
		@HystrixProperty(name = "timerDelayInMilliseconds",value = "100")
})
public Future<User> getUserFuture(Long id) {
	return new AsyncResult<User>() {
		@Override
		public User invoke() {
			Result result = restTemplate.getForObject(userServiceUrl + "/user/{1}", Result.class, id);
			Map data = (Map) result.getData();
			User user = BeanUtil.mapToBean(data, User.class, true);
			LOGGER.info("getUserById username:{}",user.getUsername());
			return user;
		}
	};
}

@HystrixCommand
public List<User> listUsersByIds(List<Long> ids) {
	LOGGER.info("listUsersByIds:{}",ids);
	Result result = restTemplate.getForObject(userServiceUrl + "/user/listUsersByIds?ids={1}", Result.class, CollUtil.join(ids, ","));
	return (List<User>)result.getData();
}

Note: The user-service service service needs to be restarted before testing because the test request cache just deleted the data or an error will occur

Access Interface Test http://localhost:8401/user/testCollapser Because we set up 100 milliseconds for a request merge, the first two times were merged, and the last time we merged on our own.

Common configurations for Hystrix

Global Configuration

hystrix:
  command: #Behavior used to control HystrixCommand
    default:
      execution:
        isolation:
          strategy: THREAD #Controls HystrixCommand isolation policy, THREAD->Thread Pool isolation policy (default), SEMAPHORE->semaphore isolation policy
          thread:
            timeoutInMilliseconds: 1000 #Configure the timeout for HystrixCommand execution beyond which service degradation will occur
            interruptOnTimeout: true #Configure whether HystrixCommand should be interrupted when executing timeout
            interruptOnCancel: true #Configure whether to interrupt HystrixCommand when execution is cancelled
          timeout:
            enabled: true #Configure whether execution of HystrixCommand enables timeout
          semaphore:
            maxConcurrentRequests: 10 #When signal quantity isolation policy is used to control the size of concurrent quantities, requests beyond which are rejected
      fallback:
        enabled: true #Used to control whether service demotion is enabled
      circuitBreaker: #Used to control the behavior of HystrixCircuitBreaker
        enabled: true #Used to control whether the breaker tracks health and fuse requests
        requestVolumeThreshold: 20 #Requests exceeding this number will be rejected
        forceOpen: false #Force circuit breaker on, reject all requests
        forceClosed: false #Force circuit breaker shutdown to receive all requests
      requestCache:
        enabled: true #Used to control whether request caching is turned on
  collapser: #Used to control the execution behavior of HystrixCollapser
    default:
      maxRequestsInBatch: 100 #Controlling the maximum number of requests for a merge request merge
      timerDelayinMilliseconds: 10 #Control how many milliseconds requests will be merged into one
      requestCache:
        enabled: true #Controls whether merge requests turn on caching
  threadpool: #Used to control the behavior of the thread pool where HystrixCommand executes
    default:
      coreSize: 10 #Number of core threads in the thread pool
      maximumSize: 10 #Maximum number of threads in the thread pool, requests that exceed this number will be rejected
      maxQueueSize: -1 #Used to set the maximum queue size for the thread pool, -1 uses SynchronousQueue and other positive numbers uses LinkedBlockingQueue
      queueSizeRejectionThreshold: 5 #Used to set rejection threshold for thread pool queues, since LinkedBlockingQueue cannot dynamically resize, this parameter is required to control the number of threads used

Instance Configuration

Instance configurations simply need to replace default in the global configuration with the corresponding key.

hystrix:
  command:
    HystrixComandKey: #Replace default with HystrixComrnandKey
      execution:
        isolation:
          strategy: THREAD
  collapser:
    HystrixCollapserKey: #Replace default with HystrixCollapserKey
      maxRequestsInBatch: 100
  threadpool:
    HystrixThreadPoolKey: #Replace default with HystrixThreadPoolKey
      coreSize: 10
Description of related key s in the configuration file
  • HystrixComandKey corresponds to the commandKey attribute in @HystrixCommand;
  • HystrixCollapserKey corresponds to the collapserKey attribute in the @HystrixCollapser comment;
  • HystrixThreadPoolKey corresponds to the threadPoolKey property in @HystrixCommand.

Modules used

springcloud-learning
├── eureka-server -- eureka Registration Center
├── user-service -- provide User object CRUD Service of interface
└── hystrix-service -- hystrix Service Call Test Service

Project Source Address

GitHub project source address

 

Posted by KindredHyperion on Wed, 12 Feb 2020 08:47:12 -0800