介绍
OpenFeign 是一个声明式的 Web 服务客户端,用于简化在 Java 应用中调用 HTTP API 的过程,在 Spring Cloud 体系里被广泛应用,它有以下关键特性:
- 声明式调用:基于注解,开发人员只需定义接口并添加注解,就能轻松描述对远程服务的 HTTP 请求,无需手动编写大量重复的 HTTP 客户端代码,如使用
@FeignClient
注解来指定要调用的服务名称。- 集成与适配:它能与 Spring Cloud 的服务发现机制(如 Eureka、Consul 等)无缝集成,调用远程服务时,借助服务发现找到目标服务实例的实际网络地址,开发者不用关心服务实例的具体位置和动态变化。同时,OpenFeign 可以方便地整合 Ribbon 实现客户端负载均衡,自动在多个服务实例间分摊请求流量。
- 可定制化:提供了丰富的编码器和解码器,支持多种数据格式的传输,例如 JSON、XML 等。此外,还允许自定义请求拦截器,可在请求发送前添加公共的请求头、鉴权信息,满足复杂业务场景下的定制需求。
- 熔断降级:可以结合 Hystrix 等熔断器框架,当被调用的远程服务出现故障、响应超时等异常情况时,快速触发熔断机制,返回预设的兜底数据,避免级联故障,保障整个微服务系统的稳定性。
使用原因
在我的上一章博客中使用了Nacos对微服务实现了服务的注册以及发现:微服务-注册中心-Nacos,但是发现了一个问题,我们在编写远程调用其他微服务的使用需要很长很复杂的一段http代码,如下:
其实远程调用的几个关键点就是:请求方式、请求路径、请求参数、返回值类型,所以本章用OpenFeign来解决微服务之间的远程调用复杂性,提高开发效率。
OpenFeign使用
这个案例是对上一章微服务-注册中心-Nacos的实例进行改造
1.引入依赖
在CloudDemo2中引入如下依赖:
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
删除启动类上的如下代码:
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
2.启用OpenFeign
接下来,我们在cloudDemo2的CloudDemo2Application启动类上添加注解,启动OpenFeign功能:
@SpringBootApplication
@EnableFeignClients//开启OpenFeign功能支持
public class CloudDemo2Application {
public static void main(String[] args) {
SpringApplication.run(CloudDemo2Application.class);
}
}
3.编写OpenFeign客户端
在cloudDemo2中创建一个client包,然后在下面创建一个OpenFeign客户端:
package com.waitforme.clouddemo2.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@FeignClient("cloudDemo1") //cloudDemo1微服务模块中配置文件所配置的微服务名
public interface CloudDemo1Client {
/*
@FeignClient("cloudDemo1") :声明服务名称
@GetMapping :声明请求方式
@GetMapping("/cloudDemo1/list") :声明请求路径
List<Object> :返回值类型
*/
@GetMapping("/cloudDemo1/list")
List<Object> demo1List();
}
编写完上面的代码后,OpenFeign就可以利用动态代理帮我们实现这个方法,并且向http://cloudDemo1/cloudDemo1/list
发送一个GET
请求,并自动将返回值处理为List<Object>
。
我们只需要直接调用这个方法,即可实现远程调用了。
4.使用OpenFeign
修改CloudDemo2ServiceImpl代码
package com.waitforme.clouddemo2.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.waitforme.clouddemo2.client.CloudDemo1Client;
import com.waitforme.clouddemo2.service.CloudDemo2Service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor//为必要的类成员变量生成构造函数
public class CloudDemo2ServiceImpl implements CloudDemo2Service {
private final CloudDemo1Client client;
@Override
public List<Object> getDemo1List() {
List<Object> list = client.demo1List();
if (CollUtil.isEmpty(list)) {
return null;
}
return list;
}
}
对比修改前后的代码:
修改前,使用restTemplate:
修改后:
5.启动测试
启动服务,并清空IDEA控制台
在浏览器访问cloudDemo2的getDemo1路径,并刷新看我们的控制台输出
刷新三次页面控制台对比,得出可以使用OpenFeign正常调用,使用的是轮询负载均衡策略
cloudDmoe1的服务实例1
cloudDmoe1的服务实例2
连接池的使用
OpenFeign 使用连接池主要有以下几方面好处:
- 性能提升
- 减少连接创建开销:在频繁发起 HTTP 请求的场景下,如果不使用连接池,每次请求都需要创建新的 TCP 连接。这个创建过程涉及 TCP 三次握手,会消耗额外的网络往返时间以及系统资源。而连接池能够复用已有的连接,避免了反复创建和销毁连接的开销,显著加快请求响应速度。
- 并发处理更高效:面对高并发的请求,连接池预先创建并管理了一批连接,多个线程可以同时从池中获取连接发起请求,无需等待新连接的缓慢建立,使得系统能够更从容地应对突发流量,提升整体并发处理能力。
- 资源优化
- 降低系统资源消耗:持续不断地创建新连接会占用大量的系统资源,包括内存、CPU 时间等。连接池通过复用有限数量的连接,将资源消耗维持在一个较低水平,尤其对于资源受限的容器化环境或者小型服务器,这有助于维持系统稳定运行,避免因资源耗尽而出现性能瓶颈甚至崩溃。
- 稳定性增强
- 应对网络波动:网络环境往往存在不稳定因素,如短暂的网络抖动、临时的服务中断。连接池中的连接一旦建立,只要没有出现严重故障,就可以持续复用。当遇到轻微网络波动时,不用频繁重新建立连接,减少因网络异常导致请求失败的概率,增强服务调用的稳定性。
- 可配置性与管控
- 灵活配置:连接池通常提供了丰富的配置参数,例如连接池的最大连接数、最小连接数、连接的空闲时长等。开发人员可以根据实际业务场景和服务器资源状况,精细调整这些参数,实现性能与资源消耗之间的最优平衡。
- 监控与维护:借助连接池,还能够方便地对连接的使用状态进行监控,了解连接的活跃数量、空闲数量、等待获取连接的线程数等关键信息,以便及时发现潜在问题,并采取诸如清理空闲连接、动态调整连接池大小等维护措施。
1.引入依赖
在cloudDemo2中引入:
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
2.开启连接池
修改cloudDemo2的yaml配置文件,添加:
feign:
okhttp:
enabled: true # 开启OKHttp功能
OpenFeign进阶使用
思考:如果我们的cloudDemo3模块也要调用cloudDemo1的demo1List方法,也要在cloudDemo3上面写一个OpenFeign客户端吗?
所以我们可以把要与其他微服务进行交互的部分抽取出来,单独提成一个微服务来专门管理远程调用
1.抽取Feign客户端
创建一个远程调用的模块
在openFeignApi的pom.xml中导入依赖,并删除cloudDemo2中的如下两个依赖,因为我们已经把远程连接相关的都提到单独的微服务中了,cloudDemo2中也不需要这两个依赖了。
<dependencies>
<!--open feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- load balancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
2.把cloudDemo2中的client包剪切到openFeignApi模块下
3.在cloudDemo2和cloudDemo3的pom.xml中引入openFeign-Api的依赖
<dependency>
<groupId>com.waitforme</groupId>
<artifactId>openFeign-Api</artifactId>
<version>1.0</version>
</dependency>
4.启动测试
报错?
原因分析:我们把cloudDemo2的feign客户端抽取到了一个独立的微服务中,所以找不到了
解决方案:
1.在cloudDemo2的启动类的EnableFeignClients上声明扫描的包
2.声明要用的FeignClient
最后在浏览器中分别用cloudDemo2和cloudDemo3来访问测试
日志配置
因为我们把openFeign单独抽出来做了一个微服务,而OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。它的日志级别有:
-
NONE:不记录任何日志信息,这是默认值。
-
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
-
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
-
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
1.定义日志级别
在openFeign-Api下创建一个config包,并创建一个配置类
package com.waitforme.openFeignApi.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel() {
return Logger.Level.FULL;
}
}
2.配置
让日志级别生效
方式1:
局部配置,在某个FeignClient上加上configuration属性
@FeignClient(value = "cloudDemo1", configuration = DefaultFeignConfig.class) //cloudDemo1微服务模块中配置文件所配置的微服务名
方式2:
全局配置,在启动类添加defaultConfiguration属性,使FeignClient生效
@EnableFeignClients(clients = {CloudDemo1Client.class}, defaultConfiguration = DefaultFeignConfig.class)
3.重启服务,刷新浏览器请求,测试
控制台日志输出格式:
11:16:15:063 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] ---> GET http://cloudDemo1/cloudDemo1/list HTTP/1.1
11:16:15:065 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] ---> END HTTP (0-byte body)
11:16:15:242 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] <--- HTTP/1.1 200 (176ms)
11:16:15:242 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] connection: keep-alive
11:16:15:242 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] content-type: application/json
11:16:15:242 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] date: Sun, 29 Dec 2024 03:16:15 GMT
11:16:15:242 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] keep-alive: timeout=60
11:16:15:242 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] transfer-encoding: chunked
11:16:15:242 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List]
11:16:15:243 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] ["你好","你好,nacos","你好,微服务"]
11:16:15:243 DEBUG 2456 --- [nio-8082-exec-2] c.w.o.client.CloudDemo1Client : [CloudDemo1Client#demo1List] <--- END HTTP (48-byte body)