跳至主要內容

OpenFeign 2020 最新教程

Zenghr大约 7 分钟SpringSpringCloud

OpenFeign 2020 最新教程

OpenFeign是声明式的Http客户端,通过OpenFeign发送Http请求非常的简单

  • 注解式开发,接口+注解的方式
  • OpenFeign支持多种的对象的序列化 和 反序列化的工具
  • OpenFeign 默认集成了Ribbon(2020 版本弃用),可以直接进行负载均衡

Feign 和 OpenFeign 的区别

Feign 和 OpenFeign是两个技术,都是作为服务调用存在的,OpenFeign 是SpringCloud在Feign的基础上进行封装得到的,支持SpringMvc的注解

2020 版本说明以及坑

由于 SpringCloud 2020 弃用了 Ribbon,所以如果你使用的是 2020 版本,且使用了 OpenFeign 组件,那么在启动时会报错

No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?

官方建议我们使用 Spring Cloud LoadBalanceropen in new window 来代替 Ribbon

所以导入 Spring Cloud LoadBalancer 的依赖就解决了

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

我写了一篇文章专门记录 SpringCloud 相应的报错或者填坑 → Spring Cloud 趟坑记录open in new window

演示环境说明

框架版本
OpenFeign3.0.3
Loadbalancer3.0.3
Spring Cloud2020.0.3
Spring Cloud Alibaba2021.1

我们使用 nacos + OpenFeign 来模拟微服务之间的远程调用场景

首先创建 订单服务(order-server)商品服务(product-server) 将这两个服务注册到 nacos 中,其中商品服务部署多个,方便后面演示 负载均衡的操作

提示

nacos 相关的一些操作这里不详细赘述,请看上一篇有关 nacos 的文章➡️ SpringCloud Alibaba Nacos 教程open in new window

4tTurU
4tTurU

父项目 pom 文件

<!--依赖版本的锁定-->
<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-cloud.version>2020.0.3</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

订单、商品的 pom 文件

<!--nacos discovery-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--open feign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--loadbalancer-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

启动类

在启动类上添加 OpenFegin 的扫描注解,注意扫描路径(默认扫描当前包及其子包)

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServer {
    public static void main(String[] args) {
        SpringApplication.run(OrderServer.class,args);
    }
}

配置文件

server:
  port: 8091
# 指定当前服务的名称,会注册到注册中心
spring:
  application:
    name: order-server
  # nacos 注册中心
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

通过以上步骤我们就拥有了一个最初步的项目,接下来,我们会通过 接口+注解 的方式开发 OpenFeign 的服务调用

OpenFeign 接口的开发

我们在 订单服务中 新增接口 ProductServer ,使用 @FeignClient 注解标注该类是 OpenFeign 远程调用的类

@FeignClient 的 name 参数就是你要调用的服务名称

接口中的方法就是调用服务的 Controller 接口,注意: 接口的地址以及参数要和调用服务的 controller 一致

// 参数是要请求服务的服务名称
@FeignClient(name = "product-server")
public interface ProductServer {
    @RequestMapping("/product")
    Product findByPid(@RequestParam("pid") Long pid);
}

商品服务中的 Controller

@Slf4j
@RestController
public class ProductController {
    @Autowired
    private IProductService productService;
    
    @RequestMapping("/product")
    public Product findByPid(@RequestParam("pid") Long pid) {
        Product product = productService.findByPid(pid);
        product.setPname(product.getPname() + " from: " + port);
        return product;
    }
}

调用OpenFeign 的接口

@Slf4j
@RestController
public class OrderController {
    @Autowired
    private ProductServer productServer;

    @RequestMapping("/get")
    public Product findProduct(Long pid) {
        return productServer.findByPid(pid);
    }

}

启动测试

运行启动类之后,在浏览器或者 PostMan 之类的工具访问 http://localhost:8091/get?pid=1open in new window

// http://localhost:8091/get?pid=1
{
  "pid": 1,
  "pname": "小米 from: 8081",
  "pprice": 1000.0,
  "stock": 5000
}
{
  "pid": 1,
  "pname": "小米 from: 8082",
  "pprice": 1000.0,
  "stock": 5000
}

我们会发现多刷新几次,出现的端口不一致,这就是 OpenFeign 的负载均衡导致的,OpenFeign 默认的负载均衡策略是 轮询策略

负载均衡

负载均衡,英文名称为Load Balance,其含义就是指将工作任务(负载)进行平衡、分摊到多个操作单元上进行运行,从而协同完成工作任务

