SpringBoot Actual E-commerce Project Mall (20K + STAR)

Abstract

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

Hystrix profile

In the microservice architecture, services communicate with each other by means of remote invocation. Once a invoked service fails, its dependent services will also fail. At this time, the spread of failure will occur, and eventually the system will break down. Hystrix implements a circuit breaker mode. When a service fails, the circuit breaker monitors and returns an error response to the caller, rather than waiting for a long time. This prevents the caller from holding up the thread for a long time, preventing the failure from spreading. Hystrix features service degradation, service circuit breaker, thread isolation, request caching, request merging, and service monitoring.

Create a hystrix-service module

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

Add dependencies in pom.xml

<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>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code

Configure it in application.yml

The port, registry address, and invocation path of the user-service 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
Copy the code

Add @enablecircuitbreaker to the boot class to enable the breaker function of the Hystrix

@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class HystrixServiceApplication {

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

Create a UserHystrixController interface to invoke the user-service service

Service degradation demonstration

  • Add an interface for testing service degradation to UserHystrixController:
@GetMapping("/testFallback/{id}")
public CommonResult testFallback(@PathVariable Long id) {
    return userService.getUser(id);
}
Copy the code
  • Add the call method and service degradation method to UserService. Add @hystrixCommand annotation to the method:
@HystrixCommand(fallbackMethod = "getDefaultUser")
public CommonResult getUser(Long id) {
    return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
}

public CommonResult getDefaultUser(@PathVariable Long id) {
    User defaultUser = new User(-1L."defaultUser"."123456");
    return new CommonResult<>(defaultUser);
}
Copy the code
  • Start the Eureka-server, user-service, and hystrix-service services.

  • Call interface test: http://localhost:8401/user/testFallback/1

  • Disable the user-service service and test the interface again. It is found that service degradation has occurred:

@ HystrixCommand,

Common arguments in @hystrixCommand

  • FallbackMethod: Specifies the handling method for service degradation.
  • IgnoreExceptions: Ignores certain exceptions without service degradation.
  • CommandKey: specifies the command name, which is used to distinguish different commands.
  • GroupKey: indicates the group name. Hystrix collects command alarms and dashboard information based on different groups.
  • ThreadPoolKey: The name of the thread pool, which is used to divide the thread pool.

Sets the command, group, and thread pool name

  • Add test interface to UserHystrixController:
@GetMapping("/testCommand/{id}")
public CommonResult testCommand(@PathVariable Long id) {
    return userService.getUserCommand(id);
}
Copy the code
  • Select * from UserService where UserService = UserService;
 @HystrixCommand(fallbackMethod = "getDefaultUser",
    commandKey = "getUserCommand",
    groupKey = "getUserGroup",
    threadPoolKey = "getUserThreadPool")
public CommonResult getUserCommand(@PathVariable Long id) {
    return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
 }
Copy the code

Ignore some exception degradation using ignoreExceptions

  • Add test interface to UserHystrixController:
@GetMapping("/testException/{id}")
public CommonResult testException(@PathVariable Long id) {
    return userService.getUserException(id);
}
Copy the code
  • Added in the UserService implementation method, ignores NullPointerException here, when the id is 1 throw IndexOutOfBoundsException, id for throwing NullPointerException when 2:
@HystrixCommand(fallbackMethod = "getDefaultUser2", ignoreExceptions = {NullPointerException.class})
public CommonResult getUserException(Long id) {
    if (id == 1) {
        throw new IndexOutOfBoundsException();
    } else if (id == 2) {
        throw new NullPointerException();
    }
    return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
}

public CommonResult getDefaultUser2(@PathVariable Long id, Throwable e) {
    LOGGER.error("getDefaultUser2 id:{},throwable class:{}", id, e.getClass());
    User defaultUser = new User(-2L."defaultUser2"."123456");
    return new CommonResult<>(defaultUser);
}
Copy the code
  • Call interface test: http://localhost:8401/user/tesException/1

  • Call interface test: http://localhost:8401/user/tesException/1

Hystrix request cache

When the amount of concurrent requests in the system is increasing, we need to optimize the system by using cache to reduce the number of concurrent requests and provide the effect of response speed.

Related note

  • @cacheeresult: Enable caching. By default, all parameters are used as the key of the cache. CacheKeyMethod can be specified with a method of mandatory String;
  • @cachekey: specifies a CacheKey. You can specify a parameter or attribute in a parameter as a CacheKey. CacheKeyMethod can also be specified by a String method;
  • CacheRemove: To remove a cache, specify a commandKey.

Testing using caching

  • Add a test interface that uses cache to UserHystrixController and call getUserCache three times directly:
@GetMapping("/testCache/{id}")
public CommonResult testCache(@PathVariable Long id) {
    userService.getUserCache(id);
    userService.getUserCache(id);
    userService.getUserCache(id);
    return new CommonResult("Operation successful".200);
}
Copy the code
  • Add getUserCache to UserService;
@CacheResult(cacheKeyMethod = "getCacheKey")
@HystrixCommand(fallbackMethod = "getDefaultUser", commandKey = "getUserCache")
    public CommonResult getUserCache(Long id) {
    LOGGER.info("getUserCache id:{}", id);
    return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
}

/** * Method to generate key for cache */
public String getCacheKey(Long id) {
    return String.valueOf(id);
}
Copy the code
  • Call interface testing http://localhost:8401/user/testCache/1, call the three getUserCache method in this interface, but I only print a log, shows that there are two times of cache:

Test removing cache

  • Add a test interface for removing caches to UserHystrixController and call the removeCache method once:
@GetMapping("/testRemoveCache/{id}")
public CommonResult testRemoveCache(@PathVariable Long id) {
    userService.getUserCache(id);
    userService.removeCache(id);
    userService.getUserCache(id);
    return new CommonResult("Operation successful".200);
}
Copy the code
  • Add a removeCache method to UserService that removes the cache:
@CacheRemove(commandKey = "getUserCache", cacheKeyMethod = "getCacheKey")
@HystrixCommand
public CommonResult removeCache(Long id) {
    LOGGER.info("removeCache id:{}", id);
    return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}".null, CommonResult.class, id);
}
Copy the code
  • Call interface testing http://localhost:8401/user/testRemoveCache/1, there are two queries are walking is the interface:

Problems with cache usage

  • We need to initialize and close HystrixRequestContext before and after each cached request. 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.518].Copy the code
  • Here we solve this problem by using a filter to initialize and close HystrixRequestContext before and after each request:
/** * Created by macro on 2019/9/4. */
@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(); }}}Copy the code

Request to merge

The communication between services in a microservice system needs to be realized through remote calls, which will consume more thread resources as the number of calls increases. Hystrix provides the @hystrixCollapser for merging requests, reducing communication consumption and the number of threads in the collapse request.

Common properties in @hystrixCollapser

  • BatchMethod: Method for setting the request merge;
  • CollapserProperties: Request merge properties to control instance properties, many of which are collapsed;
  • TimerDelayInMilliseconds: Collapse the property in the collapserProperties to control how often to fold the request;

Function demonstration

  • Add the testCollapser method in the UserHystrixController, and make two service calls, then a third 200ms later:
@GetMapping("/testCollapser")
public CommonResult testCollapser(a) 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 CommonResult("Operation successful".200);
}
Copy the code
  • Implement the merge request using the @hystrixCollapser, and all multiple calls to getUserFuture are transformed into a single call to getUserByIds:
@HystrixCollapser(batchMethod = "getUserByIds",collapserProperties = {
    @HystrixProperty(name = "timerDelayInMilliseconds", value = "100")})public Future<User> getUserFuture(Long id) {
    return new AsyncResult<User>(){
    @Override
    public User invoke(a) {
        CommonResult commonResult = restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
        Map data = (Map) commonResult.getData();
        User user = BeanUtil.mapToBean(data,User.class,true);
        LOGGER.info("getUserById username:{}", user.getUsername());
        returnuser; }}; }@HystrixCommand
