[Microservice] Feign integrates Sentinel, and deeply explores Sentinel’s isolation and circuit breaker degradation rules, as well as authorization rules and custom exception return results.

Article directory

  • Preface
  • 1. Feign integrates Sentinel
    • 1.1 Implementation steps
    • 1.2 FallbackFactory example
  • 2. Sentinel implements isolation
    • 2.1 How to implement isolation
    • 2.2 Sentinel implementation of thread isolation example
  • 3. Circuit breaker downgrade rules
    • 3.1 Principle and process of circuit breaker degradation
    • 3.2 Circuit breaker strategy – slow call
    • 3.3 Circuit breaker strategy – abnormal proportion and number of exceptions
  • 4. Authorization rules
    • 4.1 What are authorization rules?
    • 4.2 Example of authorization rules
  • 5. Customized exception return results

Foreword

In the previous article, we introduced Sentinel’s flow control mode and flow control effect. However, flow limiting is only a preventive measure. Although service failures caused by concurrency issues can be avoided as much as possible, services may still fail due to other factors. In order to control these failures within a certain range to avoid the avalanche effect, we need to rely on thread isolation (bulkhead mode) and circuit breaker degradation mechanisms.

Whether it is thread isolation or circuit breaker degradation, they are all designed to protect the client (caller) from service failure.

Sentinel Protection Client

Calls between microservices usually rely on Open Feign, so we first need to effectively integrate Feign with Sentinel.

This article will explore how Feign integrates with Sentinel, as well as key concepts of Sentinel such as isolation, circuit breaker degradation rules, and authorization rules.

1. Feign integrates Sentinel

1.1 Implementation steps

In Spring Cloud, calls between microservices usually rely on Feign. To secure clients in a microservices architecture, Feign and Sentinel need to be brought together. The following are the steps to integrate Feign with Sentinel, taking a microservice case named cloud-demo as an example:

1. Modify the application.yml file of order-service to enable Feign’s support for Sentinel:

feign:
  sentinel:
    enabled: true # Enable Feign's support for Sentinel

With this configuration, we tell Feign to work with Sentinel when making remote calls to ensure the client is properly protected.

2. Write downgrade logic after the call fails:

Degrade logic can be implemented when remote calls fail. There are two ways to choose from:

  • Method 1: FallbackClass, FallbackClass is a direct downgrade processing mechanism of Feign. It involves creating a class that implements the original Feign interface and defining the fallback logic in the methods of that class. However, this method cannot handle exceptions caused by remote calls.
  • Method 2: FallbackFactory (downgrade factory). FallbackFactory provides a more flexible way to handle remote service call failures. It allows us to dynamically create downgraded instances of the Feign interface and obtain specific exception information.

1.2 FallbackFactory Example

In the next section, we’ll delve into how to use FallbackFactory to handle exceptions on remote calls and provide a better downgrade experience for clients.

Step 1: Create a UserClientFallbackFactory class in the feign-api module, implement the FallbackFactory interface, and rewrite create method:

@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {<!-- -->
    @Override
    public UserClient create(Throwable throwable) {<!-- -->
        return new UserClient() {<!-- -->
            @Override
            public User findById(Long id) {<!-- -->
                log.error("Query user failed!", throwable);
                return new User();
            }
        };
    }
}

This code will be automatically called after order-service fails to call user-service. An error log will be output on the console and an empty User object.

Step 2: Register the UserClientFallbackFactory class as a Bean in config:

@Bean
public UserClientFallbackFactory userClientFallbackFactory(){<!-- -->
    return new UserClientFallbackFactory();
}

