Getting Started with Spring Cloud - Hystrix Circuit Breaker (Hoxton version)
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>
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); } }
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); } }
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); }
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); }
@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/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); }
@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); }
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); }
@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); }
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]
@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(); } } }
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); }
@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 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
- 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