Spring Cloud Feign组件实例解析

这篇文章主要介绍了Spring Cloud Feign组件实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

采用Spring Cloud微服务框架后,经常会涉及到服务间调用,服务间调用采用了Feign组件。

由于之前有使用dubbo经验。dubbo的负载均衡策略(轮训、最小连接数、随机轮训、加权轮训),dubbo失败策略(快速失败、失败重试等等),

所以Feign负载均衡策略的是什么? 失败后是否会重试,重试策略又是什么?带这个疑问,查了一些资料,最后还是看了下代码。毕竟代码就是一切

Spring boot集成Feign的大概流程:

1、利用FeignAutoConfiguration自动配置。并根据EnableFeignClients 自动注册产生Feign的代理类。

2、注册方式利用FeignClientFactoryBean,熟悉Spring知道FactoryBean 产生bean的工厂,有个重要方法getObject产生FeignClient容器bean

3、同时代理类中使用hystrix做资源隔离,Feign代理类中 构造 RequestTemplate ,RequestTemlate要做的向负载均衡选中的server发送http请求,并进行编码和解码一系列操作。

下面只是粗略的看了下整体流程,先有整体再有细节吧,下面利用IDEA看下细节:

一、Feign失败重试

SynchronousMethodHandler的方法中的处理逻辑:

@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) {
    retryer.continueOrPropagate(e);
    if (logLevel != Logger.Level.NONE) {
     logger.logRetry(metadata.configKey(), logLevel);
    }
    continue;
   }
  }
 }
  • 上面的逻辑很简单。构造 template 并去进行服务间的http调用,然后对返回结果进行解码
  • 当抛出 RetryableException 后,异常逻辑是否重试? 重试多少次? 带这个问题,看了retryer.continueOrPropagate(e);

具体逻辑如下:

public void continueOrPropagate(RetryableException e) {
   if (attempt++ >= maxAttempts) {
    throw e;
   }

   long interval;
   if (e.retryAfter() != null) {
    interval = e.retryAfter().getTime() - currentTimeMillis();
    if (interval > maxPeriod) {
     interval = maxPeriod;
    }
    if (interval < 0) {
     return;
    }
   } else {
    interval = nextMaxInterval();
   }
   try {
    Thread.sleep(interval);
   } catch (InterruptedException ignored) {
    Thread.currentThread().interrupt();
   }
   sleptForMillis += interval;
  }
  • 当重试次数大于默认次数5时候,直接抛出异常,不在重试
  • 否则每隔一段时间 默认值最大1ms 后重试一次。

这就Feign这块的重试这块的粗略逻辑,由于之前工作中一直使用dubbo。同样是否需要将生产环境中重试操作关闭?

思考:之前dubbo生产环境的重试操作都会关闭。原因有几个:

  • 一般第一次失败,重试也会失败,极端情况下不断的重试,会占用大量dubbo连接池,造成连接池被打满,影响核心功能
  • 也是比较重要的一点原因,重试带来的业务逻辑的影响,即如果接口不是幂等的,重试会带来业务逻辑的错误,引发问题

二、Feign负载均衡策略

那么负载均衡的策略又是什么呢?由上图中可知 executeAndDecode(template)

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);
   // ensure the request is set. TODO: remove in Feign 10
   response.toBuilder().request(request).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);

  boolean shouldClose = true;
  try {
   if (logLevel != Logger.Level.NONE) {
    response =
      logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
    // ensure the request is set. TODO: remove in Feign 10
    response.toBuilder().request(request).build();
   }
   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 {
     return decode(response);
    }
   } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
    return decode(response);
   } 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());
   }
  }
 }

概括的说主要做了两件事:发送HTTP请求,解码响应数据

想看的负载均衡应该在11行 response = client.execute(request, options); 而client的实现方式有两种 Default、LoadBalancerFeignClient

猜的话应该是LoadBalancerFeignClient,带这个问题去看源码(其实个人更喜欢带着问题看源码,没有目的一是看很难将复杂的源码关联起来,二是很容易迷失其中)

果然通过一番查找发现 Client 实例就是LoadBalancerFeignClient,而设置这个Client就是通过上面说的FeignClientFactoryBean的getObject方法中设置的,具体不说了

下面重点看LoadBalancerFeignClient execute(request, options)

@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);
      FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
          this.delegate, request, uriWithoutHost);

      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);
    }
  }