Step 3: Specify fallbackFactory in the @FeignClient annotation of the UserClient interface of feign For UserClientFallbackFactory:

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {<!-- -->
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

Through these steps, you can use FallbackFactory to handle remote service call failures, capture exception information, and provide a better downgrade experience.

2. Sentinel implements isolation

In Sentinel, there are two main ways to implement isolation, namely semaphore isolation and thread isolation. The default is semaphore isolation:

Sentinel isolation method

2.1 Implementation method of isolation

Semaphore isolation:

Semaphore isolation is a resource isolation method that controls concurrent access by setting the number of licenses for each resource (or interface). When concurrent requests arrive, if the resource's license number has been exhausted, new requests will be blocked to protect the resource from being over-accessed. Semaphore isolation is suitable for scenarios where concurrent access needs to be controlled, such as database connections, external API calls, etc.

Advantages:

  • Lightweight, no additional overhead
  • Suitable for high-frequency calls and high fan-out scenarios

Disadvantages:

  • Active timeouts are not supported
  • Asynchronous calls are not supported

Scenario: Suitable for high-frequency calls and high fan-out scenarios, where resource isolation is relatively lightweight.

Thread isolation:

Thread isolation is a resource isolation method that isolates different resource requests into different thread pools for execution to ensure that they do not affect each other. Each thread pool is responsible for executing requests for specific resources. If a request causes thread blocking or exception for some reason, it will not affect requests for other resources. Thread isolation is suitable for scenarios that require independent thread execution, such as time-consuming operations, blocking calls, etc.

Advantages:

  • Support active timeout
  • Support asynchronous calls

Disadvantages:

  • Threads have a large additional overhead

Scenario: Thread isolation is suitable for low fan-out scenarios, where resource isolation is more important, or where asynchronous calls and active timeouts need to be supported.

The choice of implementation method for isolation depends on specific needs and application scenarios. According to different scenarios and resource calling characteristics, semaphore isolation or thread isolation can be flexibly selected to ensure system stability and performance.

2.2 Sentinel implementation of thread isolation example

Looking back when using the Sentinel console to set current limiting rules, we found that there are two types of thresholds:

  • QPS: is the number of requests per second, which has been demonstrated in the quick start
  • Number of threads: is the maximum number of tomcat threads that can be used by this resource. That is, by limiting the number of threads, the bulkhead mode is achieved.

The following is an example that demonstrates how to implement thread isolation settings in Sentinel's console.

1. Set flow control rules for the query user interface of UserClient. The number of threads cannot exceed 2.

2. Then use JMeter to test.

Set the number of threads to 10:

Set up HTTP request:

Start JMeter:

It can be found that only two of the final 10 threads' requests passed.

View real-time monitoring from the Sentinel console:

3. Circuit breaker downgrade rules

3.1 Principle and process of circuit breaker degradation

Principle of circuit breaker degradation:

Circuit breaker downgrade is an important means to solve the avalanche problem. The principle is that the circuit breaker counts the abnormal proportion and slow request proportion of service calls. If the threshold is exceeded, the service will be circuit breaker. The specific principles are as follows:

  1. Closed: In the initial state, the circuit breaker is closed and all requests will be allowed to access the service.
  2. Circuit breaker state (Open): When the abnormal proportion of service calls or the proportion of slow requests exceeds the threshold, the circuit breaker will enter the circuit breaker state and intercept a certain proportion of requests. These requests will fail quickly and will not be actually accessed. Serve.
  3. Half-Open: After a period of time, the circuit breaker will enter the half-open state, allowing some requests to access the service to test whether the service has returned to normal.
  4. Closed or Open: Depending on whether the request in the half-open state is successful or not, the circuit breaker will decide whether to remain in the open state or return to the closed state.

Circuit breaker downgrade process:

The circuit breaker downgrade process is shown in the figure below:

Circuit breaker downgrade process

The process description is as follows:

  1. The circuit breaker is initially in the Closed state, allowing all requests to access the service.
  2. When the number of failed service calls or the proportion of slow requests reaches the threshold, the circuit breaker enters the Open state, intercepting all requests and failing quickly.
  3. After a period of time, the circuit breaker enters the Half-Open state, allowing some requests to access the service to test whether the service has returned to normal.
  4. If a request in the Half-Open state succeeds, the circuit breaker enters the Closed state, allowing all requests to access the service.
  5. If a request in the Half-Open state still fails, the circuit breaker continues to remain in the Open state until the next attempt to enter the Half-Open state.

Circuit breaker degradation is implemented through this state machine, which can help services avoid avalanche effects under abnormal circumstances and improve system availability and stability.

3.2 Circuit breaker strategy - slow call

There are three circuit breaker fusing strategies: slow call, abnormal ratio, and abnormal number.

Slow call means that when the service response time (RT) is greater than the specified time, the request is considered a slow call request. Within the specified time, if the number of requests exceeds the set minimum number and the slow call ratio is greater than the set threshold, the circuit breaker will be triggered.

For example, set a slow call degradation policy through the Sentinel console:

Description:
When the RT exceeds 500ms, it is a slow call. The requests in the last 10000ms are counted. If the number of requests exceeds 5 times and the slow call ratio is not less than 0.5, the circuit breaker is triggered and the circuit breaker duration is 5 seconds. Then enter the Half-open state and release a request for testing.

Now there is a requirement: to set degradation rules for the query user interface of UserClient. The RT threshold for slow calls is 50ms, the statistical time is 1 second, the minimum number of requests is 5, the failure threshold ratio is 0.4, and the circuit breaker Duration is 5:

In order to trigger the slow call rule, we need to modify the business in UserService to increase the business time:

@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id ) throws InterruptedException {<!-- -->
    if(id == 1){<!-- -->
        // Sleep, trigger slow call circuit breaker strategy
        Thread.sleep(60);
    }
    return userService.queryById(id);
}

