在前面的 springCloud 之 Ribbon 负载均衡 中 服务消费者 调用 服务提供者 使用 RestTemplate 技术,代码如下:

// 使用 RestTemplate 模板对象进行远程调用
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
    return new RestTemplate();
}

存在以下不便之处

  • 需要拼接 url,拼接 url ⾮常的 low,拼接字符串,拼接参数,很low还容易出错
  • restTmplate.getForObJect() 
  • 这两处代码都⽐较模板化,能不能不写这种模板化的代码 ???

Feign 简介

Feign 是 Netflix 开发的⼀个轻量级 RESTful 的 HTTP 服务客户端(可以⽤它来发起远程调⽤请求),是以 Java 接⼝注解的⽅式调⽤ Http 请求,⽽不⽤像 Java 中通过封装 HTTP 请求报⽂,可以直接用来调⽤,Feign 被⼴泛应⽤在 SpringCloud 的解决⽅案中。

类似于Dubbo,服务消费者拿到服务提供者的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,而实际发出的是远程的调用请求。

Feign 可帮助我们更加便捷,优雅的调⽤ HTTP API:不需要我们去拼接 url、⽤ restTemplate 的 api,在 SpringCloud 中,使⽤ Feign ⾮常简单,创建⼀个接⼝(在消费者--服务调⽤⽅这⼀端),并在接⼝上添加⼀些注解,代码就完成了

SpringCloud 对 Feign 进⾏了增强,使 Feign ⽀持了 SpringMVC 注解(OpenFeign)

本质:封装了 Http 调用流程,更符合⾯向接⼝化的编程习惯,类似于 Dubbo 的服务调⽤

Dubbo 的调⽤⽅式其实就是很好的⾯向接⼝编程

Feign 配置应用

在服务调⽤者⼯程(消费)创建接⼝(添加注解)

(效果)Feign = RestTemplate + Ribbon + Hystrix

服务消费者⼯程中引⼊Feign依赖(或者⽗类⼯程)

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

服务消费者⼯程(⾃动投递微服务)启动类使⽤注解 @EnableFeignClients 添加 Feign ⽀持

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients  // 开启 Feign 客户端功能
public class MicroServiceApplication {

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

注意:

此时去掉  Hystrix  熔断的⽀持注解  @EnableCircuitBreaker  即可包括引⼊的依赖,因为  Feign  会⾃动引⼊

创建 Feign 接口

// 原来:http://micro-service-A/resume/openstate/ + userId;
// @FeignClient表明当前类是一个 Feign 客户端,value 指定该客户端要请求的服务名称(登记到注册中心上的服务提供者的服务名称)
@FeignClient(value = "micro-service-A", fallback = ServiceAFeignClientFallback.class, path = "/resume")
//@RequestMapping("/resume")
public interface ServiceAFeignClient {
    // Feign要做的事情就是,拼装 url 发起请求
    // 我们调用该方法就是调用本地接口方法,那么实际上做的是远程请求
    @GetMapping("/openstate/{userId}")
    public Integer findDefaultResumeState(@PathVariable("userId") Long userId);
}

注意:

  1. @FeignClient注解的name属性⽤于指定要调⽤的服务提供者名称,和服务提供者yml⽂件中spring.application.name保持⼀致
  2. 接⼝中的接⼝⽅法,就好⽐是远程服务提供者Controller中的Hander⽅法(只不过如同本地调⽤了),那么在进⾏参数绑定的时,可以使⽤ @PathVariable、@RequestParam、@RequestHeader等,这也是 OpenFeign 对 SpringMVC 注解的⽀持,但是需要注意 value 必须设置,否则会抛出异常

使用 feign 接口中的法完成远程调用(注入接接口即可,实际注入的是接口的实现)(代码略)

Feign对负载均衡的支持

Feign 本身已经集成了 Ribbon 依赖和⾃动配置,因此我们不需要额外引⼊依赖,可以通过 ribbon.xx 来进⾏全局配置,也可以通过 服务名.ribbon.xx 来对指定服务进⾏细节配置配置(参考之前,此处略)

Feign 默认的请求处理超时时⻓1s,有时候我们的业务确实执⾏的需要⼀定时间,那么这个时候,我们就需要调整请求处理超时时⻓,Feign ⾃⼰有超时设置,如果配置 Ribbon 的超时,则会以 Ribbon 的为准

Ribbon 设置

# 针对的被调用方微服务名称,不加就是全局生效
lagou-service-resume:
  ribbon:
    # 请求连接超时时间
    ConnectTimeout: 2000
    # 请求处理超时时间
    # Feign超时时长设置
    ReadTimeout: 3000
    # 对所有操作都进行重试
    OkToRetryOnAllOperations: true
    # 根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),
    # 如果不行,就换一个实例进行访问,如果还不行,再换一次实例访问(更换次数由 MaxAutoRetriesNextServer 配置),
    # 如果依然不行,返回失败信息。
    MaxAutoRetries: 0 # 对当前选中实例重试次数,不包括第一次调用
    MaxAutoRetriesNextServer: 0 # 切换实例的重试次数
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # 负载策略调整

Feign 对熔断器的支持

1)在 Feign 客户端⼯程配置⽂件(application.yml)中开启 Feign 对熔断器的⽀持

