Spring Cloud Alibaba Sentinel對Feign的支持

Spring Cloud Alibaba Sentinel 除了對 RestTemplate 作了支持,一樣對於 Feign 也作了支持,若是咱們要從 Hystrix 切換到 Sentinel 是很是方便的,下面來介紹下如何對 Feign 的支持以及實現原理。spring

集成 Feign 使用

spring-cloud-starter-alibaba-sentinel 的依賴仍是要加的,以下:微信

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>0.2.1.RELEASE</version>
</dependency>

須要在配置文件中開啓 sentinel 對 feign 的支持:app

feign.sentinel.enabled=true

而後咱們定義本身須要調用的 Feign Client:框架

@FeignClient(name = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
    
    @GetMapping("/user/get")
    public String getUser(@RequestParam("id") Long id);
    
}

定義 fallback 類 UserFeignClientFallback:ide

@Component
public class UserFeignClientFallback implements UserFeignClient {

    @Override
    public String getUser(Long id) {
        return "fallback";
    }

}

測試代碼:源碼分析

@Autowired
private UserFeignClient userFeignClient;

@GetMapping("/testFeign")
public String testFeign() {
    return userFeignClient.getUser(1L);
}

你能夠將這個 Client 對應的 user-service 停掉,而後就能夠看到輸出的內容是 "fallback"學習

若是要對 Feign 調用作限流,資源名稱的規則是精確到接口的,以咱們上面定義的接口來分析,資源名稱就是GET:http://user-service/user/get,至於資源名稱怎麼定義的,接下面的源碼分析你就知道了。測試

原理分析

feign包裏的代碼就是對feign支持的代碼

首先看SentinelFeignAutoConfiguration中如何自動配置:ui

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
    return SentinelFeign.builder();
}

@ConditionalOnProperty 中 feign.sentinel.enabled 起了決定性做用,這也就是爲何咱們須要在配置文件中指定 feign.sentinel.enabled=truethis

接下來看 SentinelFeign.builder 裏面的實現:

build方法中從新實現了super.invocationHandlerFactory方法,也就是動態代理工廠,構建的是InvocationHandler對象。

build中會獲取Feign Client中的信息,好比fallback,fallbackFactory等,而後建立一個SentinelInvocationHandler,SentinelInvocationHandler繼承了InvocationHandler。

@Override
public Feign build() {
    super.invocationHandlerFactory(new InvocationHandlerFactory() {
        @Override
        public InvocationHandler create(Target target,
                Map<Method, MethodHandler> dispatch) {
            // 獲得Feign Client Bean
            Object feignClientFactoryBean = Builder.this.applicationContext
                    .getBean("&" + target.type().getName());
            // 獲得fallback類
            Class fallback = (Class) getFieldValue(feignClientFactoryBean,
                    "fallback");
            // 獲得fallbackFactory類
            Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
                    "fallbackFactory");
            // 獲得調用的服務名稱
            String name = (String) getFieldValue(feignClientFactoryBean, "name");

            Object fallbackInstance;
            FallbackFactory fallbackFactoryInstance;
            // 檢查 fallback 和 fallbackFactory 屬性
            if (void.class != fallback) {
                fallbackInstance = getFromContext(name, "fallback", fallback,
                                target.type());
                return new SentinelInvocationHandler(target, dispatch,
                                new FallbackFactory.Default(fallbackInstance));
            }
            if (void.class != fallbackFactory) {
                fallbackFactoryInstance = (FallbackFactory) getFromContext(name,
                        "fallbackFactory", fallbackFactory,
                                FallbackFactory.class);
                return new SentinelInvocationHandler(target, dispatch,
                                fallbackFactoryInstance);
            }
            return new SentinelInvocationHandler(target, dispatch);
        }
  
        // 省略部分代碼                
    });
        
    super.contract(new SentinelContractHolder(contract));
    return super.build();
}

SentinelInvocationHandler中的invoke方法裏面進行熔斷限流的處理。

// 獲得資源名稱(GET:http://user-service/user/get)
String resourceName = methodMetadata.template().method().toUpperCase() + ":"
                    + hardCodedTarget.url() + methodMetadata.template().url();
Entry entry = null;
try {
    ContextUtil.enter(resourceName);
    entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
    result = methodHandler.invoke(args);
}
catch (Throwable ex) {
    // fallback handle
    if (!BlockException.isBlockException(ex)) {
        Tracer.trace(ex);
    }
    if (fallbackFactory != null) {
        try {
            // 回退處理
            Object fallbackResult = fallbackMethodMap.get(method)
                    .invoke(fallbackFactory.create(ex), args);
            return fallbackResult;
        }
        catch (IllegalAccessException e) {
            // shouldn't happen as method is public due to being an interface
            throw new AssertionError(e);
        }
        catch (InvocationTargetException e) {
            throw new AssertionError(e.getCause());
        }
    }
    // 省略.....
}

總結

總的來講,這些框架的整合都有類似之處,前面講RestTemplate的整合其實和Ribbon中的@LoadBalanced原理差很少,此次的Feign的整合其實咱們從其餘框架的整合也是能夠參考出來的,最典型的就是Hystrix了。

咱們想下Hystrix要對Feign的調用進行熔斷處理,那麼確定是將Feign的請求包裝了HystrixCommand。一樣的道理,咱們只要找到Hystrix是如何包裝的,無非就是將Hystrix的代碼換成Sentinel的代碼而已。

InvocationHandlerFactory是用於建立動態代理的工廠,有默認的實現,也有Hystrix的實現feign.hystrix.HystrixFeign。

Feign build(final FallbackFactory<?> nullableFallbackFactory) {
      super.invocationHandlerFactory(new InvocationHandlerFactory() {
        @Override public InvocationHandler create(Target target,
            Map<Method, MethodHandler> dispatch) {
          return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
        }
      });
      super.contract(new HystrixDelegatingContract(contract));
      return super.build();
}

上面這段代碼是否是跟Sentinel包裝的相似,不一樣的是Sentinel構造的是SentinelInvocationHandler ,Hystrix構造的是HystrixInvocationHandle。在HystrixInvocationHandler的invoke方法中進行HystrixCommand的包裝。

歡迎加入個人知識星球,一塊兒交流技術,免費學習猿天地的課程(http://cxytiandi.com/course

PS:目前星球中正在星主的帶領下組隊學習Sentinel,等你哦!

微信掃碼加入猿天地知識星球

猿天地

相關文章
相關標籤/搜索