通过几行代码比较重要的点RibbonRequest ,原来Feign负载均衡还是通过Ribbon实现的,那么Ribbo又是如何实现负载均衡的呢?

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
    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);
      }
    });
  }

通过上面代码分析,发现Ribbon和Hystrix一样都是利用了rxjava看来有必要掌握下rxjava了又。这里面 比较重要的就是17行,

selectServer() 方法选择指定的Server,负载均衡的策略主要是有ILoadBalancer接口不同实现方式:

  • BaseLoadBalancer采用的规则为RoundRobinRule 轮训规则
  • DynamicServerListLoadBalancer继承了BaseLoadBalancer,主要运行时改变Server列表
  • NoOpLoadBalancer 什么操作都不做
  • ZoneAwareLoadBalancer 功能主要是根据区域Zone分组的实例列表

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • SpringCloud使用Feign实现服务调用

    Spring Cloud Feign简介 Spring Cloud Feign也是一个基础工具类,它整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能以外,它还提供了一种声明式的Web服务客户端定义方式.使用它可以进行服务的消费,但是它的客户端负载平衡仍是通过Ribbon实现的 使用Spring Cloud Feign 创建一个SpringBoot工程,作为服务调用方 1.pom.xml <dependency> <group

  • SpringCloud使用Feign文件上传、下载

    文件上传.下载也是实际项目中会遇到的场景,本篇我们介绍下springcloud中如何使用feign进行文件上传与下载. 还是使用feign 进行http的调用. 一.Feign文件上传 服务提供方java代码: /** * 文件上传 * @param file 文件 * @param fileType * @return */ @RequestMapping(method = RequestMethod.POST, value = "/uploadFile", produces = {

  • SpringCloud Feign 服务调用的实现

    前言 前面我们已经实现了服务的注册与发现(请戳:SpringCloud系列--Eureka 服务注册与发现),并且在注册中心注册了一个服务myspringboot,本文记录多个服务之间使用Feign调用. Feign是一个声明性web服务客户端.它使编写web服务客户机变得更容易,本质上就是一个http,内部进行了封装而已. GitHub地址:https://github.com/OpenFeign/feign 官方文档:https://cloud.spring.io/spring-cloud-

  • 详解Spring-Cloud2.0之Feign调用远程服务指南

    Feign是什么 Feign是简化Java HTTP客户端开发的工具(java-to-httpclient-binder),它的灵感来自于Retrofit.JAXRS-2.0和WebSocket.Feign的初衷是降低统一绑定Denominator到HTTP API的复杂度,不区分是否为restful. 为什么使用Feign 开发人员使用Jersey和CXF等工具可以方便地编写java client,从而提供REST或SOAP服务:开发人员也可以基于Apache HC等http传输工具包编写自己

  • Spring-cloud-eureka使用feign调用服务接口

    Spring-cloud-eureka使用feign调用服务接口的具体方法,供大家参考,具体内容如下 基于spring-boot 2.0以上版本完成的微服务架构 pom.xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE<

  • 详解Spring Cloud Feign 熔断配置的一些小坑

    1.在使用feign做服务调用时,使用继承的方式调用服务,加入Hystrix的熔断处理fallback配置时,会报错,已解决. 2.使用feign默认配置,熔断不生效,已解决. 最近在做微服务的学习,发现在使用feign做服务调用时,使用继承的方式调用服务,加入Hystrix的熔断处理fallback配置时,会报错,代码如下: @RequestMapping("/demo/api") public interface HelloApi { @GetMapping("user/

  • 详解spring cloud feign踩坑记录

    1:多客户端时,feign接口抽取到公共jar中,此时,客户端的启动类上需要对该jar中feign所在的包进行扫描,要在spring和feign中同时注册,否则启动时会报:"Consider defining a bean of type '******Feign' in your configuration." @SpringBootApplication @EnableTransactionManagement @EnableDiscoveryClient @ComponentSc

  • Spring Cloud中关于Feign的常见问题总结

    一.FeignClient接口,不能使用@GettingMapping 之类的组合注解 代码示例: @FeignClient("microservice-provider-user") public interface UserFeignClient { @RequestMapping(value = "/simple/{id}", method = RequestMethod.GET) public User findById(@PathVariable(&quo

  • Spring Cloud Feign组件实例解析

    这篇文章主要介绍了Spring Cloud Feign组件实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 采用Spring Cloud微服务框架后,经常会涉及到服务间调用,服务间调用采用了Feign组件. 由于之前有使用dubbo经验.dubbo的负载均衡策略(轮训.最小连接数.随机轮训.加权轮训),dubbo失败策略(快速失败.失败重试等等), 所以Feign负载均衡策略的是什么? 失败后是否会重试,重试策略又是什么?带这个疑问,查了

  • Spring cloud Feign 深度学习与应用详解

    简介 Spring Cloud Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单.Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数.格式.地址等信息.Feign会完全代理HTTP请求,开发时只需要像调用方法一样调用它就可以完成服务请求及相关处理.开源地址:https://github.com/OpenFeign/feign.Feign整合了Ribbon负载和Hystrix熔断,可以不再需要显式地

  • Spring Cloud Feign高级应用实例详解

    这篇文章主要介绍了Spring Cloud Feign高级应用实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.使用feign进行服务间的调用 Spring boot2X Consul如何使用Feign实现服务调用 2.开启gzip压缩 Feign支持对请求与响应的压缩,以提高通信效率,需要在服务消费者配置文件开启压缩支持和压缩文件的类型 添加配置 feign.compression.request.enabled=true feig

  • Spring Cloud Feign实例讲解学习

    前面博文搭建了一个Eureka+Ribbon+Hystrix的框架,虽然可以基本满足服务之间的调用,但是代码看起来实在丑陋,每次客户端都要写一个restTemplate,为了让调用更美观,可读性更强,现在我们开始学习使用Feign. Feign包含了Ribbon和Hystrix,这个在实战中才慢慢体会到它的意义,所谓的包含并不是Feign的jar包包含有Ribbon和Hystrix的jar包这种物理上的包含,而是Feign的功能包含了其他两者的功能这种逻辑上的包含.简言之:Feign能干Ribb

  • 使用Spring Cloud Feign上传文件的示例

    最近经常有人问Spring Cloud Feign如何上传文件.有团队的新成员,也有其他公司的兄弟.本文简单做个总结-- 早期的Spring Cloud中,Feign本身是没有上传文件的能力的(1年之前),要想实现这一点,需要自己去编写Encoder 去实现上传.现在我们幸福了很多.因为Feign官方提供了子项目feign-form ,其中实现了上传所需的 Encoder . 注:笔者测试的版本是Edgware.RELEASE.Camden.Dalston同样适应本文所述. 加依赖 <depen

  • Spring Cloud Feign原理详解

    目录 Feign的大体机制 @EnableFeignClients 和 @FeignClient 注解 registerDefaultConfiguration方法 registerFeignClients方法 feign客户端的动态代理 Feign 主要是帮助我们方便进行rest api服务间的调用,其大体实现思路就我们通过标记注解在一个接口类上(注解上将包含要调用的接口信息),之后在调用时根据注解信息组装好请求信息,接下来基于ribbon这些负载均衡器来生成真实的服务地址,最后将请求发送出去

  • 使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务的方法(推荐)

    在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端.我们可以使用JDK原生的URLConnection.Apache的Http Client.Netty的异步HTTP Client, Spring的RestTemplate.但是,用起来最方便.最优雅的还是要属Feign了. Feign简介 Feign是一种声明式.模板化的HTTP客户端.在Spring Cloud中使用Feign, 我们可以做到使用HTT

  • Spring Cloud Feign 自定义配置(重试、拦截与错误码处理) 代码实践

    基于 spring-boot-starter-parent 2.1.9.RELEASE, spring-cloud-openfeign 2.1.3.RELEASE 引子 Feign 是一个声明式.模板化的HTTP客户端,简化了系统发起Http请求.创建它时,只需要创建一个接口,然后加上FeignClient注解,使用它时,就像调用本地方法一样,作为开发者的我们完全感知不到这是在调用远程的方法,也感知不到背后发起了HTTP请求: /** * @author axin * @suammry xx 客

  • 解决Spring Cloud Feign 请求时附带请求头的问题

    问题描述 Feign 在请求时是不会将 request 的请求头带着请求的,导致假如 Feign 调用的接口需要请求头的信息,比如当前用户的 token 之类的就获取不到 解决方案 FeignConfiguration 通过实现 Feign 的 RequestInterceptor 将从上下文中获取到的请求头信息循环设置到 Feign 请求头中. /** * feign 配置文件 * 将请求头中的参数,全部作为 feign 请求头参数传递 * @author: linjinp * @create

随机推荐