# 开启Feign的熔断功能
feign:
  hystrix: 
    enabled: true

Feign 的超时时⻓设置那其实就上⾯ Ribbon 的超时时⻓设置 Hystrix 超时设置(就按照之前 Hystrix 设置的⽅式就OK了)

注意:

  1. 开启 Hystrix 之后,Feign 中的⽅法都会被进⾏⼀个管理了,⼀旦出现问题就进⼊对应的回退逻辑处理
  2. 针对超时这⼀点,当前有两个超时时间设置(Feign / hystrix),熔断的时候是根据这两个时间的最⼩值来进⾏的,即处理时⻓超过最短的那个超时时间了就熔断进⼊回退降级逻辑
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            # Hystrix的超时时长设置
            timeoutInMilliseconds: 15000

⾃定义 FallBack 处理类(需要实现 FeignClient 接⼝)

/**
 * 降级回退逻辑需要定义一个类,实现 FeignClient 接口,实现接口中的方法
 */
@Component  // 别忘了这个注解,还应该被扫描到
public class ServiceAFeignClientFallback implements ResumeServiceFeignClient {
    @Override
    public Integer findDefaultResumeState(Long userId) {
        return -6;
    }
}

在 @FeignClient 注解中关联 中⾃定义异常处理类

就是下面注解中的 fallback 属性,采用了在类头统一 url 前缀的配置的,此时需要改到配置在 @feignClient 的 path 属性中,否则会报错

@FeignClient(value = "micro-service-A", fallback = ServiceAFeignClientFallback.class, path = "/resume")

Feign 对请求压缩和响应压缩的⽀持

Feign  ⽀持对请求和响应进⾏ GZIP 压缩,以减少通信过程中的性能损耗。通过下⾯的参数   即可开启请求与响应的压缩功能:

feign:
  compression:
    request:
      enabled: true # 开启请求压缩
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型,此处也是默认值
      min-request-size: 2048 # 设置触发压缩的⼤⼩下限,此处也是默认值
    response:
      enabled: true # 开启响应压缩

Feign 的⽇志级别配置

Feign 是http请求客户端,类似于咱们的浏览器,它在请求和接收响应的时候,可以打印出⽐较详细的⼀些⽇志信息(响应头,状态码等等)

如果我们想看到 Feign 请求时的⽇志,我们可以进⾏配置,默认情况下 Feign 的⽇志没有开启。

开启Feign⽇志功能及级别

@Configuration
public class FeignLog {
    @Bean
    Logger.Level feignLevel() {
         // Feign 的⽇志级别(Feign请求过程信息)
         // NONE:默认的,不显示任何⽇志---- 性能最好
         // BASIC:仅记录请求⽅法、URL、响应状态码以及执⾏时间----- ⽣产问题追踪
         // HEADERS:在BASIC级别的基础上,记录请求和响应的header
         // FULL:记录请求和响应的header、body和元数据----- 适⽤于开发及测试环境定位问题
        return Logger.Level.FULL;
    }
}

配置log日志级别为 debug

logging:
  level:
    # Feign 日志只会对日志级别为 debug 的做出响应,针对单个类设置日志级别
    com.tansun.controller.service.ServiceFeignClient: debug

Feign 核心源码剖析

思考⼀个问题:只定义了接⼝,添加上 @FeignClient,真的没有实现的话,能完成远程请求么?答案肯定是不能,所以我们可以猜到应该是做了代理了。