After restarting the user-service service, we can trigger this circuit breaker mechanism by quickly refreshing the browser:

When the container is triggered, the interfaces serving other IDs will also be disconnected:

3.3 Circuit Breaker Strategy - Abnormal Ratio and Number of Abnormalities

The exception ratio or number of exceptions are statistics of calls within a specified period. If the number of calls exceeds the specified number of requests, and the proportion of exceptions reaches the set proportion threshold (or exceeds the specified number of exceptions), a circuit breaker is triggered.

This can also be set via the Sentinel console:

Description:

Count the requests in the last 1000ms. If the number of requests exceeds 10 times and the abnormality ratio is not less than 0.5, the circuit breaker will be triggered and the circuit breaker duration is 5 seconds. Then enter the Half-open state and release one request for testing.

The following is an example of abnormal proportions:

For example, there is a requirement now: to set a degradation rule for the query user interface of UserClient. The statistical time is 1 second, the minimum number of requests is 5, the failure threshold ratio is 0.4, and the circuit breaker duration is 5s:

In order to trigger exception statistics, you also need to modify the business in UserService and throw an exception:

@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) throws InterruptedException {<!-- -->
    if(id == 1){<!-- -->
        // Sleep, trigger slow call circuit breaker strategy
        Thread.sleep(60);
    } else if (id == 2) {<!-- -->
        throw new RuntimeException("Demonstration triggers abnormal ratio fuse!");
    }
    return userService.queryById(id);
}

At this time, the user with access ID 2 will throw an exception.

After restarting the user-service service, we can trigger this circuit breaker mechanism by quickly refreshing the browser:

4. Authorization rules

4.1 What are authorization rules

Authorization rules are used to control the source of the caller, and are usually divided into two methods: whitelist and blacklist:

  • Whitelist: Add the specified origin to the whitelist to allow these callers to access the service.
  • Blacklist: Add the specified origin to the blacklist and do not allow these callers to access the service.

In the Sentinel console, you can configure authorization rules as follows:

Authorization rule configuration

Now, we can serve the order-service service from both the browser and the gateway:

If you now need to restrict only requests from the gateway to access the order-service service, then fill in the name of the gateway in the flow control application.

