一、OpenFeign 模块间调用
现在的项目中已经使用 RestTemplate 实现模块间的调用,为什么还要使用 OpenFeign
因为 RestTemplate 是基于类调用,每次调用都需要 new 出 RestTemplate 类,耦合性很强。
1. OpenFeign 介绍
介绍 OpenFeign 之前先来介绍 Feign:
- feign 是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需要创建一个接口并在接口上添加注释即可
- Spring Cloud 对 Feign 进行了封装,使其支持了 SpringMVC 标准注解和 HttpMessageConverters 。 Feign 可以与 Eureka 和 Ribbon 组合使用以支持负载均衡。
使用的目的为了用接口的方式让模块之间的调用更加灵活,而不是 new 出一个个的类
Feign 集成了 Ribbon ,与 Ribbon 不同的是,通过 Feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
OpenFeign 是 Spring Cloud 在 Feign 的基础上继续进行封装,支持了 SpringMVC 的注释,如@RequesMapping等等。
OpenFeign 的 @FeignClient 可以解析 SpringMVC 的 @RequesMapping 注释下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
2. 使用方式
服务提供方(生产者)和服务调用方(消费者)在注册中心注册后,服务调用方由原来的 Ribbon + RestTemplate 的方式改为使用 OpenFeign 方式调用,使用方式为:
微服务调用接口 + @FeignClient
二、当前项目结构介绍
当前项目中包含四个模块:
- 公有API:cloud-api-commons
- 服务提供者: cloud-provider-payment8001/8002 (端口号8001 和 8002 )
- 服务消费者: cloud-consumer-order80 (端口号80 )
- 注册中心 Eureka: cloud-eureka-server7001(端口号7001 )
前文链接: https://blog.csdn.net/weixin_42547014/article/details/120334570
项目源码:https://gitee.com/zhangchouchou/spring-cloud-demo/tree/46f877311e6c491aaa766e69d41f6f12d1256f01/
三、项目中添加 Feign
1. 新建模块
新建一个模块,命名为 cloud-consumer-feign-order80
,添加方式依旧为 Maven
2. POM 引入依赖
注:OpenFeign 也是自带 Ribbon
<?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">
<parent>
<artifactId>SpringCloudDemo</artifactId>
<groupId>org.zjh.springclouddemo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-order80</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!--OpenFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--Eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.zjh.springclouddemo</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>
</project>
3. 添加 YML
server:
port: 80
spring:
application:
name: cloud-consumer-feign-order80
eureka:
client:
# 是否注册到eureka
register-with-eureka: true
# 是否发现相关服务
fetch-registry: true
# 指定路径
service-url:
defaultZone: http://localhost:7001/eureka
4. 创建启动类
在启动类上添加 @EnableFeignClients 注解,该注解只在服务调用方添加即可。
@SpringBootApplication
// Eureka客户端
@EnableEurekaClient
//服务调用方开启Feign
@EnableFeignClients
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class,args);
}
}
5. 添加调用接口
添加接口 PaymentFeignService
目录结构
//作为组件被发现
@Component
//指定远程调用的微服务的名称
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/user/get/{id}")
public CommonResult<User> getByID(@PathVariable("id")Integer id);
}
6. 实现 Controller
@RestController
@Slf4j
public class FeignController {
@Autowired
private PaymentFeignService paymentFeignService; //调用远程的微服务接口
@GetMapping("/consumer/user/get/{id}")
public CommonResult<User> getByID(@PathVariable("id")Integer id){
return paymentFeignService.getByID(id);
}
}
至此便实现了模块间接口的远程调用。
7. 验证
启动注册中心 -> 启动8001端口模块 -> 启动新建的模块
刷新注册中心看新建的服务是否注册上
注册成功后,访问新启动的80端口进行数据获取
数据成功获取,说明配置成功。
四、请求超时处理
模块之间的调用很有可能出现请求超时的情况,给 8001 端口的生产者添加一个新的请求,该请求不做任何查询,在方法中等待几秒,如下所示:
@GetMapping("/user/timeout")
public String feignTimeout(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回端口号
return port;
}
再在刚刚添加的 Feign 方式的消费者的接口中添加方法
最后在Controller中添加请求
@GetMapping("/consumer/user/timeout")
public String feignTimeout(){
return paymentFeignService.feignTimeout();
}
重新启动服务并访问路径,因为 OpenFeign 默认等待1秒,所以会出现请求超时的错误
处理方式很简单,设置超时时间。
在 YML 中开启 OpenFeign 客户端超时控制。
ribbon:
ReadTimeout: 5000 #读取的超时时间
ConnectTimeout: 5000 #链接的超时时间
MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数,不包括首次调用
OkToRetryOnAllOperations: false #是否所有操作都重试
当然,这种方法治标不治本,真正的解决方式是使用断路器(Hystrix),会在后文讲解。
五、日志打印
留意一下控制台可以发现一些打印出来的请求信息, Feign 支持打印接口调用情况,方便进行接口监控。
日志级别:
- NONE:默认的,不显示任何日志
- BASIC:仅记录请求方法、URL、响应状态码及执行时间。
- HEADERS: 除了BASIC中定义的信息外,还有请求和响应头信息
- FULL: 除了HEADERS中定义的信息外,还有请求及响应的正文及元数据
配置日志 Bean
@SpringBootConfiguration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
YML 中开启日志的 Feign 客户端
logging:
level:
org.zjh.springcloud.service.PaymentFeignService: debug
重启后再次查询信息,可以看到打印的日志信息
2021-09-17 17:06:53.825 INFO 808 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: CLOUD-PAYMENT-SERVICE.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2021-09-17 17:06:55.223 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID] <--- HTTP/1.1 200 (2906ms)
2021-09-17 17:06:55.223 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID] connection: keep-alive
2021-09-17 17:06:55.223 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID] content-type: application/json
2021-09-17 17:06:55.223 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID] date: Fri, 17 Sep 2021 09:06:55 GMT
2021-09-17 17:06:55.224 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID] keep-alive: timeout=60
2021-09-17 17:06:55.224 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID] transfer-encoding: chunked
2021-09-17 17:06:55.224 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID]
2021-09-17 17:06:55.225 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID] {"code":200,"message":"查询成功","data":{"id":6,"name":"吴","phone":"19804563454","sex":"女"}}
2021-09-17 17:06:55.225 DEBUG 808 --- [p-nio-80-exec-1] o.z.s.service.PaymentFeignService : [PaymentFeignService#getByID] <--- END HTTP (100-byte body)