文章目录

  • SpringCloud - OpenFeign服务调用
  • 0. OpenFeign概述
  • 1. OpenFeign服务调用
  • 2. OpenFeign超时机制
  • 3. OpenFeign日志打印功能


SpringCloud - OpenFeign服务调用

0. OpenFeign概述

在Spring Cloud 中服务之间通过restful方式调用有两种方式 - RestTemplate+Ribbon 和 Feign

回想上一个博客我们在使用Eureka 和 Ribbon的时候是怎么调用注册在Eureka Server上的微服务的地址呢?

openfeign 返回文件流 responseEntity openfeign调用接口报错_服务调用

可以看到是通过拼接的方式,当然了这个例子只有一个参数 id,看起来没有这麻烦。

如果请求url有多个参数呢?:比如http://localhost:8080/find?name=lisi&age=20

那我们用RestTemplate如何调用对方的微服务呢? 可以采用如下方式:

@GetMapping("consumer/find")
public User user(String name ,String age) {
    Map<String, Object> map = new HashMap<String ,Object>() {
        {
            put("name",name);
            put("age",age);
        }  
    };
    return  this.restTemplate.getForObject(PAYMENT_URL+"/find?name={name}&age={age}", User.class, paraMap);
}

是不是已经很麻烦了?

Spring Cloud为我们整合了Fegin解决上述苦恼。

Feign 是在 Ribbon 的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建 http 请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。

Ribbon 是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。它可以 在客户端 配置 RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟 http 请求,步骤相当繁琐。

1. OpenFeign服务调用

① 创建一个cloud-consumer-feign-order80服务消费者模块,然后修改pom文件,引入openfeign坐标依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!--eureka server-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--自定义的cloud-api-commons公共服务模块jar包-->
    <dependency>
        <groupId>com.atguigu.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

② 编写application.yml文件:

server:
  port: 80
# 这里只把feign做客户端用,不注册进eureka
eureka:
  client:
    # 表示是否将自己注册进EurekaServer默认为true
    register-with-eureka: false
    service-url:
      # defaultZone: http://localhost:7001/eureka
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

③ 编写主启动类:

@SpringBootApplication
@EnableFeignClients  //激活OpenFeign客户端
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}

④ 编写Fegin要调用的其他服务的接口

写这个service业务层接口,主要是80消费者去调用它时,实际上调用的是CLOUD-PAYMENT-SERVICE生产者8001和8002微服务的controller层的该方法(轮循)。可以看到这个接口的抽象方法和生产者8001和8002 controller层的方法相同,而且连@GetMapping注解的地址也一样,只是这个方法是抽象的。

package com.atguigu.springcloud.service;

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);
}

⑤ 在controller层中调用配置的接口方法PaymentFeignService

@RestController
@Slf4j
public class OrderFeignController {
    @Resource
    private PaymentFeignService paymentFeignService;
    
    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentByI(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);
    }
}

浏览器上访问/consumer/payment/get/id地址,因为添加了@FeignClient注解的接口,Feign会根据@FeignClient上的value值(CLOUD-PAYMENT-SERVICE),找到具体的服务向它发送/payment/get/id的请求,从而实现远程调用生产者8001和8002的controller中的/payment/get/id。在使用感受上,好像是调用了cloud-consumer-feign-order80项目内部的一个业务一样,无需关心实现,也不用写RestTemplate了。

⑥ 测试:

启动Eureka集群7001和7002,服务提供者8001和8002,服务消费者OrderFeignMain80。浏览器访问http://localhost/consumer/payment/get/1 ,一切正常,可以查询到数据,并且不断在8001和8002之间切换(可以实现负载均衡)。

2. OpenFeign超时机制

OpenFeign默认等待时间是1秒,超过1秒,直接报错;因为OpenFeign的底层是ribbon进行负载均衡,所以它的超时时间是由ribbon控制。

① 为了测试超时控制,我们在服务提供者的模块,即8001和8002中,添加一个controller方法,方法体内Thread.sleep(3000);停留3秒钟返回。

@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return serverPort;
}

② 为了让cloud-consumer-feign-order80能调用到这个接口,需要将其定义写在PaymentFeignService.java接口中:

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);

    @GetMapping(value = "/payment/feign/timeout")
    public String paymentFeignTimeout();
}

③ 在OrderFeignController.java中加入映射地址,用于浏览器访问:

@RestController
@Slf4j
public class OrderFeignController {
    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);
    }
    
    //调用PaymentFeignService.java接口的该方法,实际上去调用生产者8001或8002模块的controller层的该方法
    @GetMapping("/consumer/payment/feign/timeout")
    public String paymentFeignTimeout() throws InterruptedException {
        return paymentFeignService.paymentFeignTimeout();
    }
}

通过浏览器直接访问生产者8001模块http://localhost:8001/payment/feign/timeout,等待3秒钟后,可以看到结果

通过浏览器访问80消费者http://localhost/consumer/payment/feign/timeout,发现页面报错,提示Read time out。

openfeign 返回文件流 responseEntity openfeign调用接口报错_客户端_02

④ Feign默认连接超时为1秒,默认读取资源超时是1秒,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错,为了避免这样的情况,有时需要设置Feign客户端的超时时间,在80模块的yml里添加配置:

# 设置feign客户端超时时间,OpenFeign默认支持Ribbon
ribbon:
  # 建立连接后,读取资源所用时间5s
  ReadTimeout: 5000
  # 建立连接所用时间5s
  ConnectTimeout: 5000

3. OpenFeign日志打印功能

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而查看Feign中Http请求细节,对Feign接口调用进行监控。

日志级别:

  • NONE:默认,不显示任何日志
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间
  • HEADERS:除了BASIC中定义的信息外,还有请求头和响应头的信息
  • FULL:除了HEADERS中定义的信息之外,还有请求体和响应体的正文及元数据

① 添加一个日志配置类。这个需要配置在需要的模块,如cloud-consumer-feign-order80消费者模块:

package com.atguigu.springcloud.config;
import feign.Logger;

@Configuration
public class FeignConfig {
    @Bean
     Logger.Level fiegnLoggerLevel(){
         return Logger.Level.FULL;
     }
}

② 在yml里指定监控哪个请求接口和监控级别:一般打印Feign接口调用的日志

logging:
  level:
    # 指定监控哪个接口,以及监控的级别,80消费者模块中的业务接口PaymentFeignService
    com.atguigu.springcloud.service.PaymentFeignService: debug

③ 测试:浏览器访问http://localhost/consumer/payment/get/1后,控制台打印日志:

openfeign 返回文件流 responseEntity openfeign调用接口报错_客户端_03