[OpenFeign] OpenFeign combines Hystrix and Sentinel to implement circuit breaker downgrade

OpenFeign can be used in conjunction with Hystrix and Sentinel to implement downgrades and circuit breakers.

OpenFeign combined with Hystrix

To use OpenFeign, you need to introduce OpenFeign dependencies:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

The dependencies introduced by spring-cloud-starter-openfeign are as follows:

[INFO] + - org.springframework.cloud:spring-cloud-starter-openfeign:jar:2.2.6.RELEASE:compile
[INFO] | + - org.springframework.cloud:spring-cloud-openfeign-core:jar:2.2.6.RELEASE:compile
[INFO] | | + - org.springframework.boot:spring-boot-starter-aop:jar:2.3.2.RELEASE:compile
[INFO] | | | \- org.aspectj:aspectjweaver:jar:1.9.6:compile
[INFO] | | \- io.github.openfeign.form:feign-form-spring:jar:3.8.0:compile
[INFO] | | + - io.github.openfeign.form:feign-form:jar:3.8.0:compile
[INFO] | | \- commons-fileupload:commons-fileupload:jar:1.4:compile
[INFO] | + - io.github.openfeign:feign-core:jar:10.10.1:compile
[INFO] | + - io.github.openfeign:feign-slf4j:jar:10.10.1:compile
[INFO] | \- io.github.openfeign:feign-hystrix:jar:10.10.1:compile
[INFO] | + - com.netflix.archaius:archaius-core:jar:0.7.6:compile
[INFO] | \- com.netflix.hystrix:hystrix-core:jar:1.5.18:compile
[INFO] | \- org.hdrhistogram:HdrHistogram:jar:2.1.9:compile

The dependency of hystrix has been automatically introduced by default, and there is no need to introduce hystrix separately.

Use fallback to implement downgrade method

The class of the downgrade method needs to implement the FeignClient interface, and this class needs to be injected into the Spring container:

package com.morris.user.client;

import com.morris.user.entity.Order;
import com.morris.user.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collections;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class OrderFeignService implements OrderClient {<!-- -->

    @Override
    public List<Order> findOrderByUserId(Long userId) {<!-- -->
        log.error("findOrderByUserIdFall error {}", userId);
        return Collections.emptyList();
    }
}

Specify the fallback attribute in the @FeignClient annotation:

@FeignClient(value = "order-service", path = "/order", fallback = OrderFeignService.class)
public interface OrderClient {<!-- -->
... ...

Enable hystrix in the configuration file:

feign:
  hystrix:
    enabled: true

Use fallbackFactory to implement the downgrade method

Using fallback to implement the downgrade method cannot obtain exception information, but using fallbackFactory to implement the downgrade method can obtain exception information.

The factory class of the fallback method needs to implement the FallbackFactory interface, and this class needs to be injected into the Spring container:

package com.morris.user.client;

import com.morris.user.entity.Order;
import com.morris.user.entity.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collections;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class OrderFeignFactory implements FallbackFactory {<!-- -->

    @Override
    public Object create(Throwable throwable) {<!-- -->
        log.error("OrderFeignFactory ", throwable);
        return new OrderClient() {<!-- -->
            @Override
            public List<Order> findOrderByUserId(Long userId) {<!-- -->
                return Collections.emptyList();
            }
        }
    }
}

Specify the fallback attribute in the @FeignClient annotation:

@FeignClient(value = "order-service", path = "/order", fallbackFactory = OrderFeignFactory.class)
public interface OrderClient {<!-- -->
... ...

Enable hystrix in the configuration file:

feign:
  hystrix:
    enabled: true

Using Hystrix fuse

The Hystrix circuit breaker function is enabled by default, and the commandKey is Class name#Method name (parameter type). For example, the commandKey corresponding to the above method is OrderClient#findOrderByUserId(Long).

You can set some parameters of circuit breaker according to commandKey in the configuration file:

hystrix:
  command:
    OrderClient#findOrderByUserId(Long):
      circuitBreaker:
        enabled: false
        requestVolumeThreshold: 2
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            #Set the request timeout, the default is 1 second. After the specified time is exceeded, the service circuit breaker is triggered.
            timeoutInMilliseconds: 10000

OpenFeign combined with Sentinel

Introduce Sentinel dependencies:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

And enable Sentinel’s circuit breaker and downgrade function:

spring:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080

Feign:
  sentinel:
    enabled: true

The use of downgrade is still the same as before, using fallback and fallbackFactory.

The use of Sentinel fuse

Although the fusing function is enabled, the fusing rules need to be configured. Without the configuration rules, the fusing will not be triggered.

Configure the following downgrade rules to test circuit breaker:

[
     {<!-- -->
          "count": 0.5,
          "grade": 1,
          "limitApp": "default",
          "minRequestAmount": 5,
          "resource": "GET:http://order-service/order/findOrderByUserId",
          "slowRatioThreshold": 1,
          "statIntervalMs": 5000,
          "timeWindow": 5000
     }
]

Customized global exception

OpenFeign can configure a global exception to wrap other exceptions in the request process, so that what is obtained in the fallbackFactory is a custom global exception instead of the original exception.

package com.morris.user.config;

import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import feign.form.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.io.Reader;
import java.text.MessageFormat;

@Configuration
@Slf4j
public class FeignErrorDecoder implements ErrorDecoder {<!-- -->
    @Override
    public Exception decode(String methodKey, Response response) {<!-- -->
        Reader reader = null;
        try {<!-- -->
            reader = response.body().asReader(CharsetUtil.UTF_8);
            String errMsg = Util.toString(reader);
            log.error("FeignErrorDecoder {}", errMsg);
            return new RuntimeException(errMsg);
        } catch (IOException e) {<!-- -->
            return new RuntimeException(MessageFormat.format("Custom Feign error message error: {0}", e.getMessage()));
        } finally {<!-- -->
            if (null != reader) {<!-- -->
                try {<!-- -->
                    reader.close();
                } catch (IOException e) {<!-- -->
                    e.printStackTrace();
                }
            }
        }
    }
}