public List<User> getUserByIds(List<Long> ids) {
    LOGGER.info("getUserByIds:{}", ids);
    CommonResult commonResult = restTemplate.getForObject(userServiceUrl + "/user/getUserByIds? ids={1}", CommonResult.class, CollUtil.join(ids,","));
    return (List<User>) commonResult.getData();
}
Copy the code
  • http://localhost:8401/user/testCollapser access interface testing, because we set the 100 milliseconds for a request to merge, the first two are combined, merged last alone.

Common Hystrix configurations

Global configuration

hystrix:
  command: # to control the behavior of HystrixCommand
    default:
      execution:
        isolation:
          strategy: THREAD # Control HystrixCommand's isolation policy, THREAD-> THREAD pool isolation policy (default), SEMAPHORE-> SEMAPHORE isolation policy
          thread:
            timeoutInMilliseconds: 1000 Configure the timeout period for HystrixCommand execution. If the timeout period is exceeded, the service will be degraded
            interruptOnTimeout: true # Configure whether to interrupt the execution of HystrixCommand timeout
            interruptOnCancel: true Configure whether to interrupt when HystrixCommand execution is cancelled
          timeout:
            enabled: true # Configure HystrixCommand execution with timeout enabled
          semaphore:
            maxConcurrentRequests: 10 When using the semaphore isolation policy, it is used to control the amount of concurrency. Requests exceeding this amount will be rejected
      fallback:
        enabled: true # to control whether service degradation is enabled
      circuitBreaker: # used to control the behavior of HystrixCircuitBreaker
        enabled: true Use to control whether the circuit breaker tracks health status and fuse requests
        requestVolumeThreshold: 20 # Requests exceeding this number will be rejected
        forceOpen: false Force open circuit breaker to reject all requests
        forceClosed: false Force off circuit breaker to receive all requests
      requestCache:
        enabled: true # to control whether request caching is enabled
  collapser: # Controls the execution behavior of the HystrixCollapser
    default:
      maxRequestsInBatch: 100 Control the maximum number of requests that can be merged at a time
      timerDelayinMilliseconds: 10 # Controls how many milliseconds in which requests are combined
      requestCache:
        enabled: true Controls whether caching is enabled for merge requests
  threadpool: # to control the behavior of the thread pool where HystrixCommand is executed
    default:
      coreSize: 10 # Number of core threads in the thread pool
      maximumSize: 10 # Maximum number of threads in the thread pool. Requests exceeding this number will be rejected
      maxQueueSize: - 1 # sets the maximum queue size for the thread pool. -1 is SynchronousQueue and all positive numbers are LinkedBlockingQueue
      queueSizeRejectionThreshold: 5 # Set the thread pool queue rejection threshold. Since LinkedBlockingQueue cannot be dynamically resized, use this parameter to control the number of threads
Copy the code

The instance configuration

For instance configuration, you only need to replace default with the corresponding key in the global configuration.

hystrix:
  command:
    HystrixComandKey: # Replace default by Hystrixon-Comr andKey
      execution:
        isolation:
          strategy: THREAD
  collapser:
    HystrixCollapserKey: # CollapserKey in the default folder
      maxRequestsInBatch: 100
  threadpool:
    HystrixThreadPoolKey: Replace default with HystrixThreadPoolKey
      coreSize: 10
Copy the code

Description of related keys in the configuration file

  • HystrixComandKey corresponds to the commandKey attribute in @hystrixCommand;
  • HystrixCollapserKey corresponds to the collapserKey attribute in the @hystrixCollapser annotation;
  • HystrixThreadPoolKey corresponds to the threadPoolKey attribute in @hystrixCommand.

The module used

Springcloud - learning ├ ─ ─ eureka - server-- Eureka Registry├ ─ ─ the user - service-- Provides services for the CRUD interface of User objects└ ─ ─ hystrix - serviceThe Hystrix service calls the test service
Copy the code

Project source address

Github.com/macrozheng/…

The public,

Mall project full set of learning tutorials serialized, pay attention to the public account for the first time.