OpenFeign简介

地址https://github.com/spring-cloud/spring-cloud-openfeign

Feign是一个声明式的Web Service客户端。它的出现使开发Web Service客户端变得很简单。使用Feign只需要创建一个接口加上对应的注解,比如:FeignClient注解。Feign有可插拔的注解,包括Feign注解和JAX-RS注解。

Feign也支持编码器和解码器,Spring Cloud Open Feign对Feign进行增强支持Spring MVC注解,可以像Spring Web一样使用HttpMessageConverters等。

Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。

Feign旨在使编写Java Http客户端变得更容易

前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置他(以前是Dao接口上面标注Mappe注解,现在是一个微服务接口上面标注一个Feign注解即可),完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon,利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

Feign和OpenFeign的区别

image-20200707191826400

Feign OpenFeign
Feign是Spring Cloud组件中的一个轻量级RESTful的Http服务客户端。Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。 OpenFeign是SpringCloud在Feign的基础上支持了SpirngMVC的注解,如@RequesetMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
<dependency><br/> <groupId>org.springframework.cloud</groupId><br/> <artifactId>spring-cloud-starter-feign</artifactId><br/> </dependency> <dependency><br/> <groupId>org.springframework.cloud</groupId><br/> <artifactId>spring-cloud-starter-openfeign</artifactId><br/> </dependency>

OpenFeign的使用

新建cloud-consumer-feign-order80。服务注册中心依旧使用Eureka

image-20200707192552463

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</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>

image-20200707192819187

yml

将服务注册进Eureka注册中心

image-20200707193020164

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 80


spring:
application:
name: cloud-order-service #服务名称

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka

主启动类

使用注解@EnableFeignClients启用Feign客户端

image-20200707193408346

1
2
3
4
5
6
7
8
9
@SpringBootApplication
//使用注解@EnableFeignClients启用Feign客户端
@EnableFeignClients
public class OrderFeignMain80 {

public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}

service

新建PaymentFeignService接口并新增注解@FeignClient,@Feign的value值是服务提供者注册的名称

image-20200707194321237

1
2
3
4
5
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")//CLOUD-PAYMENT-SERVICE的调用地址
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}

controller

编写OrderFeignController调用service层

image-20200707194647835

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@Slf4j
public class OrderFeignController {

@Resource
PaymentFeignService paymentFeignService;

@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){

return paymentFeignService.getPaymentById(id);
}

}

测试

启动Eureka服务注册中心,服务提供者,启动Feign客户端

image-20200707194857168

访问http://localhost/consumer/payment/get/31

GIF1951

使用OpenFeign调用服务成功!

OpenFeign超时控制

什么是超时控制?

默认Feign 客户端只等待1秒钟,但是当服务端处理需要超过1秒钟,导致Feign 客户端不想等待了,直接返回报错。这是非常不好的。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

为了达到服务端超时处理的效果,我们首先在服务提供方8001故意写暂停程序

image-20200707200735520

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

服务消费方80的service接口添加超时方法

image-20200707201441962

1
2
@GetMapping(value = "/payment/feign/timeout")
String paymentFeignTimeout();

服务消费方80的coontroller中添加请求方法 openfeign 客户端默认一般等待一秒钟,而这个服务提供者处理这个业务时停止了3秒,所以会出现报错

image-20200707201722231

启动测试

访问http://localhost/consumer/payment/feign/timeout 一秒钟后提示超时报错

GIF2020

为了避免出现这种情况,我们需要配置OpenFeign的超时控制时间,OpenFeign 内与 ribbon 整合了,支持负载均衡,它的超时控制也由最底层的 ribbon 进行控制,yml 添加配置

image-20200707215020957

1
2
3
4
5
6
#设置feign 客户端超时时间(openFeign默认支持ribbon)
ribbon:
#指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 5000
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ConnectTimeout: 5000

ribbon.ReadTimeout是建立连接后从服务器读取到可用资源所用的时间

ribbon.ConnectTimeout是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间

通过配置超时控制,我们将时间从一秒提高到了五秒。5>3 这时候就不会出现客户端等待超时而报错了

启动测试再次访问http://localhost/consumer/payment/feign/timeout

GIF2200

等待了至少3秒后调用服务成功!

OpenFeign日志打印功能

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。说白了就是对Feign接口的调用i情况进行监控和输出

日志级别

None:默认的,不显示任何日志

BASIC:仅记录请求方法,URL,响应状态码几执行时间

HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息

FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据

配置类

编写日志配置类FeignConfig

image-20200707220601032

1
2
3
4
5
6
7
8
@Configuration
public class FeignConfig {

@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}

yml

image-20200707220806902

1
2
3
4
logging:
level:
# feign日志以什么级别监控哪个接口
com.kylin.service.PaymentFeignService: debug

测试

启动80调用服务

image-20200707220859460

查看控制台,看见详细的日志信息输出

image-20200707221400965