一、提问
已经有loadbalancer为什么还要学习OpenFeign?
两个都有道理的话,日常用那个?
二、是什么
OpenFeign是什么
官网翻译
Feign是一个声明性web服务客户端。它使编写web服务客户端变得更容易。使用Feign创建一个接口并对其进行注释。它具有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,以及对使用Spring Web中默认使用的HttpMessageConverter的支持。Spring Cloud集成了Eureka、Spring Cloud CircuitBreaker以及Spring Cloud LoadBalancer,以便在使用Feign时提供负载平衡的http客户端。
Spring Cloud OpenFeign
GitHub
GitHub - spring-cloud/spring-cloud-openfeign: Support for using OpenFeign in Spring Cloud apps
一句话
openfeign是一个声明式的Web服务客户端,只需创建一个Rest接口并在该接口上添加注解@FeignClient即可
OpenFeign基本上就是当前微服务之间调用的事实标准
三、能干嘛
OpenFeign能干什么
前面在使用SpringCloud LoadBalancer+RestTemplate时,利用RestTemplate对http请求的封装处理形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,OpenFeign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在OpenFeign的实现下,我们只需创建一个接口并使用注解的方式来配置它(在一个微服务接口上面标注一个@FeignClient注解即可),即可完成对服务提供方的接口绑定,统一对外暴露可以被调用的接口方法,大大简化和降低了调用客户端的开发量,也即由服务提供者给出调用接口清单,消费者直接通过OpenFeign调用即可,O(∩_∩)O。
OpenFeign同时还集成SpringCloud LoadBalancer
可以在使用OpenFeign时提供Http客户端的负载均衡,也可以集成阿里巴巴Sentinel来提供熔断、降级等功能。而与SpringCloud LoadBalancer不同的是,通过OpenFeign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
- 可插拔的注解支持,包括Feign注解和JAX-RS注解
- 支持可插拔的HTTP编码器和解码器
- 支持Sentinel和它的Fallback
- 支持SpringCloudLoadBalancer的负载均衡
- 支持HTTP请求和响应的压缩
四、OpenFeign通用步骤(怎么用)
1、接口+注解
微服务Api接口+@FeignClient注解标签
架构说明图
服务消费者80 → 调用含有@FeignClient注解的Api服务接口 → 服务提供者(8001/8002)
2、流程步骤
1)、建Module
创建cloud-consumer-feign-order80
Feign在消费端使用
2)、改POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lzx.cloud</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloud-consumer-feign-order80</artifactId>
<description>微服务消费者订单Module模块(LoadBalancer + OpenFeign实现服务调用)</description>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringCloud consul discovery-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-all-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--fastjson2-->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.lzx.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
</plugins>
</build>
</project>
引入依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3)、写YML
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
#优先使用服务ip进行注册
prefer-ip-address: true
service-name: ${spring.application.name}
4)、主启动(修改类名为MainOpenFeign80)
package com.lzx.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author admin
* //@EnableDiscoveryClient 该注解用于向使用consul为注册中心时注册服务
* //@EnableFeignClients 启用feign客户端,定义服务+绑定接口,以声明式的方法优雅而简单的实现服务调用
*/
@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
public class MainOpenFeign80 {
public static void main(String[] args) {
SpringApplication.run(MainOpenFeign80.class, args);
}
}
主启动类上面配置@EnableFeignClients表示开启OpenFeign功能并激活
5)、业务类
a、按照架构说明图进行编码准备
订单模块要去调用支付模块,订单和支付两个微服务,需要通过Api接口解耦,一般不要在订单模块写非订单相关的业务,自己的业务自己做+其它模块走FeignApi接口调用
b、修改cloud-api-commons通用模块
引入openfeign依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
新建服务接口PayFeignApi,头上配置@FeignClient注解
参考微服务8001的Controller层,新建PayFeignApi接口
package com.lzx.cloud.apis;
import com.lzx.cloud.entities.Pay;
import com.lzx.cloud.resp.ResultData;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @author admin
*/
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi {
/**
* 新增支付交易信息
*
* @param pay 支付交易信息
* @return 受影响行数
*/
@PostMapping(value = "/pay/add")
ResultData<String> addPay(@RequestBody Pay pay);
/**
* 根据主键ID获取支付交易信息
*
* @param id 支付交易主键ID
* @return 支付交易信息
*/
@GetMapping(value = "/pay/get/{id}")
ResultData<Pay> getById(@PathVariable("id") Integer id);
/**
* 获取配置文件中信息
*
* @param companyInfo 公司信息
* @return 配置文件中信息
*/
@GetMapping(value = "/pay/get/info")
String getInfoByConsul(@Value("${company.info}") String companyInfo);
}
bug提醒一下
c、拷贝之前的80工程进cloud-consumer-feign-order80,记得去掉部分代码和LoadBalancer不相关特性
d、修改Controller层的调用
package com.lzx.cloud.controller;
import com.lzx.cloud.apis.PayFeignApi;
import com.lzx.cloud.entities.Pay;
import com.lzx.cloud.resp.ResultData;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @author admin
*/
@Slf4j
@RestController
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class OrderController {
private final PayFeignApi payFeignApi;
/**
* 新增支付交易信息
*
* @param pay 支付交易信息
* @return 受影响行数
*/
@PostMapping("/feign/pay/add")
public ResultData<String> addOrder(@RequestBody Pay pay) {
log.info("第一步:模拟本地addOrder新增订单成功(省略sql操作),第二步:再开启addPay支付微服务远程调用");
return payFeignApi.addPay(pay);
}
/**
* 根据主键ID获取支付交易信息
*
* @param id 支付交易主键ID
* @return 支付交易信息
*/
@GetMapping("/feign/pay/get/{id}")
public ResultData<Pay> getPayInfo(@PathVariable("id") Integer id) {
log.info("-------支付微服务远程调用,按照id查询订单支付流水信息");
return payFeignApi.getById(id);
}
/**
* 获取配置文件中信息
*
* @return 配置文件中信息
*/
@GetMapping(value = "/feign/pay/info")
public String getInfoByConsul() {
return payFeignApi.getInfoByConsul();
}
}
3、测试
- 先启动Consul服务器
- 再启动微服务8001
- 再启动cloud-consumer-feign-order80
- PostMan测试
新增(http://localhost/feign/pay/add)
查询(http://localhost/feign/pay/get/1) - 再启动微服务8002,测试看看O(∩_∩)O哈哈~
http://localhost/feign/pay/info
OpenFeign默认集成了LoadBalancer
上述官网说明
4、小总结
五、OpenFeign高级特性
1、OpenFeign超时控制
a、本次OpenFeign的版本要注意,最新版和网络上你看到的配置不一样
在Spring Cloud微服务架构中,大部分公司都是利用OpenFeign进行服务间的调用,而比较简单的业务使用默认配置是不会有多大问题的,但是如果是业务比较复杂,服务要进行比较繁杂的业务计算,那后台很有可能会出现Read Timeout这个异常,因此定制化配置超时时间就有必要了。
b、超时设置,故意设置超时演示出错情况,自己使坏写bug
服务提供方cloud-provider-payment8001故意写暂停62秒钟程序
为什么写62秒?
服务调用方cloud-consumer-feign-order80写好捕捉超时异常
code
/**
* 根据主键ID获取支付交易信息
*
* @param id 支付交易主键ID
* @return 支付交易信息
*/
@GetMapping("/feign/pay/get/{id}")
public ResultData<Pay> getPayInfo(@PathVariable("id") Integer id) {
log.info("-------支付微服务远程调用,按照id查询订单支付流水信息");
try {
return payFeignApi.getById(id);
} catch (Exception e) {
log.error("支付微服务远程调用异常:", e);
log.error("---调用结束:{}", DateUtil.now());
return ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
}
}
测试
http://localhost/feign/pay/get/1
错误页面
结论
OpenFeign默认等待60秒钟,超过后报错
c、官网解释+配置处理
两个关键参数
默认OpenFeign客户端等待60秒钟,但是服务端处理超过规定时间会导致Feign客户端返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制,默认60秒太长或者业务时间太短都不好
yml文件中开启配置:
connectTimeout 连接超时时间
readTimeout 请求处理超时时间
超时配置参考官网要求
d、修改cloud-consumer-feign-order80YML文件里需要开启OpenFeign客户端超时控制
官网出处
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign-overriding-defaults
全局配置
- 关键内容
spring: cloud: openfeign: client: config: default: #连接超时时间 connectTimeout: 3000 #读取超时时间 readTimeout: 3000
- all
server: port: 80 spring: application: name: cloud-consumer-openfeign-order ####Spring Cloud Consul for Service Discovery cloud: consul: host: 112.74.84.186 port: 8500 discovery: #优先使用服务ip进行注册 prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: #连接超时时间 connectTimeout: 3000 #读取超时时间 readTimeout: 3000
- 3秒测试
指定配置
- 单个服务配置超时时间
spring: cloud: openfeign: client: config: default: #连接超时时间 connectTimeout: 3000 #读取超时时间 readTimeout: 3000 # 为serviceC这个服务单独配置超时时间,单个配置的超时时间将会覆盖全局配置 serviceC: #连接超时时间 connectTimeout: 2000 #读取超时时间 readTimeout: 2000
- 关键内容
spring: cloud: openfeign: client: config: cloud-payment-service: #连接超时时间 connectTimeout: 2000 #读取超时时间 readTimeout: 2000
- all
server: port: 80 spring: application: name: cloud-consumer-openfeign-order ####Spring Cloud Consul for Service Discovery cloud: consul: host: 112.74.84.186 port: 8500 discovery: #优先使用服务ip进行注册 prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: #连接超时时间 connectTimeout: 3000 #读取超时时间 readTimeout: 3000 # 为serviceC这个服务单独配置超时时间,单个配置的超时时间将会覆盖全局配置 cloud-payment-service: #连接超时时间 connectTimeout: 2000 #读取超时时间 readTimeout: 2000
- 5秒测试
2、OpenFeign重试机制
步骤
a、默认重试是关闭的,给了默认值
b、默认关闭重试机制,测试看看
http://localhost/feign/pay/get/1
结果,只会调用一次后就结束
c、开启Retryer功能
新增配置类FeignConfig并修改Retryer配置
package com.lzx.cloud.config;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author admin
*/
@Configuration
public class FeignConfig {
@Bean
public Retryer myRetryer() {
/*
* Feign默认配置是不走重试策略的
* return Retryer.NEVER_RETRY;
*
* 最大请求次数为3(1+2),初始间隔时间为100ms,重试间最大间隔时间为1s
*/
return new Retryer.Default(100, 1, 3);
}
}
结果,总体调用3次
3 = 1(default)+2
补充一句
如果你觉得效果不明显,后续演示feign 日志功能的时候再演示,目前控制台没有看到3次重试过程,只看到结果,正常的,正确的,是feign的日志打印问题。
3、OpenFeign默认HttpClient修改
a、是什么
OpenFeign中http client
如果不做特殊配置,OpenFeign默认使用JDK自带的HttpURLConnection发送HTTP请求,
由于默认HttpURLConnection没有连接池、性能和效率比较低,如果采用默认,性能上不是最牛B的,所以加到最大。
b、替换之前,还是按照超时报错的案例
/**
* 根据主键ID获取支付交易信息
*
* @param id 支付交易主键ID
* @return 支付交易信息
*/
@GetMapping("/feign/pay/get/{id}")
public ResultData<Pay> getPayInfo(@PathVariable("id") Integer id) {
log.info("-------支付微服务远程调用,按照id查询订单支付流水信息");
try {
log.info("---调用开始: "+ DateUtil.now());
return payFeignApi.getById(id);
} catch (Exception e) {
log.error("支付微服务远程调用异常:", e);
log.info("---调用结束:{}", DateUtil.now());
return ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
}
}
替换之前,默认用的是什么
c、Apache HttpClient 5替换OpenFeign默认的HttpURLConnection
why
修改微服务feign80
cloud-consumer-openfeign-order
FeignConfig类里面将Retryer属性修改为默认
package com.lzx.cloud.config;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author admin
*/
@Configuration
public class FeignConfig {
@Bean
public Retryer myRetryer() {
//Feign默认配置是不走重试策略的
return Retryer.NEVER_RETRY;
}
}
POM修改
<!-- httpclient5-->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>
<!-- feign-hc5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<version>13.1</version>
</dependency>
Apache HttpClient5配置开启说明
#Apache HttpClient5 配置开启
spring:
cloud:
openfeign:
httpclient:
hc5:
enabled: true
YML修改
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
#优先使用服务ip进行注册
prefer-ip-address: true
service-name: ${spring.application.name}
openfeign:
#Apache HttpClient5 配置开启
httpclient:
hc5:
enabled: true
client:
config:
default:
#连接超时时间
connectTimeout: 4000
#读取超时时间
readTimeout: 4000
d、替换之前
e、替换之后
4、OpenFeign请求/响应压缩
a、官网说明
b、是什么
对请求和响应进行GZIP压缩
Spring Cloud OpenFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。
通过下面的两个参数设置,就能开启请求与相应的压缩功能:
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.response.enabled=true
细粒度化设置
对请求压缩做一些更细致的设置,比如下面的配置内容指定压缩的请求数据类型并设置了请求压缩的大小下限,
只有超过这个大小的请求才会进行压缩:
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json #触发压缩数据类型
spring.cloud.openfeign.compression.request.min-request-size=2048 #最小触发压缩的大小
c、YML
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
#优先使用服务ip进行注册
prefer-ip-address: true
service-name: ${spring.application.name}
openfeign:
#Apache HttpClient5 配置开启
httpclient:
hc5:
enabled: true
compression:
request:
enabled: true
#最小触发压缩的大小
min-request-size: 2048
#触发压缩数据类型
mime-types: text/xml,application/xml,application/json
response:
enabled: true
client:
config:
default:
#连接超时时间
connectTimeout: 4000
#读取超时时间
readTimeout: 4000
d、压缩效果测试在下一章节体现
5、OpenFeign日志打印功能
a、日志打印功能是什么
Feign 提供了日志打印功能,可以通过配置来调整日志级别,
从而了解 Feign 中 Http 请求的细节,
说白了就是对Feign接口的调用情况进行监控和输出
b、日志级别
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
- FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
c、配置日志bean
package com.lzx.cloud.config;
import feign.Logger;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author admin
*/
@Configuration
public class FeignConfig {
@Bean
public Retryer myRetryer() {
/*
* Feign默认配置是不走重试策略的
*/
return Retryer.NEVER_RETRY;
}
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
d、YML文件里需要开启日志的Feign客户端
公式(三段):logging.level + 含有@FeignClient注解的完整带包名的接口名+debug
# feign日志以什么级别监控哪个接口
logging:
level:
com:
lzx:
cloud:
apis:
PayFeignApi: debug
e、后台日志查看
带着压缩调用
去掉压缩调用
f、补充实验,重试机制控制台看到3次过程
类FeignConfig
package com.lzx.cloud.config;
import feign.Logger;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author admin
*/
@Configuration
public class FeignConfig {
@Bean
public Retryer myRetryer() {
/*
* Feign默认配置是不走重试策略的
* return Retryer.NEVER_RETRY;
*
* 最大请求次数为3(1+2),初始间隔时间为100ms,重试间最大间隔时间为1s
*/
return new Retryer.Default(100, 1, 3);
}
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
YML(看到效果改为2秒)
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
#优先使用服务ip进行注册
prefer-ip-address: true
service-name: ${spring.application.name}
openfeign:
#Apache HttpClient5 配置开启
httpclient:
hc5:
enabled: true
compression:
request:
enabled: true
#最小触发压缩的大小
min-request-size: 2048
#触发压缩数据类型
mime-types: text/xml,application/xml,application/json
response:
enabled: true
client:
config:
default:
#连接超时时间
connectTimeout: 2000
#读取超时时间
readTimeout: 2000
# feign日志以什么级别监控哪个接口
logging:
level:
com:
lzx:
cloud:
apis:
PayFeignApi: debug
测试地址
http://localhost/feign/pay/get/1
控制台3次重试触发效果的过程
g、本节内容最后的YML
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
#优先使用服务ip进行注册
prefer-ip-address: true
service-name: ${spring.application.name}
openfeign:
#Apache HttpClient5 配置开启
httpclient:
hc5:
enabled: true
compression:
request:
enabled: true
#最小触发压缩的大小
min-request-size: 2048
#触发压缩数据类型
mime-types: text/xml,application/xml,application/json
response:
enabled: true
client:
config:
default:
#连接超时时间
connectTimeout: 2000
#读取超时时间
readTimeout: 2000
# feign日志以什么级别监控哪个接口
logging:
level:
com:
lzx:
cloud:
apis:
PayFeignApi: debug
六、OpenFeign和Sentinel集成实现fallback服务降级
后续springcloud alibaba篇章