Spring Cloud Alibaba Sentinel 对 Feign 的支持

Spring Cloud Alibaba Sentinel 除了对 RestTemplate 做了支持,同样对于 Feign 也做了支持,如果我们要从 Hystrix 切换到 Sentinel 是非常方便的,下面来介绍下如何对 Feign 的支持以及实现原理。

集成 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 的支持:

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:

@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,至于资源名称怎么定义的,接下面的源码分析你就知道了。

原理分析

首先看SentinelFeignAutoConfiguration中如何自动配置:

@Bean

@Scope("prototype")

@ConditionalOnMissingBean

@ConditionalOnProperty(name = "feign.sentinel.enabled")

public Feign.Builder feignSentinelBuilder() {

    return SentinelFeign.builder();

}

@ConditionalOnProperty 中 feign.sentinel.enabled 起了决定性作用,这也就是为什么我们需要在配置文件中指定 feign.sentinel.enabled=true

接下来看 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的包装。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值