使用

引入依赖

<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--openfeign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

配置文件

配置文件需要配置eureka服务器信息,使用了ribbon负载均衡,需要配置负载均衡相关信息

server:
  port: 8889
  contextPath: /feign

spring:
  #项目名字
  application:
    name: feign

# eureka client配置
eureka:
  client:
    registryFetchIntervalSeconds: 5
    serviceUrl:
      defaultZone: http://localhost:8098/eureka/  #eureka服务端提供的注册地址 参考服务端配置的这个路径
  instance:
    hostname: feign #此实例注册到eureka服务端的唯一的实例ID
    prefer-ip-address: true #是否显示IP地址
    leaseRenewalIntervalInSeconds: 10 #eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
    leaseExpirationDurationInSeconds: 30 #Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
    health-check-url-path: /actuator/health

ribbon:
  MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试
  MaxAutoRetriesNextServer: 3 #切换实例的重试次数
  OkToRetryOnAllOperations: false  #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
  ConnectTimeout: 5000  #请求连接的超时时间
  ReadTimeout: 6000 #请求处理的超时时间

# 调用USER-MGT微服务时使用随机策略
USER-MGT:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

启动类增加注解

增加@EnableFeignClients注解,自动加载Feign的配置

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignApplication.class, args);
    }

}

定义feign调用标签

加入客户端要调用user-mgt微服务的一个接口获取当前的端口号,可以使用@FeignClient定义客户端

@FeignClient("user-mgt")
public interface UserMgtClient {
    @RequestMapping(method = RequestMethod.GET, value = "/sequence/number/port")
    String getPort();
}

调用目标接口

可以通过属性注入的方式将上面定义的UserMgtClient进行注入

@RestController
@RequestMapping("/feign")
@Slf4j
public class FeignTestController {
    @Autowired
    private UserMgtClient userMgtClient;

    @RequestMapping(value = "/server/port", method = RequestMethod.GET, produces = "application/json")
    public String getServerPort(){
        String port = userMgtClient.getPort();
        log.info("port={}",port);
        return port;
    }
}

调用过程

调用的过程可以总结如下:

  • 首先通过JDK动态代理调用ReflectiveFeign#invoke
  • 然后调用SynchronousMethodHandler#invoke创建RestTemplate
  • 接着调用Client#execute处理负载均衡,替换serviceName为具体的服务器IP
  • 再调用LoadBalancerUtils#executeWithLoadBalancerLifecycleProcessing,完成HTTP的远程调用后返回结果

对上面的调用过程做一个具体的源码回顾

首先userMgtClient.getPort()会通过JDK代理的方式进行调用,ReflectiveFeign#invoke的实现中会判断是否调用的是常用的类方法equals、hashCode和toString,如果是直接返回,否则会获取具体的代理目标类来调用目标方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if ("equals".equals(method.getName())) {
    try {
      Object otherHandler =
          args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
      return equals(otherHandler);
    } catch (IllegalArgumentException e) {
      return false;
    }
  } else if ("hashCode".equals(method.getName())) {
    return hashCode();
  } else if ("toString".equals(method.getName())) {
    return toString();
  }
  // 获取代理类,调用目标方法
  return dispatch.get(method).invoke(args);
}

dispatch.get(method).invoke(args)会调用对应的处理器SynchronousMethodHandler#invoke方法,他会新建RestTemplate,执行调用,如果失败了,会进行一次重试

public Object invoke(Object[] argv) throws Throwable {
    // 新建RestTemplate
  RequestTemplate template = buildTemplateFromArgs.create(argv);
    // 获取options
  Options options = findOptions(argv);
    // 克隆重试器
  Retryer retryer = this.retryer.clone();
  while (true) {
    try {
        // 执行调用和解码
      return executeAndDecode(template, options);
    } catch (RetryableException e) {
      try {
          // 重试
        retryer.continueOrPropagate(e);
      } catch (RetryableException th) {
          // 重试后依然报错,抛出异常
        Throwable cause = th.getCause();
        if (propagationPolicy == UNWRAP && cause != null) {
          throw cause;
        } else {
          throw th;
        }
      }
      if (logLevel != Logger.Level.NONE) {
        logger.logRetry(metadata.configKey(), logLevel);
      }
      continue;
    }
  }
}

