在前面的 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);
}
注意:
- @FeignClient注解的name属性⽤于指定要调⽤的服务提供者名称,和服务提供者yml⽂件中spring.application.name保持⼀致
- 接⼝中的接⼝⽅法,就好⽐是远程服务提供者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了)
注意:
- 开启 Hystrix 之后,Feign 中的⽅法都会被进⾏⼀个管理了,⼀旦出现问题就进⼊对应的回退逻辑处理
- 针对超时这⼀点,当前有两个超时时间设置(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 ⽅法
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 动态代理对象
请求进来时候,是进⼊增强逻辑的,所以接下来我们要关注增强逻辑部分:feign.ReflectiveFeign.FeignInvocationHandler#invoke
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 的选择
最后在下面的方法完成请求的调用
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);
}
s