Ribbon(已弃用)

Spring Cloud Ribbon 是一个基于 HTTPTCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用

Ribbon 已弃用

2016年以来,Ribbon就进入维护状态,官方建议使用Spring Cloud LoadBalancer代替Ribbon

Client Side Load Balancing with Ribbon and Spring Cloudopen in new window 中提到Spring Cloud Netflix Ribbon is now deprecated,建议使用 Spring Cloud LoadBalanceropen in new window

Spring Cloud LoadBalancer

官方文档: Client-Side Load-Balancing with Spring Cloud LoadBalanceropen in new window

Spring Cloud LoadBalance r是一个客户端负载均衡器,类似于Ribbon,但是由于Ribbon已经进入维护模式,并且Ribbon 2并不与Ribbon 1相互兼容

所以Spring Cloud全家桶在Spring Cloud Commons项目中,添加了 Spring cloud Loadbalancer 作为新的负载均衡器,并且做了向前兼容,就算你的项目中继续用 Spring Cloud Netflix 套装(包括Ribbon,Eureka,Zuul,Hystrix等等)让你的项目中有这些依赖,你也可以通过简单的配置,把 ribbon 替换成 Spring Cloud LoadBalancer

负载均衡器在哪里使用?

Spring Cloud 中内部微服务调用默认是 http 请求,主要通过下面三种 API:

  • RestTemplate:同步 http API
  • WebClient:异步响应式 http API
  • 三方客户端封装,例如 openfeign

如果项目中加入了 spring-cloud-loadbalancer 的依赖并且配置启用了,那么会自动在相关的 Bean 中加入负载均衡器的特性。

  • 对于 RestTemplate,会自动对所有 @LoadBalanced 注解修饰的 RestTemplate Bean 增加 Interceptor 从而加上了负载均衡器的特性。
  • 对于 WebClient,会自动创建 ReactorLoadBalancerExchangeFilterFunction,我们可以通过加入ReactorLoadBalancerExchangeFilterFunction会加入负载均衡器的特性。
  • 对于三方客户端,一般不需要我们额外配置什么

OpenFeign 负载均衡策略

OpenFeign 默认的策略是轮询策略,如果我们想要使用随机策略,根据官方的例子

编写配置类

@Configuration
public class RandomLoadbalancerConfig {
    @Bean
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                                                   LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

使用 @LoadBalancerClient

在启动类,使用 @LoadBalancerClient 或者 @LoadBalancerClients 注解,加载自己的配置类,由此切换 loadBalancer 默认的负载均衡策略

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@LoadBalancerClient(value = "product-server", configuration = RandomLoadbalancerConfig.class)
public class OrderServer {
    public static void main(String[] args) {
        SpringApplication.run(OrderServer.class, args);
    }
}

最后达到随机负载均衡的效果

@FeignClient注解

  • name:执行FeignClient的名称,如果项目中使用Ribbon,name属性会作为微服务的名称,用作服务发现。
  • url:url一般用于调试,可以手动指定 @FeignClient 调用的地址
  • decode404:当发生404错误时,如果该字段为true,会调用decoder进行解码,否则抛出 FeignException。
  • configuration:Feigin配置类,可自定义Feign的Encode,Decode,LogLevel,Contract。
  • fallback:定义容错的类,当远程调用的接口失败或者超时的时候,会调用对应接口的容错罗杰,fallback执行的类必须实现@FeignClient标记的接口。在OpenFeign的依赖中可以发现,集成Hystrix。
  • fallbackFactory:工厂类,用于生成fallback类实例,通过此属性可以实现每个接口通用的容错逻辑,以达到减少重复的代码。
  • path:定义当前FeignClient的统一前缀

OpenFeign 开始GZIP压缩

OpenFeign支持对请求和响应进行GZIP压缩,以此来提供通信效率。只需在配置文件中配置即可,比较简单

feign:
  # 压缩配置
  compression:
    request:
      enabled: true
      # 配置压缩支持的MIME TYPE
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048  # 配置压缩数据大小的下限
    response:
      enabled: true # 配置响应GZIP压缩

OpenFeign 超时配置

如果使用 OpenFeign 调用接口时,该接口耗时比较长,那么 OpenFeign 会报错异常 Read timed out

OpenFeign 默认超时是 10 秒,我们可以根据自己的业务场景适当得调一下超时时间

# open-feign 超时配置
feign:
  client:
    config:
      default:
        connectTimeout: 5000 # 连接超时配置
        readTimeout: 5000 # 读取超时配置