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
- through
CircuitBreakerConfig
Fuses are configured - According to the
CircuitBreakerConfig
Configuration to createCircuitBreakerRegistry
The registry - According to the
CircuitBreakerRegistry
Registry creationCircuitBreaker
- use
CircuitBreaker
Wrap 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 requests
minimumNumberOfCalls
After that, the failure rate was calculated - The request failure rate threshold exceeds the configured threshold
failureRateThreshold
After, 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