1. Series of articles

  • You must understand and can understand microservices series 1: service registration
  • You must understand and can understand microservices series 2: service unregister
  • The third in the series of microservices that you must and can understand: service invocation
  • Microservices series 4: Service discovery and load balancing

2. The preface

The principles of service invocation are introduced in the must-understand microservice series 3: Service Invocation, and the principles of service discovery and load balancing are introduced in the must-understand and must-understand microservice series 4: Service Discovery and Load Balancing.

In fact, some unexpected exceptions often occur in the process of remote service invocation. If we do not take relevant measures to deal with these exceptions, it will often bring unimaginable disasters. For example, the slow response of an interface of the service provider will cause the calling thread of the service caller to block. When the request reaches a certain amount, the resources of the service caller will be consumed and the service caller will be unable to provide services externally.

In order to avoid the unavailability of its own services due to downstream service failures, the service caller must take some measures to protect itself. Such protection technology in micro-services is called circuit breaker, which is also the content of this article

3. The fuse

3.1 Fuse Introduction

3.1.1 Fuse Configuration

Before using a fuse, you need to configure related parameters, such as the size of the sliding window and the minimum number of requests. For more related configurations, see Create and configure a CircuitBreaker

CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .minimumNumberOfCalls(3)
                .slidingWindowSize(10)
                .build();
Copy the code

3.1.2 Fuse registry

The fuse registry stores fusion-related configurations, so you can specify fuse configurations to create the fuse registry

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
Copy the code

3.1.3 Creating a Fuse

The fuse registry stores fusion-related configurations and can therefore be used to create fuse instances

CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("hello");
Copy the code

3.1.4 Use fuse to execute target logic

To achieve the fusing function, it is necessary to wrap the target logic with a fuse

CheckedFunction0<String> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier(
                circuitBreaker, () -> { throw new NullPointerException(); });

for (int i = 0; i < 10; i++) {
    TimeUnit.SECONDS.sleep(1);
    Try.of(decoratedSupplier).map(value -> value + " world'");
}
Copy the code

3.2 Fuse implementation

static <T> CheckedFunction0<T> decorateCheckedSupplier(CircuitBreaker circuitBreaker, CheckedFunction0
       
         supplier)
        {
    return() - > {// 1. Obtain the license and execute the service logic
        circuitBreaker.acquirePermission();
        final long start = circuitBreaker.getCurrentTimestamp();
        try {
            T result = supplier.apply();
            long duration = circuitBreaker.getCurrentTimestamp() - start;
            // 2. The operation is successful
            circuitBreaker.onResult(duration, circuitBreaker.getTimestampUnit(), result);
            return result;
        } catch (Exception exception) {
            // Do not handle java.lang.Error
            long duration = circuitBreaker.getCurrentTimestamp() - start;
            // 3. The execution fails
            circuitBreaker.onError(duration, circuitBreaker.getTimestampUnit(), exception);
            throwexception; }}; }Copy the code

3.2.1 Logic handling of fuse failure

void record(long duration, TimeUnit durationUnit, Outcome outcome) {
    ++this.numberOfCalls;
    this.totalDurationInMillis += durationUnit.toMillis(duration);
    switch(outcome) {
        case SLOW_SUCCESS:
            ++this.numberOfSlowCalls;
            break;
        case SLOW_ERROR:
            ++this.numberOfSlowCalls;
            ++this.numberOfFailedCalls;
            ++this.numberOfSlowFailedCalls;
            break;
        case ERROR:
            ++this.numberOfFailedCalls; }}Copy the code

Each failed execution executes a + 1 operation on the failure counter numberOfFailedCalls and the total request counter numberOfCalls. The upper level method that calls this method uses the synchronized keyword and is therefore thread-safe

3.2.2 Calculate the failure rate