我们可以在使用 feignClient 调用远程服务的位置打断点,看看对应的接口实例是什么类型,通过这个⽅法可以确定,确实是个代理对象!!

源码剖析我从下面几个面来进行

1、从 @EnableFeignClients 正向切入

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients  // 开启Feign 客户端功能
public class FeignClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignClientApplication .class,args);
    }
}

启动类增加了 @EnableFeignClients 注解,我们首先来看看这个注解的源码:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    // ...
}

明显这导入了 FeignClientsRegistrar 组件,

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}
}

FeignClientsRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口的 registerBeanDefinitions 方法,在这个方法中做了两件非常关键的事情

1、把 feignClient 的全局默认配置注入到容器中

1.1、将 @EnableFeignClients 中的 defaultConfiguration 属性中配置的 class 类型注入到容器

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	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();
		}
        // 这里是针对全局的 feignClientConfigration
		registerClientConfiguration(registry, name,	defaultAttrs.get("defaultConfiguration"));
	}
}

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
	builder.addConstructorArgValue(name);
	builder.addConstructorArgValue(configuration);
	registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}

2、将被 @FeignClient 修饰的类创建实例对象并注入到容器【专门针对添加了 @FeignClient 注解的接口的操作】,从下面的源码可以知道:@FeignClient 修饰过的接口注册到容器中的对象类型是 FeignClientFactoryBean,是一个 factoryBean,其实真正使用的时候是 FeignClientFactoryBean.getObject() 返回的对象,该对象才是对应接口的代理类

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 初始化扫描器,并后续做相关属性的填充【最主要的就是:类型过滤器】
	ClassPathScanningCandidateComponentProvider scanner = getScanner();
	scanner.setResourceLoader(this.resourceLoader);
	Set<String> basePackages;
	Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
	// 创建注解类型过滤器,用于后面包扫描时进行过滤
	AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
	final Class<?>[] clients = attrs == null ? null	: (Class<?>[]) attrs.get("clients");
	if (clients == null || clients.length == 0) {
		// 将上面的过滤器添加到扫描器中
		scanner.addIncludeFilter(annotationTypeFilter);
		// 获取需要扫描的包,默认扫描启动下面的包,如果配置了,那么久按照配置的包来扫描
		basePackages = getBasePackages(metadata);
	} else {
		final Set<String> clientClasses = new HashSet<>();
		basePackages = new HashSet<>();
		for (Class<?> clazz : clients) {
			basePackages.add(ClassUtils.getPackageName(clazz));
			clientClasses.add(clazz.getCanonicalName());
		}
		AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
			@Override
			protected boolean match(ClassMetadata metadata) {
				String cleaned = metadata.getClassName().replaceAll("\\$", ".");
				return clientClasses.contains(cleaned);
			}
		};
		scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
	}

	for (String basePackage : basePackages) {
		Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
				Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
				Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
				String name = getClientName(attributes);
                // @feignClient 注解有一个配置属性 configration(这里是针对单个类的 feignClientConfigration),获取后注入到容器中
				registerClientConfiguration(registry, name,	attributes.get("configuration"));
				// 注册 FeignClient 对象到容器中【核心步骤】
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}
}

// 根据 @FeignClient 注解的属性封装 beanDefinition 对象,对象类型是什么???
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
	String className = annotationMetadata.getClassName();
	// 这里说明 beanDefinition 对象,对象类型是 FeignClientFactoryBean,是一个 factoryBean,其实真正使用的时候是 FeignClientFactoryBean.getObject() 返回的对象,该对象才是对应接口的代理类
	BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
	validate(attributes);
	definition.addPropertyValue("url", getUrl(attributes));
	definition.addPropertyValue("path", getPath(attributes));
	String name = getName(attributes);
	definition.addPropertyValue("name", name);
	String contextId = getContextId(attributes);
	definition.addPropertyValue("contextId", contextId);
	definition.addPropertyValue("type", className);
	definition.addPropertyValue("decode404", attributes.get("decode404"));
	definition.addPropertyValue("fallback", attributes.get("fallback"));
	definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
	definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

	String alias = contextId + "FeignClient";
	AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

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

	String qualifier = getQualifier(attributes);
	if (StringUtils.hasText(qualifier)) {
		alias = qualifier;
	}

	BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
	BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