executeAndDecode(template, options)方法中首先构造对目标接口调用的HTTP请求,这里可以扩展请求定制,通过实现RequestInterceptor接口可以为请求添加header属性等信息,如果微服务鉴权需要token,可以使用这种方式;然后进行客户端的调用,比通过异步任务的方式对响应进行解码

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 构造对目标接口调用的HTTP请求
  Request request = targetRequest(template);

  if (logLevel != Logger.Level.NONE) {
    logger.logRequest(metadata.configKey(), logLevel, request);
  }

  Response response;
  long start = System.nanoTime();
  try {
      // 客户端调用
    response = client.execute(request, options);
      // 构造响应
    response = response.toBuilder()
        .request(request)
        .requestTemplate(template)
        .build();
  } catch (IOException e) {
    if (logLevel != Logger.Level.NONE) {
      logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
    }
    throw errorExecuting(request, e);
  }
  long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

  // 解码响应信息
  if (decoder != null)
    return decoder.decode(response, metadata.returnType());
  // 解码响应采用异步的方式,通过CompletableFuture实现
  CompletableFuture<Object> resultFuture = new CompletableFuture<>();
  asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
      metadata.returnType(),
      elapsedTime);

  try {
      // 等待解码结果
    if (!resultFuture.isDone())
      throw new IllegalStateException("Response handling not done");
      // 异步执行结果反馈
    return resultFuture.join();
  } catch (CompletionException e) {
    Throwable cause = e.getCause();
    if (cause != null)
      throw cause;
    throw e;
  }
}

Request targetRequest(RequestTemplate template) {
    // 通过实现RequestInterceptor接口可以为请求添加header属性等信息,如果微服务鉴权需要token,可以使用
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    // 执行目标对象的apply方法
    return target.apply(template);
  }

client.execute(request, options)客户端执行方法是调用的核心,

public Response execute(Request request, Options options) throws IOException {
    // 创建原始URI
    URI originalUri = URI.create(request.url());
    // 获取URI的HOST
    String serviceId = originalUri.getHost();
    Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
    // 获取hint
    String hint = this.getHint(serviceId);
    // 构造负载均衡请求
    DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest(new RequestDataContext(LoadBalancerUtils.buildRequestData(request), hint));
    // 获取支持生命周期的处理器
    Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(this.loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class);
    // 生命周期开始
    supportedLifecycleProcessors.forEach((lifecycle) -> {
        lifecycle.onStart(lbRequest);
    });
    // 根据负载均衡策略获取实例
    ServiceInstance instance = this.loadBalancerClient.choose(serviceId, lbRequest);
    // 构造负载均衡响应
    org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(instance);
    String message;
    if (instance == null) { // 没有负载均衡实例
        message = "Load balancer does not contain an instance for the service " + serviceId;
        if (LOG.isWarnEnabled()) {
            LOG.warn(message);
        }
        // 生命周期结束
        supportedLifecycleProcessors.forEach((lifecycle) -> {
            lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, lbResponse));
        });
        return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value()).body(message, StandardCharsets.UTF_8).build();
    } else { // 存在负载均衡实例
        // 重新构造负载均衡请求,将serviceId替换为IP+端口
        message = this.loadBalancerClient.reconstructURI(instance, originalUri).toString();
        Request newRequest = this.buildRequest(request, message);
        // 请求处理
        return LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(this.delegate, options, newRequest, lbRequest, lbResponse, supportedLifecycleProcessors);
    }
}

LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing执行负载均衡处理的具体处理,主要完成负载均衡生命周期的处理,执行HTTP请求

static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
      Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
      org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
      Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced) throws IOException {
    // 生命周期开始
   supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, lbResponse));
   try {
       // 执行实际的HTTP请求
      Response response = feignClient.execute(feignRequest, options);
      if (loadBalanced) { // 启用了负载均衡
          // 生命周期结束
         supportedLifecycleProcessors.forEach(
               lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
                     lbRequest, lbResponse, buildResponseData(response))));
      }
      return response;
   }
   catch (Exception exception) {
      if (loadBalanced) {
         supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
               new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, lbResponse)));
      }
      throw exception;
   }
}

feignClient.execute(feignRequest, options)客户端执行HTTP请求,主要是建立HTTP的连接,然后调用,再对返回结果进行封装

public Response execute(Request request, Options options) throws IOException {
    // 建立HTTP连接,执行请求
  HttpURLConnection connection = convertAndSend(request, options);
    // 转换返回结果
  return convertResponse(connection, request);
}

配置加载

首先,要使用Feign,需要在启动类添加@EnableFeignClients注解,这个注解里面的@Import会加载FeignClientsRegistrar类,该类中会进行Feign客户端相关的配置

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
   String[] value() default {};
   String[] basePackages() default {};
   Class<?>[] basePackageClasses() default {};
   Class<?>[] defaultConfiguration() default {};
   Class<?>[] clients() default {};
}

FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,该接口在Spring创建bean的时候可以注入额外的属性信息

openfeign结合restfull请求_负载均衡


应用启动加载bean的时候,先执行FeignClientsRegistrar#registerBeanDefinitions方法,它会注册默认的配置和Feign客户端信息

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 注册默认配置
   registerDefaultConfiguration(metadata, registry);
    // 注册Feign客户端
   registerFeignClients(metadata, registry);
}

registerDefaultConfiguration会获取@EnableFeignClients注解的属性信息,然后注册客户端的配置

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 获取@EnableFeignClients注解的属性信息
   Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

   if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
      String name;
       // 获取添加注解的类名
      if (metadata.hasEnclosingClass()) {
         name = "default." + metadata.getEnclosingClassName();
      }
      else {
         name = "default." + metadata.getClassName();
      }
       // 注册客户端配置
      registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
   }
}

registerFeignClient 先获取代码中所有添加了@EnableFeignClients注解的信息,根据注解信息获取clients数组, 如果clients为空,扫描代码里面所有的@FeignClient客户端client; 注册client时,先校验注解的类是不是一个接口,然后获取注解@FeignClient的属性,注册Client配置,再注册client;

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
    // 获取@EnableFeignClients注解的信息
   Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    // 获取clients
   final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
    // 如果clients为空,代码里面的所有客户端配置信息
   if (clients == null || clients.length == 0) {
      ClassPathScanningCandidateComponentProvider scanner = getScanner();
      scanner.setResourceLoader(this.resourceLoader);
      scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
      Set<String> basePackages = getBasePackages(metadata);
      for (String basePackage : basePackages) {
         candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
      }
   } else { // 如果clients不为空,候选组件中增加clients
      for (Class<?> clazz : clients) {
         candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
      }
   }
   // 遍历候选组件,获取每一个组件的信息,校验,然后进行注册
   for (BeanDefinition candidateComponent : candidateComponents) {
      if (candidateComponent instanceof AnnotatedBeanDefinition) {
          // 校验注解的类是不是一个接口
         AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
         AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
         Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

          // 获取注解@FeignClient的属性
         Map<String, Object> attributes = annotationMetadata
               .getAnnotationAttributes(FeignClient.class.getCanonicalName());

         String name = getClientName(attributes);
          // 注册Client配置
         registerClientConfiguration(registry, name, attributes.get("configuration"));

          // 注册client
         registerFeignClient(registry, annotationMetadata, attributes);
      }
   }
}

registerFeignClient 会将具体的client信息注册到IOC容器,并为创建client的bean设置回调方法

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
      Map<String, Object> attributes) {
    // 获取类名
   String className = annotationMetadata.getClassName();
    // 解析类信息
   Class clazz = ClassUtils.resolveClassName(className, null);
    // 转化beanFactory
   ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
         ? (ConfigurableBeanFactory) registry : null;
   String contextId = getContextId(beanFactory, attributes);
   String name = getName(attributes);
    // factoryBean属性设置
   FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
   factoryBean.setBeanFactory(beanFactory);
   factoryBean.setName(name);
   factoryBean.setContextId(contextId);
   factoryBean.setType(clazz);
   factoryBean.setRefreshableClient(isClientRefreshEnabled());
    // 构造bean定义信息
   BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
       // 以下为创建bean的实例的回调方法
      factoryBean.setUrl(getUrl(beanFactory, attributes));
      factoryBean.setPath(getPath(beanFactory, attributes));
      factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
      Object fallback = attributes.get("fallback");
      if (fallback != null) {
         factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
               : ClassUtils.resolveClassName(fallback.toString(), null));
      }
      Object fallbackFactory = attributes.get("fallbackFactory");
      if (fallbackFactory != null) {
         factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
               : ClassUtils.resolveClassName(fallbackFactory.toString(), null));
      }
       // bean实例返回
      return factoryBean.getObject();
   });
   definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    // 懒加载
   definition.setLazyInit(true);
   validate(attributes);

   AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
   beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
   beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

   // has a default, won't be null
   boolean primary = (Boolean) attributes.get("primary");

   beanDefinition.setPrimary(primary);

   String[] qualifiers = getQualifiers(attributes);
   if (ObjectUtils.isEmpty(qualifiers)) {
      qualifiers = new String[] { contextId + "FeignClient" };
   }

   BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    // 注册client bean到IOC
   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
   // 如果isClientRefreshEnabled==true, 会注册Request.Options beans
   registerOptionsBeanDefinition(registry, contextId);
}

