上篇文章我讲了openfeign的超时和重试。首先我想发2个勘误

1.下面的2个配置对单个接口超时并没有起作用,作为eureka客户端使用时,起作用的其实是默认超时时间,作为普通http客户端时,起作用的其实也是默认超时时间。

hystrix.command.FeignAsHttpCient#feignReadTimeout().execution.isolation.thread.timeoutInMilliseconds=13000hystrix.command.FeignAsEurekaClient#feignReadTimeout().execution.isolation.thread.timeoutInMilliseconds=23000

2.openfeign作为普通客户端,其实是可以重试的。

看了本文的源码解读,就可以搞明白上面的2个问题了。

Feignclient注册

服务启动时,feignclient需要注册为spring的bean,具体实现代码在FeignClientsRegistrar,这个类实现了ImportBeanDefinitionRegistrar,spring初始化容器的时候会扫描实现这个接口的方法,进行bean注册。

接口定义的方法是registerBeanDefinitions,FeignClientsRegistrar的实现如下:

public void registerBeanDefinitions(AnnotationMetadata metadata,    BeanDefinitionRegistry registry) {  registerDefaultConfiguration(metadata, registry);  registerFeignClients(metadata, registry);}private void registerDefaultConfiguration(AnnotationMetadata metadata,    BeanDefinitionRegistry registry) {  Map 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();//name="default.boot.Application"    }    registerClientConfiguration(registry, name,        defaultAttrs.get("defaultConfiguration"));  }}

下面这个方法看过spring代码的就熟悉了,一个bean的注册:

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,    Object configuration) {  BeanDefinitionBuilder builder = BeanDefinitionBuilder      .genericBeanDefinition(FeignClientSpecification.class);  builder.addConstructorArgValue(name);  builder.addConstructorArgValue(configuration);  registry.registerBeanDefinition(  //这里name="default.boot.Application.FeignClientSpecification",  //bean="org.springframework.cloud.openfeign.FeignClientSpecification"      name + "." + FeignClientSpecification.class.getSimpleName(),      builder.getBeanDefinition());}

下面的代码是注册Feign客户端:

public void registerFeignClients(AnnotationMetadata metadata,    BeanDefinitionRegistry registry) {  ClassPathScanningCandidateComponentProvider scanner = getScanner();  scanner.setResourceLoader(this.resourceLoader);  //配置要扫描的basePackage,这里是"boot"(@SpringBootApplication(scanBasePackages = {"boot"}))  for (String basePackage : basePackages) {    Set 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");                //找出注解是FeignClient的attributes,注册到spring容器        Map<String, Object> attributes = annotationMetadata            .getAnnotationAttributes(                FeignClient.class.getCanonicalName());        String name = getClientName(attributes);//这里的name是springboot-mybatis        registerClientConfiguration(registry, name,            attributes.get("configuration"));                //这个方法就不讲了,封装BeanDefinition,注册到spring容器        registerFeignClient(registry, annotationMetadata, attributes);      }    }  }}

FeignClient初始化

Feign客户端的初始化在FeignClientFactoryBean类,这个类实现了FactoryBean接口,在getObject,这里的uml类图如下:

getObject方法的代码如下:

 T getTarget() {  FeignContext context = applicationContext.getBean(FeignContext.class);  Feign.Builder builder = feign(context);    //如果feignClient没有指定url,就走这个分支,这里会通过ribbon走负载均衡  if (!StringUtils.hasText(this.url)) {    if (!this.name.startsWith("http")) {      url = "http://" + this.name;    }    else {      url = this.name;    }    //http://springboot-mybatis    url += cleanPath();    return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,        this.name, url));  }  //feignClient指定了url,走到这儿  if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {    this.url = "http://" + this.url;  }  //url = "http://localhost:8083"  String url = this.url + cleanPath();  //LoadBalancerFeignClient  Client client = getOptional(context, Client.class);  if (client != null) {    if (client instanceof LoadBalancerFeignClient) {      // not load balancing because we have a url,      // but ribbon is on the classpath, so unwrap      //OKHttpClient      client = ((LoadBalancerFeignClient)client).getDelegate();    }    builder.client(client);  }  //这里是HystrixTargeter,不知道为什么总是不用DefaultTargeter  Targeter targeter = get(context, Targeter.class);  return (T) targeter.target(this, builder, context, new HardCodedTarget<>(      this.type, this.name, url));}

我们先来看一下FeignClient不指定url的情况,代码如下:

protected  T loadBalance(Feign.Builder builder, FeignContext context,    HardCodedTarget target) {  //这里的client是LoadBalancerFeignClient  Client client = getOptional(context, Client.class);  if (client != null) {    builder.client(client);    //这里的targeter是HystrixTargeter    Targeter targeter = get(context, Targeter.class);    return targeter.target(this, builder, context, target);  }  throw new IllegalStateException(      "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");}

再看看HystrixTargeter中的target

public  T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,          Target.HardCodedTarget target) {  if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {    return feign.target(target);  }  feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;  SetterFactory setterFactory = getOptional(factory.getName(), context,    SetterFactory.class);  if (setterFactory != null) {    builder.setterFactory(setterFactory);  }  Class> fallback = factory.getFallback();  if (fallback != void.class) {    return targetWithFallback(factory.getName(), context, target, builder, fallback);  }  Class> fallbackFactory = factory.getFallbackFactory();  if (fallbackFactory != void.class) {    return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);  }    //这里返回的是一个HardCodedTarget的代理,HardCodedTarget(type=FeignAsEurekaClient, name=springboot-mybatis, url=http://springboot-mybatis)  //FeignAsEurekaClient就是我demo中的feign客户端类,可以看出,这里是为FeignAsEurekaClient做了一个代理  return feign.target(target);}

上面targe返回的对象debug内容如下:

proxy = {$Proxy168@11372} "HardCodedTarget(type=FeignAsEurekaClient, name=springboot-mybatis, url=http://springboot-mybatis)"h = {HystrixInvocationHandler@11366} "HardCodedTarget(type=FeignAsEurekaClient, name=springboot-mybatis, url=http://springboot-mybatis)" target = {Target$HardCodedTarget@11142} "HardCodedTarget(type=FeignAsEurekaClient, name=springboot-mybatis, url=http://springboot-mybatis)"  type = {Class@9295} "interface boot.feign.FeignAsEurekaClient"  name = "springboot-mybatis"  url = "http://springboot-mybatis" dispatch = {LinkedHashMap@11346}  size = 5  {Method@11392} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.getEmployeebyName(java.lang.String)" -> {SynchronousMethodHandler@11431}   {Method@11393} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.saveEmployeebyName(boot.feign.Employee)" -> {SynchronousMethodHandler@11432}   {Method@11394} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.feignReadTimeout()" -> {SynchronousMethodHandler@11433}   {Method@11395} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.uploadFile(org.springframework.web.multipart.MultipartFile)" -> {SynchronousMethodHandler@11434}   {Method@11396} "public abstract feign.Response boot.feign.FeignAsEurekaClient.downloadFile(java.lang.String)" -> {SynchronousMethodHandler@11435}  fallbackFactory = null fallbackMethodMap = {LinkedHashMap@11382}  size = 5  {Method@11392} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.getEmployeebyName(java.lang.String)" -> {Method@11392} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.getEmployeebyName(java.lang.String)"  {Method@11393} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.saveEmployeebyName(boot.feign.Employee)" -> {Method@11393} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.saveEmployeebyName(boot.feign.Employee)"  {Method@11394} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.feignReadTimeout()" -> {Method@11394} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.feignReadTimeout()"  {Method@11395} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.uploadFile(org.springframework.web.multipart.MultipartFile)" -> {Method@11395} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.uploadFile(org.springframework.web.multipart.MultipartFile)"  {Method@11396} "public abstract feign.Response boot.feign.FeignAsEurekaClient.downloadFile(java.lang.String)" -> {Method@11396} "public abstract feign.Response boot.feign.FeignAsEurekaClient.downloadFile(java.lang.String)" setterMethodMap = {LinkedHashMap@11383}  size = 5  {Method@11392} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.getEmployeebyName(java.lang.String)" -> {HystrixCommand$Setter@11414}   {Method@11393} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.saveEmployeebyName(boot.feign.Employee)" -> {HystrixCommand$Setter@11415}   {Method@11394} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.feignReadTimeout()" -> {HystrixCommand$Setter@11416}   {Method@11395} "public abstract java.lang.String boot.feign.FeignAsEurekaClient.uploadFile(org.springframework.web.multipart.MultipartFile)" -> {HystrixCommand$Setter@11417}   {Method@11396} "public abstract feign.Response boot.feign.FeignAsEurekaClient.downloadFile(java.lang.String)" -> {HystrixCommand$Setter@11418}

我们再来看一下FeignClient指定url的情况,这种情况跟不指定url类似,只是代理的类中有url值。debug发现使用的代理也是HardCodedTarget,代码如下:

proxy = {$Proxy161@11205} "HardCodedTarget(type=FeignAsHttpCient, name=feign, url=http://localhost:8083)" h = {ReflectiveFeign$FeignInvocationHandler@11201} "HardCodedTarget(type=FeignAsHttpCient, name=feign, url=http://localhost:8083)"  target = {Target$HardCodedTarget@11192} "HardCodedTarget(type=FeignAsHttpCient, name=feign, url=http://localhost:8083)"   type = {Class@9298} "interface boot.feign.FeignAsHttpCient"   name = "feign"   url = "http://localhost:8083"  dispatch = {LinkedHashMap@11194}  size = 1   {Method@11221} "public abstract java.lang.String boot.feign.FeignAsHttpCient.feignReadTimeout()" -> {SynchronousMethodHandler@11222}

从上面的代码分析中,我们看出,这2种方式的主要不同是,如果不指定url,则给Feign传入的是LoadBalancerFeignClient,它是一个装饰器,里面的delegate指定了实际的client,这里是OkHttpClient。而如果指定了url,给Feign传入的就是实际的httpclient,这里是OKHttpClient。

上面使用了代理,这里的UML类图如下:

通过这张图,我们可以看到代理是怎么最终走到OkHttpClient的。如果使用了熔断,则使用HystrixInvocationHandler,否则使用FeignInvocationHandler,他们的invoke方法最终都调用了SynchronousMethodHandler的invoke,这里最终调用了底层的OkHttpClient。

指定url

上面的类图看出,SynchronousMethodHandler这个类的invoke方法是上面的代理中反射触发的方法,我们来看一下:

public Object invoke(Object[] argv) throws Throwable {  //RequestTemplate封装RequestTemplate  RequestTemplate template = buildTemplateFromArgs.create(argv);  Retryer retryer = this.retryer.clone();  while (true) {    try {      return executeAndDecode(template);    } catch (RetryableException e) {    //这里可以看出,无论是不是指定url,都会走重试的逻辑,默认重试是不生效的      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;    }  }}Object executeAndDecode(RequestTemplate template) throws Throwable {    Request request = targetRequest(template);    Response response;    long start = System.nanoTime();    try {    //这里调用OkHttpClient,这个并不是原生的那个OkHttpClient,而是Feign封装的,看下面的讲解      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 (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());      }    }  }

前面我们讲过,如果指定了url,就不走ribbon的LoadBalance了,而是直接用httpclient去发送请求。其实说"直接",也不完全是直接,因为feign封装了一个自己的OkHttpClient,并且有自己的Request,Response。

OkHttpClient这个装饰器类首先包含了一个okhttp3.OkHttpClient的客户端,发送请求的时候,首先把feign.Request转换成okhttp的Request,而接收响应的时候,会把okhttp的Response转换成feign.Response,代码如下:

feign.Request转换成okhttp的Request

static Request toOkHttpRequest(feign.Request input) {  Request.Builder requestBuilder = new Request.Builder();  requestBuilder.url(input.url());//封装url  MediaType mediaType = null;  boolean hasAcceptHeader = false;  //封装headers  for (String field : input.headers().keySet()) {    if (field.equalsIgnoreCase("Accept")) {      hasAcceptHeader = true;    }    for (String value : input.headers().get(field)) {      requestBuilder.addHeader(field, value);      if (field.equalsIgnoreCase("Content-Type")) {        mediaType = MediaType.parse(value);        if (input.charset() != null) {          mediaType.charset(input.charset());        }      }    }  }  // Some servers choke on the default accept string.  if (!hasAcceptHeader) {    requestBuilder.addHeader("Accept", "*/*");  }  byte[] inputBody = input.body();  boolean isMethodWithBody =      HttpMethod.POST == input.httpMethod() || HttpMethod.PUT == input.httpMethod()          || HttpMethod.PATCH == input.httpMethod();  if (isMethodWithBody) {    requestBuilder.removeHeader("Content-Type");    if (inputBody == null) {      // write an empty BODY to conform with okhttp 2.4.0+      // http://johnfeng.github.io/blog/2015/06/30/okhttp-updates-post-wouldnt-be-allowed-to-have-null-body/      inputBody = new byte[0];    }  }  //封装body  RequestBody body = inputBody != null ? RequestBody.create(mediaType, inputBody) : null;  requestBuilder.method(input.httpMethod().name(), body);  return requestBuilder.build();}

把okhttp的Response转换成feign.Response

private static feign.Response toFeignResponse(Response response, feign.Request request)    throws IOException {  return feign.Response.builder()      .status(response.code())      .reason(response.message())      .request(request)      .headers(toMap(response.headers()))      .body(toBody(response.body()))      .build();}

发送请求的方法

public feign.Response execute(feign.Request input, feign.Request.Options options)    throws IOException {  okhttp3.OkHttpClient requestScoped;  //这里delegate的connectTimeoutMillis默认是2000,delegate的readTimeoutMillis默认是100000  //从代码可以看到,如果配置了options的超时时间跟不一样,会被替换掉  /**   *比如下面的时间设置就会替换掉默认时间   *feign.client.config.default.connectTimeout=3000   *feign.client.config.default.readTimeout=13000         *   *网上说的对单个接口设置超时时间,下面这个超时时间是不生效的,从源码中我们也能看到了   *hystrix.command.FeignAsHttpCient#feignReadTimeout().execution.isolation.thread.timeoutInMilliseconds=13000   *   *也可以看出,要想自定义超时,最好的方法就是给Request定制Options   *   */     if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()      || delegate.readTimeoutMillis() != options.readTimeoutMillis()) {    requestScoped = delegate.newBuilder()        .connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS)        .readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS)        .followRedirects(options.isFollowRedirects())        .build();  } else {    requestScoped = delegate;  }  Request request = toOkHttpRequest(input);  Response response = requestScoped.newCall(request).execute();  return toFeignResponse(response, input).toBuilder().request(input).build();}

到这里,我们就讲完了指定url的FeignClient请求流程,相信你对超时和重试也有了一定的认识。

不指定url

上一节的UML类图我们可以看出,无论是否指定url,最终都是要从SynchronousMethodHandler类的executeAndDecode方法调用HttpClient。不指定url的情况下,使用的client是LoadBalancerFeignClient。我们看一下他的execute方法:

public Response execute(Request request, Request.Options options) throws IOException {  try {      //asUri="http://springboot-mybatis/feign/feignReadTimeout"    URI asUri = URI.create(request.url());    String clientName = asUri.getHost();//springboot-mybatis    URI uriWithoutHost = cleanUrl(request.url(), clientName);//http:///feign/feignReadTimeout    //下面封装了OkHttpClient,默认连接超时是2s,读超时是10s    FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(        this.delegate, request, uriWithoutHost);        //这里如果配置了feign相关的配置,就是我们配置的,否则就是默认的DEFAULT_OPTIONS    IClientConfig requestConfig = getClientConfig(options, clientName);    return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,        requestConfig).toResponse();  }  catch (ClientException e) {    IOException io = findIOException(e);    if (io != null) {      throw io;    }    throw new RuntimeException(e);  }}

上面的executeWithLoadBalancer调用了AbstractLoadBalancerAwareClient的executeWithLoadBalancer方法,代码如下:

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {    LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig);    try {        return command.submit(            new ServerOperation() {                @Override                public Observablecall(Server server) {            //这里的finalUri是http://192.168.0.118:8083/feign/feignReadTimeout                    URI finalUri = reconstructURIWithServer(server, request.getUri());//这个就是一个拼接url的方法,不细讲了          //下面的requestForServer是FeignLoadBalancer,看上面的UML类图,是AbstractLoadBalancerAwareClient的子类                    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) {        Throwable t = e.getCause();        if (t instanceof ClientException) {            throw (ClientException) t;        } else {            throw new ClientException(e);        }    }   }

上面的execute方法执行的是FeignLoadBalancer里面的execute方法,代码如下:

public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)    throws IOException {  Request.Options options;  if (configOverride != null) {      /**     * 下面就是我们配置的超时时间,在这里被替换到了Request的Options中,XXX是default或者是服务名     * feign.client.config.XXX.connectTimeout=3000         * feign.client.config.XXX.readTimeout=7000     */    RibbonProperties override = RibbonProperties.from(configOverride);    options = new Request.Options(        override.connectTimeout(this.connectTimeout),        override.readTimeout(this.readTimeout));  }  else {    options = new Request.Options(this.connectTimeout, this.readTimeout);  }  //这个request里面的client就是OkHttpClient  Response response = request.client().execute(request.toRequest(), options);  return new RibbonResponse(request.getUri(), response);}

后面的逻辑就是feign.okhttp.OkHttpClient的execute方法了,跟上节介绍的一样,这里不再赘述了。

可以看出,不指定url的情况,会使用ribbon做负载均衡,并对feign的Request和Response进行了一层封装,封装类是RibbonRequest和RibbonResponse。

ribbon负载

顺带讲一下ribbon的负载吧。上面的讲解中提到了AbstractLoadBalancerAwareClient的executeWithLoadBalancer方法,我们再贴一次代码:

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {    LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig);    try {        return command.submit(            new ServerOperation() {                @Override                public Observablecall(Server server) {            //这里的finalUri是http://192.168.0.118:8083/feign/feignReadTimeout                    URI finalUri = reconstructURIWithServer(server, request.getUri());//这个就是一个拼接url的方法,不细讲了          //下面的requestForServer是FeignLoadBalancer,看上面的UML类图,是AbstractLoadBalancerAwareClient的子类                    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) {        Throwable t = e.getCause();        if (t instanceof ClientException) {            throw (ClientException) t;        } else {            throw new ClientException(e);        }    }   }

我们看一下上面的command.submit方法,这个方法调用了LoadBalancerCommand的submit方法,代码如下:

public Observablesubmit(final ServerOperation operation) {    final ExecutionInfoContext context = new ExecutionInfoContext();    /**   * 下面的配置就是当前server请求失败后再重试一次,如果还失败,就请求下一个server,如过还了3个server都失败,就返回错误了   *# 对当前server的重试次数,默认是0     *ribbon.maxAutoRetries=1     *# 切换实例的重试次数,默认是0     *ribbon.maxAutoRetriesNextServer=3     *# 对所有操作请求都进行重试,这里建议不要设置成true,否则会对所有操作请求都进行重试     *ribbon.okToRetryOnAllOperations=true     *# 根据Http响应码进行重试     *ribbon.retryableStatusCodes=500,404,502    **/    final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();    final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();    // Use the load balancer    Observable o =             (server == null ? selectServer() : Observable.just(server))            .concatMap(new Func1>() {                //省略部分代码            });    //如果没有获取到,那就重试    if (maxRetrysNext > 0 && server == null)         o = o.retry(retryPolicy(maxRetrysNext, false));        return o.onErrorResumeNext(new Func1>() {        //省略部分代码    });}

我们看一下selectServer方法:

private ObservableselectServer() {    return Observable.create(new OnSubscribe() {        @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);            }        }    });}

继续跟踪,我们来看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);            //省略代码            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;        } else {//省略代码        }    } else {//省略代码    }    // end of creating final URL    if (host == null){        throw new ClientException(ClientException.ErrorType.GENERAL,"Request contains no HOST to talk to");    }    // just verify that at this point we have a full URL    return new Server(host, port);}

简单看一下上面这个ILoadBalancer,这里是一个ZoneAwareLoadBalancer,里面保存的服务的server列表和状态:

lb = {ZoneAwareLoadBalancer@14492} "DynamicServerListLoadBalancer:{NFLoadBalancer:name=springboot-mybatis,current list of Servers=[192.168.0.118:8083],Load balancer stats=Zone stats: ]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@43db1f5d" balancers = {ConcurrentHashMap@17695}  size = 1  "defaultzone" -> {BaseLoadBalancer@17904} "{NFLoadBalancer:name=springboot-mybatis_defaultzone,current list of Servers=[192.168.0.118:8083],Load balancer stats=Zone stats: {]\n]}"

通过这个负载均衡器,feign就可以获取到一个server地址,然后把请求发送出去。

总结

openfeign作为eureka客户端和普通http客户端,有所不同。作为eureka客户端时,不用指定url,使用ribbon封装了请求和响应,并且通过ribbon作为负载均衡。

openfeign作为eureka客户端和普通http客户端,都是可以重试的。因为都是通过SynchronousMethodHandler这个类invoke来触发的,失败了都会捕获RetryableException。但是要知道,默认配置是不支持重试的。

openfeign作为eureka客户端和普通http客户端,对单个接口设置超时时间,都是不生效的,实际上还是使用了默认的超时时间。

openfeign 负载均衡_再谈openfeign,聊聊它的源代码相关推荐

  1. f5 会话保持 负载均衡_四层负载均衡和七层负载均衡区别在哪里?

    年后至今这段时间工作重心都在调整公司现有API Gateway的系统架构以及对现有技术栈选型.经过对主流互联网网关所实现各种方案的调研,我们在API Gateway前置一层接入层,接入层主要用于实现限 ...

  2. was这么做的负载均衡_中间件(WAS、WMQ)运维 9个常见难点解析

    原标题:中间件(WAS.WMQ)运维 9个常见难点解析 本文由社区中间件达人wangxuefeng266.ayy216226分享整理,包括WAS.WMQ在安装.巡检.监控.优化过程中的常见难点. 安装 ...

  3. mysql分库负载均衡_订单模块以及负载均衡和分库分表

    第202次(订单模块以及负载均衡和分库分表) 学习主题:订单模块以及负载均衡和分库分表 1. ego-rpc_ego-manager项目发布 (1) 描述ego-rpc项目发布需要什么插件,做哪些配置 ...

  4. 高可用与负载均衡(7)之聊聊Lvs-DR+Keepalived的解决方案

    今天直接开门见山了,直接说配置吧.首先介绍下我这的环境 如有问题,请联系我18500777133@sina.cn IP 安装软件 192.168.1.7 lvs1+keepalived master角 ...

  5. 压力测试过负载均衡_性能测试的方法有哪些?

    压力测试: 压力测试的关键字就是"极端".通过对系统的极端加压,从而观察系统的所表现出来性能问题.再对此性能问题进行分析,从而达到系统优化的目的.所以压力测试就是一定要让系统出问题 ...

  6. nginx工作笔记005---nginx配置负载均衡_在微服务中实现网关集群_实现TCP传输层协议__http协议的负载均衡

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 我们在微服务中,由于网关的存在,后来,在nginx中都不需要在配置其他服务的地址了,只需要,配置一 ...

  7. weblogic集群部署与负载均衡_集群,负载均衡,分布式的讲解和多台服务器代码同步...

    集群 我们的项目如果跑在一台机器上,如果这台机器出现故障的话,或者用户请求量比较高,一台机器支撑不住的话.我们的网站可能就访问不了.那怎么解决呢?就需要使用多台机器,部署一样的程序,让几个机器同时的运 ...

  8. websphere负载均衡_使用WebSphere DataPower Appliances保护JSON有效负载

    websphere负载均衡 在采用轻量级移动设备的推动下,基于REST的通信的采用日益广泛,因此需要保护这些通信. 当消息包含信息有效负载时,尤其如此,通常将其格式化为JavaScript Objec ...

  9. haproxy负载均衡_做负载均衡Nginx、HAProxy和LVS总有一个适合你

    Nginx Nginx优点: 1.工作在网络7层之上,可针对http应用做一些分流的策略,如针对域名.目录结构,它的正规规则比HAProxy更为强大和灵活,所以,目前为止广泛流行. 2.Nginx对网 ...

最新文章

  1. 数字化转型里面的这些名词都是什么关系呢?
  2. 《TCP/IP具体解释》读书笔记(18章)-TCP连接的建立与中止
  3. python tkinter教程 博客园_python tkinter教程-事件绑定
  4. 服务器系统巡检记录表,服务器月度巡检记录
  5. jmeter mysql查询结果提取_Jmeter-从数据库中获取数据并作为变量使用
  6. python 释放内存_学了4年C++后,我转向了Python
  7. 1.3 编程基础之算术表达式与顺序执行 04 带余除法
  8. Mybatis(18)注解实现表名和类属性名不匹配问题
  9. MVVM绑定多层级数据到TreeView并设置项目展开
  10. 编程恶搞项目——无限弹窗.exe版
  11. java web 实战开发经典_java web 开发实战经典(一)
  12. Android P如何去掉电池图标和固定电量显示
  13. Quartus .sof转换成.jic
  14. java写ansi_java实现utf8转换ansi
  15. 苹果手机10秒解除锁屏_忘记苹果锁屏密码10秒解决 音量键选择wipedata/
  16. 计算机人才供需状况和就业形势分析,计算机科学与技术就业形势分析
  17. 彻底删除微信聊天记录,手机中的小秘密不再泄露!
  18. C语言实现写入注册表,简单的开机自启动
  19. spring 不同注解的使用场景
  20. 网页设计与制作教程 项目1

热门文章

  1. Go进阶(7): JSON 序列化和反序列化
  2. 新型机器学习算法:正则化理解
  3. Qt修炼手册3_VS建立的Qt工程(或项目)生成pro文件
  4. vs2010 失效后的解决办法
  5. 在VS2010调试javascript程序时的各种问题
  6. 中国蚁剑的下载、安装与使用
  7. UDP socket编程中使用connect
  8. 在 C 中引用汇编语言定义的 .globl 变量
  9. Linux 0.00 Makefile 说明
  10. Java—正整数分解成质因数