所以,接下来需要重点关注 org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject 方法,这个方法应该会返回一个代理对象

通过断点,验证了前面的猜想

接下来,FeignClientFactoryBean.getObject ⽅法

Spring Cloud Gateway spel 远程代码执行 springcloud远程调用方式_spring

org.springframework.cloud.openfeign.HystrixTargeter#target

<T> T getTarget() {
	FeignContext context = applicationContext.getBean(FeignContext.class);
	Feign.Builder builder = feign(context);

	// 判断当前 FeignClientFactoryBean 对象 url 属性是否为空
	// url 属性是用来访问一个固定地址,一般用来做测试,本地开发环境可以使用这个属性联调,避免调到其他人的电脑
	if (!StringUtils.hasText(this.url)) {
		if (!this.name.startsWith("http")) {
			url = "http://" + this.name;
		} else {
			url = this.name;
		}
		url += cleanPath();
		// url 为空,那么生成的 FeignClient 客户端对象应该是具备负载均衡功能的客户端对象【feign+ribbon】
		return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url));
	}
	// ...
}

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
	// 通过断点可以看到这里的 client 类型是 loadBalancerFeignClient
	Client client = getOptional(context, Client.class);
	if (client != null) {
		// 使用 builder 构造器保证 client
		builder.client(client);
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);
	}
	// ...
}

feign.Feign.Builder#target(feign.Target<T>)

public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

public Feign build() {
  SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
          logLevel, decode404, closeAfterDecode, propagationPolicy);
  ParseHandlersByName handlersByName =
      new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
          errorDecoder, synchronousMethodHandlerFactory);
  // 这里返回了一个反射的 Feign 对象
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

feign.ReflectiveFeign#newInstance

这里明显可以看到是生成了一个 JDK 动态代理对象

Spring Cloud Gateway spel 远程代码执行 springcloud远程调用方式_服务提供者_02

请求进来时候,是进⼊增强逻辑的,所以接下来我们要关注增强逻辑部分:feign.ReflectiveFeign.FeignInvocationHandler#invoke

Spring Cloud Gateway spel 远程代码执行 springcloud远程调用方式_spring_03

feign.SynchronousMethodHandler#invoke

@Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // 处理后续请求逻辑
        return executeAndDecode(template);
      } catch (RetryableException e) {
        // ...
    }
  }

feign.SynchronousMethodHandler#executeAndDecode

Object executeAndDecode(RequestTemplate template) throws Throwable {
    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);
    } 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);

    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }

org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute

@Override
public Response execute(Request request, Request.Options options) throws IOException {
	try {
		URI asUri = URI.create(request.url());
		String clientName = asUri.getHost();
		URI uriWithoutHost = cleanUrl(request.url(), clientName);
        // 构建 ribbon 请求对象
		FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
				this.delegate, request, uriWithoutHost);

		IClientConfig requestConfig = getClientConfig(options, clientName);
        // 使用  ribbon 进行后续请求处理,包括负载均衡等
		return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
	}
	catch (ClientException e) {
        // ...
	}
}

com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
		// 【核心方法】
        return command.submit(
			// ServerOperation 对象(服务实例操作对象),
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        // 最终是在这里完成请求的调用
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } 
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            }).toBlocking().single();
    } catch (Exception e) {
        // ...
    }
}

进⼊submit⽅法,我们进⼀步就会发现使⽤Ribbon在做负载均衡了

com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit

public Observable<T> submit(final ServerOperation<T> operation) {
    final ExecutionInfoContext context = new ExecutionInfoContext();
    
    if (listenerInvoker != null) {
        try {
            listenerInvoker.onExecutionStart();
        } catch (AbortExecutionException e) {
            return Observable.error(e);
        }
    }

	// 重试相关配置获取
    final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
    final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();

    // Use the load balancer
	// selectServer() 负载均衡 --> 选择实例
    Observable<T> o = (server == null ? selectServer() : Observable.just(server)).concatMap(new Func1<Server, Observable<T>>() {
                @Override
                // Called for each server being selected
                public Observable<T> call(Server server) {
                    context.setServer(server);
                    final ServerStats stats = loadBalancerContext.getServerStats(server);
                    
                    // Called for each attempt and retry
                    Observable<T> o = Observable
                            .just(server)
                            .concatMap(new Func1<Server, Observable<T>>() {
                                @Override
                                public Observable<T> call(final Server server) {
                                    context.incAttemptCount();
                                    loadBalancerContext.noteOpenConnection(stats);
                                    
                                    if (listenerInvoker != null) {
                                        try {
                                            listenerInvoker.onStartWithServer(context.toExecutionInfo());
                                        } catch (AbortExecutionException e) {
                                            return Observable.error(e);
                                        }
                                    }
                                    
                                    final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start();
                                    
                                    return operation.call(server).doOnEach(new Observer<T>() {
                                        private T entity;
                                        @Override
                                        public void onCompleted() {
                                            recordStats(tracer, stats, entity, null);
                                            // TODO: What to do if onNext or onError are never called?
                                        }

                                        @Override
                                        public void onError(Throwable e) {
                                            recordStats(tracer, stats, null, e);
                                            logger.debug("Got error {} when executed on server {}", e, server);
                                            if (listenerInvoker != null) {
                                                listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo());
                                            }
                                        }

                                        @Override
                                        public void onNext(T entity) {
                                            this.entity = entity;
                                            if (listenerInvoker != null) {
                                                listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo());
                                            }
                                        }                            
                                        
                                        private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) {
                                            tracer.stop();
                                            loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler);
                                        }
                                    });
                                }
                            });
                    
                    if (maxRetrysSame > 0) 
                        o = o.retry(retryPolicy(maxRetrysSame, true));
                    return o;
                }
            });
        
    if (maxRetrysNext > 0 && server == null) 
        o = o.retry(retryPolicy(maxRetrysNext, false));
    
    return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
        @Override
        public Observable<T> call(Throwable e) {
            if (context.getAttemptCount() > 0) {
                if (maxRetrysNext > 0 && context.getServerAttemptCount() == (maxRetrysNext + 1)) {
                    e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED,
                            "Number of retries on next server exceeded max " + maxRetrysNext
                            + " retries, while making a call for: " + context.getServer(), e);
                }
                else if (maxRetrysSame > 0 && context.getAttemptCount() == (maxRetrysSame + 1)) {
                    e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_EXEEDED,
                            "Number of retries exceeded max " + maxRetrysSame
                            + " retries, while making a call for: " + context.getServer(), e);
                }
            }
            if (listenerInvoker != null) {
                listenerInvoker.onExecutionFailed(e, context.toFinalExecutionInfo());
            }
            return Observable.error(e);
        }
    });
}

com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer

private Observable<Server> selectServer() {
        return Observable.create(new OnSubscribe<Server>() {
            @Override
            public void call(Subscriber<? super Server> next) {
                try {
                    Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                    next.onNext(server);
                    next.onCompleted();
                } catch (Exception e) {
                    next.onError(e);
                }
            }
        });
    }

com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer

public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
        String host = null;
        int port = -1;
        if (original != null) {
            host = original.getHost();
        }
        if (original != null) {
            Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);        
            port = schemeAndPort.second();
        }

        // Various Supported Cases
        // The loadbalancer to use and the instances it has is based on how it was registered
        // In each of these cases, the client might come in using Full Url or Partial URL
        ILoadBalancer lb = getLoadBalancer();
        if (host == null) {
            // Partial URI or no URI Case
            // well we have to just get the right instances from lb - or we fall back
            if (lb != null){
                Server svc = lb.chooseServer(loadBalancerKey);
                if (svc == null){
                    throw new ClientException(ClientException.ErrorType.GENERAL,
                            "Load balancer does not have available server for client: "
                                    + clientName);
                }
                host = svc.getHost();
                if (host == null){
                    throw new ClientException(ClientException.ErrorType.GENERAL,
                            "Invalid Server for :" + svc);
                }
                logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
                return svc;
            }
        // ...
    }

从下面断点可以看出,这里已经进入了 ribbon 的逻辑,通过 ZoneAwareLoadBalancer 来调用负载均衡策略来完成 server 的选择

Spring Cloud Gateway spel 远程代码执行 springcloud远程调用方式_客户端_04

最后在下面的方法完成请求的调用

com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig) 中实现 ServerOperation 匿名对象的 com.netflix.loadbalancer.reactive.ServerOperation#call 方法

最终请求的发起使⽤的是HttpURLConnection

feign.Client.Default#execute

@Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
    }