factoryBean.getObject()会调用FeignClientFactoryBean#getTarget方法,

<T> T getTarget() {
    // 从IOC中获取上下文
   FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
         : applicationContext.getBean(FeignContext.class);
    // 构造feign,设置一些配置信息
   Feign.Builder builder = feign(context);

    // url为空,一般配置走这里
   if (!StringUtils.hasText(url)) { 
      if (LOG.isInfoEnabled()) {
         LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
      }
      if (!name.startsWith("http")) { // 如果不是http开头,在服务名前面加上http,如:http://user-mgt
         url = "http://" + name;
      }else {
         url = name;
      }
      url += cleanPath();
       // 加载负载均衡器
      return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
   }
    
    // url不为空
   if (StringUtils.hasText(url) && !url.startsWith("http")) {
      url = "http://" + url;
   }
   String url = this.url + cleanPath();
   Client client = getOptional(context, Client.class);
   if (client != null) {
      if (client instanceof FeignBlockingLoadBalancerClient) {
         // not load balancing because we have a url,
         // but Spring Cloud LoadBalancer is on the classpath, so unwrap
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
      }
      if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
         // not load balancing because we have a url,
         // but Spring Cloud LoadBalancer is on the classpath, so unwrap
         client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
      }
      builder.client(client);
   }

   applyBuildCustomizers(context, builder);

   Targeter targeter = get(context, Targeter.class);
   return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

loadBalance(builder, context, new HardCodedTarget<>(type, name, url))获取client的配置,然后引用客户化配置,再获取targeter(Feign#target),最后调用Feign接口的实现类ReflectiveFeign#newInstance,创建代理对象

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
    // 获取client
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);
       // 应用客户化配置
      applyBuildCustomizers(context, builder);
       // 获取targeter,一般是DefaultTargeter
      Targeter targeter = get(context, Targeter.class);
       // DefaultTargeter#target会调用Feign#target,Feign#target调用它的实现类ReflectiveFeign#newInstance
      return targeter.target(this, builder, context, target);
   }

   throw new IllegalStateException(
         "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
}

ReflectiveFeign#newInstance为调用的目标方法设置handler,并创建JDK动态代理对象返回

public <T> T newInstance(Target<T> target) {
  Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
  Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
  List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
  // 循环调用目标对象的方法,放到上面的handler中
  for (Method method : target.type().getMethods()) {
    if (method.getDeclaringClass() == Object.class) {
      continue;
    } else if (Util.isDefault(method)) { // 默认方法
      DefaultMethodHandler handler = new DefaultMethodHandler(method);
      defaultMethodHandlers.add(handler);
      methodToHandler.put(method, handler);
    } else { // 非默认方法
      methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    }
  }
    // 创建JDK动态代理
  InvocationHandler handler = factory.create(target, methodToHandler);
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
      new Class<?>[] {target.type()}, handler);

  for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
    // 返回代理
  return proxy;
}

总的来说,Feign 的源码实现过程如下:

  • 首先通过@EnableFeignClients注解开启FeignClient 的功能。只有这个注解存在,才会在程序启动时开启对@FeignClient注解的包扫描。
  • 根据Feign的规则实现接口,并在接口上面加上@FeignClient注解。
  • 程序启动后,会进行包扫描,扫描所有的@ FeignClient 的注解的类,并将这些信息注入IoC容器中。
  • 当接口的方法被调用时,通过JDK的代理来生成具体的RequestTemplate模板对象。
  • 根据RequestTemplate再生成Http请求的Request对象。
  • Request 对象交给Client去处理,其中Client的网络请求框架可以是HtpURLConnection、HttpClient和OkHttp。
  • 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。

替换默认的HTTP Client

Feign最终的HTTP请求是通过Client来完成,默认情况下,Client的实现类Client.Default主要使用HttpURLConnection完成HTTP调用,默认没有线程池,高并发的场景下有性能瓶颈。可以替换为HttpClient或者OKHTTP。

Feign通过FeignLoadBalancerAutoConfiguration自动配置类中使用@Import注解引入了HttpClientFeignLoadBalancerConfiguration、OkHttpFeignLoadBalancerConfiguration、DefaultFeignLoadBalancerConfiguration等配置,实现了对OKHTTP、HTTPClient的自动加载配置

@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
		DefaultFeignLoadBalancerConfiguration.class })// 这三个按顺序优先加载前面的配置
public class FeignLoadBalancerAutoConfiguration {

}