private float getFailureRate(Snapshot snapshot) {
    int bufferedCalls = snapshot.getTotalNumberOfCalls();
    if (bufferedCalls == 0 || bufferedCalls < minimumNumberOfCalls) {
        return -1.0 f;
    }
    return snapshot.getFailureRate();
}
Copy the code
public float getFailureRate(a) {
    return this.totalNumberOfCalls == 0 ? 0.0 F : (float)this.totalNumberOfFailedCalls * 100.0 F / (float)this.totalNumberOfCalls;
}
Copy the code

The total number of failed requests/total number of requests can be used to get the request failure rate, but there is one thing to note here: BufferedCalls < minimumNumberOfCalls condition. If minimumNumberOfCalls is the minimum number of requests, the minimumNumberOfCalls failure rate is calculated. The minimumNumberOfCalls configuration is also described in Create and Configure a CircuitBreaker

3.2.3 Check whether the limit is exceeded

private Result checkIfThresholdsExceeded(Snapshot snapshot) {
    float failureRateInPercentage = getFailureRate(snapshot);
    float slowCallsInPercentage = getSlowCallRate(snapshot);

    if (failureRateInPercentage == -1 || slowCallsInPercentage == -1) {
        return Result.BELOW_MINIMUM_CALLS_THRESHOLD;
    }
    
    if (failureRateInPercentage >= failureRateThreshold
        && slowCallsInPercentage >= slowCallRateThreshold) {
        return Result.ABOVE_THRESHOLDS;
    }
    
    // When the request failure rate exceeds the configured threshold (default: 50%), the request is rejected directly
    if (failureRateInPercentage >= failureRateThreshold) {
        return Result.FAILURE_RATE_ABOVE_THRESHOLDS;
    }

    if (slowCallsInPercentage >= slowCallRateThreshold) {
        return Result.SLOW_CALL_RATE_ABOVE_THRESHOLDS;
    }
    return Result.BELOW_THRESHOLDS;
}
Copy the code

When the request failure rate exceeds the configured failureRateThreshold, the system directly rejects subsequent requests

3.3 Fuse summary

  • throughCircuitBreakerConfigFuses are configured
  • According to theCircuitBreakerConfigConfiguration to createCircuitBreakerRegistryThe registry
  • According to theCircuitBreakerRegistryRegistry creationCircuitBreaker
  • useCircuitBreakerWrap target business logic
  • Add 1 to the number of failed requests and the total number of requests for each failed request
  • When the requested data exceeds the minimum number of requestsminimumNumberOfCallsAfter that, the failure rate was calculated
  • The request failure rate threshold exceeds the configured thresholdfailureRateThresholdAfter, the subsequent request is rejected

3.4 the fusion openfeign

Fuse openfeign fusion function, when the target mode when the callback org. Springframework. Cloud. Openfeign. FeignCircuitBreakerInvocationHandler invoke method

3.5 Component Packages

public <T> T run(String id, Supplier
       
         toRun, Function
        
          fallback, CircuitBreaker circuitBreaker, TimeLimiter timeLimiter)
        ,>
        {
    Supplier<CompletionStage<T>> bulkheadCall = decorateBulkhead(id, toRun);
    Supplier<CompletionStage<T>> timeLimiterCall = timeLimiter
        .decorateCompletionStage(Executors.newSingleThreadScheduledExecutor(), bulkheadCall);
    Supplier<CompletionStage<T>> circuitBreakerCall = circuitBreaker.decorateCompletionStage(timeLimiterCall);
    try {
        return circuitBreakerCall.get().toCompletableFuture().get();
    }
    catch (Exception e) {
        System.out.println("exception " + e.getMessage());
        returnfallback.apply(e); }}Copy the code

Section 3.2 introduces the use of fuses to package the target business logic, and more components are integrated in SpringCloud: TimeLimiter(call time limit), Bulkhead(call resource isolation), and CircuitBreaker(CircuitBreaker).

The execution sequence is as follows:

4. Conclusion

Only by taking measures to protect the system, the system can provide services more robust and durable