4.2 Example of authorization rules

The following will demonstrate how to use Sentinel's authorization rules to limit requests to the order-service service to only come from the gateway gateway.

Sentinel obtains the source of the request through the parseOrigin interface of the RequestOriginParser interface:

public interface RequestOriginParser {<!-- -->
// Get the origin from the request object, and customize the acquisition method
    String parseOrigin(HttpServletRequest request);
 }

RequestOriginParser is an interface provided by Sentinel for parsing the origin of the request. This interface defines a method parseOrigin. We need to implement this method to obtain the request source information from the request object in a custom way.

Therefore, we try to get a request header named origin from request as the value of origin to determine whether the request comes from the gateway. in accordance with. The steps to achieve this are as follows:

  1. First, in the application.yml file of the gateway service, use the gateway filter to add a gateway to all requests. code>origin request header:
spring:
  cloud:
    gateway:
      default-filters: #Default filters will take effect on all routing requests
        - AddRequestHeader=origin, gateway # Sentinel authorization rules, only those served from the gateway are legal, identified by adding request headers
  1. Then add authorization rules in the Sentinel console and specify the flow control application as gateway:

  1. Implement a HeadOriginParser class in order-service and implement the RequestOriginParser interface to obtain the request source:
@Component
public class HeadOriginParser implements RequestOriginParser {<!-- -->
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {<!-- -->
        // 1. Get the request header
        String origin = httpServletRequest.getHeader("origin");
        // 2. Non-empty judgment
        if (StringUtils.isEmpty(origin)) {<!-- -->
            return "blank";
        }
        return origin;
    }
}

If there is no request header in the origin field, “blank” will be returned directly. Otherwise, the value of origin will be returned directly, and then handed over to Sentinel to determine the request source.

  1. Restart the order-service and gateway services for demonstration:

At this time, if we access the interface of order-service through the browser, we will find that access is prohibited:

If you pass the gateway gateway, you can successfully access:

5. Customized exception return results

By default, when current limiting, downgrading, or authorization interception occurs, an exception will be thrown to the caller. However, through all the above examples, we found that only one result is returned, which is “Blocked by Sentinel (flow limiting)” . If we want to know the specific reason why the microservice call failed, we need to customize the exception handling:

If you want to customize Sentinel’s exceptions, you need to implement the BlockExceptionHandler interface, which is an interface provided by Sentinel for customizing exception handling when a call fails.

As for the BlockException class, it contains many subclasses, corresponding to different scenarios:

Exception Explanation
FlowException Flow limit exception
ParamFlowException Hotspot parameter current limiting exception
DegradeException Downgrade exception
AuthorityException Authorization rule exception
SystemBlockException System rule exception

We can use these subclasses to determine what specific situation occurred when calling the microservice failed, and then return the result through a custom exception. The following is a code example to implement a custom exception:

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {<!-- -->
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {<!-- -->
        String msg = "Unknown exception";
        int status = 429;

        if (e instanceof FlowException) {<!-- -->
            msg = "The request has been throttled";
        } else if (e instanceof ParamFlowException) {<!-- -->
            msg = "The request is limited by hotspot parameters";
        } else if (e instanceof DegradeException) {<!-- -->
            msg = "The request was downgraded";
        } else if (e instanceof AuthorityException) {<!-- -->
            msg = "No permission to access";
            status = 401;
        }

        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{"msg": " + msg + ", "status": " + status + "}") ;
    }
}

In the above example, a custom exception handling class named CustomBlockExceptionHandler is created that implements the BlockExceptionHandler interface. In the overridden handle method, the exception message and status code are determined according to different BlockException types, and then the information is returned to the caller.

For example, with the authorization rules configured above, access the order-service service directly through the browser:

At this point, you can clearly know the reason why the microservice call failed.

syntaxbug.com © 2021 All Rights Reserved.