下面将使用OKHTTP来处理最后的HTTP请求,他是有线程池的,可以支持高并发的场景。他的配置主要是通过OkHttpFeignLoadBalancerConfiguration配置类实现的

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")  // 需要在配置文件中配置feign.okhttp.enabled: true
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@Import(OkHttpFeignConfiguration.class) // 引入OkHttpFeignConfiguration配置
@EnableConfigurationProperties(LoadBalancerProperties.class)
class OkHttpFeignLoadBalancerConfiguration {

    // 不需要重试的时候,创建bean:FeignBlockingLoadBalancerClient
   @Bean
   @ConditionalOnMissingBean
   @Conditional(OnRetryNotEnabledCondition.class)
   public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,
         LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
      OkHttpClient delegate = new OkHttpClient(okHttpClient);
      return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
   }

    // 需要重试的时候,创建bean:RetryableFeignBlockingLoadBalancerClient
   @Bean
   @ConditionalOnMissingBean
   @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
   @ConditionalOnBean(LoadBalancedRetryFactory.class)
   @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
         matchIfMissing = true)
   public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,
         LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
         LoadBalancerClientFactory loadBalancerClientFactory) {
      OkHttpClient delegate = new OkHttpClient(okHttpClient);
      return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
            properties, loadBalancerClientFactory);
   }

}

@Import(OkHttpFeignConfiguration.class) 会引入OkHttpFeignConfiguration配置, OkHttpFeignConfiguration配置类上面的@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)表明只要引入OKHTTP的依赖就会触发该类的配置。

@Configuration(proxyBeanMethods = false)
// 引入依赖就会执行创建bean的配置
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
public class OkHttpFeignConfiguration {

   private okhttp3.OkHttpClient okHttpClient;

    // 创建线程池bean
   @Bean
   @ConditionalOnMissingBean(ConnectionPool.class)
   public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
         OkHttpClientConnectionPoolFactory connectionPoolFactory) {
      int maxTotalConnections = httpClientProperties.getMaxConnections();
      long timeToLive = httpClientProperties.getTimeToLive();
      TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
      return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
   }

    // 创建okhttp3.OkHttpClient bean
   @Bean
   public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool,
         FeignHttpClientProperties httpClientProperties) {
      boolean followRedirects = httpClientProperties.isFollowRedirects();
      int connectTimeout = httpClientProperties.getConnectionTimeout();
      Duration reaTimeout = httpClientProperties.getOkHttp().getReadTimeout();
      this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation())
            .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects)
            .readTimeout(reaTimeout).connectionPool(connectionPool).build();
      return this.okHttpClient;
   }

   @PreDestroy
   public void destroy() {
      if (this.okHttpClient != null) {
         this.okHttpClient.dispatcher().executorService().shutdown();
         this.okHttpClient.connectionPool().evictAll();
      }
   }

}

引入的依赖如下:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

feign:
  okhttp:
    enabled: true # Enables the use of the OK HTTP Client by Feign.

OkHttpClient主要是实现了Client接口,在实际调用的时候请求它的execute方法

public feign.Response execute(feign.Request input, feign.Request.Options options)
    throws IOException {
  okhttp3.OkHttpClient requestScoped;
  if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()
      || delegate.readTimeoutMillis() != options.readTimeoutMillis()
      || delegate.followRedirects() != options.isFollowRedirects()) {
    requestScoped = delegate.newBuilder()
        .connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS)
        .readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS)
        .followRedirects(options.isFollowRedirects())
        .build();
  } else {
    requestScoped = delegate;
  }
    // 转化为OkHttpRequest
  Request request = toOkHttpRequest(input);
    // 执行HTTP请求
  Response response = requestScoped.newCall(request).execute();
    // 结果转化为FeignResponse
  return toFeignResponse(response, input).toBuilder().request(input).build();
}

hystrix断路器的使用

feign在处理客户端请求的时候可以设置请求失败后的回调和服务降级处理,通过设置feign.circuitbreaker.enabled=true可以做到,但是要先引入对应的hystrix依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.10.RELEASE</version>
</dependency>

配置文件中配置

feign:
  circuitbreaker:
    enabled: true # 开启断路器

然后在客户端代码上增加配置

@FeignClient(value = "user-mgt",fallback = UserMgtFallback.class)
public interface UserMgtClient {
    @RequestMapping(method = RequestMethod.GET, value = "/sequence/number/port")
    String getPort();
}

UserMgtFallback需要注入到IOC容器中

@Component
public class UserMgtFallback implements UserMgtClient{
    @Override
    public String getPort() {
         return  "invoke port by feign encounter error";
    }
}

然后请求失败的时候返回

openfeign结合restfull请求_rpc_02