远程调用(OpenFeign)
我没专门讲编程式的远程调用,可以自己看一下 RestTemplate ,这里就不多做赘述了
什么是OpenFeign?
- 声明式HTTP客户端:通过接口+注解方式实现服务调用
- 基于Ribbon的负载均衡:内置客户端负载均衡能力
- 与Spring Cloud整合:无缝对接Eureka/Nacos等注册中心
- 优势: ✅ 简化HTTP API调用 ✅ 支持多种编码器/解码器 ✅ 可插拔的注解支持
核心优势
特性 | 传统 RestTemplate | OpenFeign |
---|---|---|
代码复杂度 | 需手动拼接 URL 和参数 | 接口声明直接映射远程 API |
维护成本 | 修改 URL 需多处调整 | 集中管理接口定义 |
扩展性 | 需自定义拦截器 | 支持全局/局部拦截器配置 |
快速入门
- 添加依赖
xml
<!-- OpenFeign 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>4.1.2</version>
</dependency>
- 启用OpenFeign
css
在启动类上加`@EnableFeignClients`注解开启 Feign 远程调用功能
less
@EnableFeignClients // 关键注解:开启 Feign 客户端扫描
@EnableDiscoveryClient // 开启服务发现功能
@SpringBootApplication
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
- 声明式接口定义
向第三方发送也是一样的,只要声明正确了(@FeignClient的url属性),feign 会帮忙发送
kotlin
@FeignClient("small-order") // feign 客户端, 指定服务名(需注册到注册中心)
public interface OrderFeignClient {
@GetMapping("/order/test") // 与远程接口完全一致
String test();
}
我这里提前写好了一个测试用的服务接口,和 feign 不是同一个服务
其他类型的参数:
和springMVC使用相同的注解即可
less
@GetMapping("/user/{id}")
User getUserById(@PathVariable("id") Long userId); // 必须指定参数名
@PostMapping("/user/create")
User createUser(@RequestBody UserDTO userDTO); // 对象自动序列化为 JSON
-
使用
在我们的业务中注入刚才声明好的 feign 接口,注入到属性中即可使用,和 mybatis 类似,后续在访问此接口的时候,会使用feign完成远程调用
less
@Slf4j
@RestController
public class TestController {
@Autowired
private OrderFeignClient orderFeignClient;
@GetMapping("/test")
public String test() {
String test = orderFeignClient.test();
log.info("使用 feign 远程调用 order 服务的test接口,返回数据:{}",test);
return "test";
}
}
可以在控制台看到日志记录,feign帮我们完成了远程调用
常见问题排查:
问题现象 | 解决方案 |
---|---|
404 接口不存在 | 检查 @FeignClient 服务名是否与注册中心一致 |
参数传递失败 | 确保 @PathVariable 指定参数名 |
无法注入 Feign 客户端 | 确认主类添加了 @EnableFeignClients |
进阶配置
- 日志追踪
springboot的配置文件
yaml
logging:
level:
com.huang.small.feign: debug # 注意这是自己的包名
配置类:
kotlin
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; // 输出完整请求日志 正式环境不推荐使用,可以用来排错
}
}
- 超时控制
我们这里通过一个场景来讲解一下为什么需要这个功能。
我们现在有两个服务,分别是订单服务和商品服务,我们生成一个商品的订单时,需要去向商品服务发送请求,获取到具体的商品数据如果商品服务的服务器宕机了,订单服务就一直无法连接到商品服务,或者说连接上了但是业务逻辑处理的太慢了,很久都无法活动响应数据,就会导致 openfeign 一致获取不到结果,就可能导致雪崩问题(这里不详细说明,简单理解一下就可以)。
简单来说,就是 openfeign 在发起远程调用的时候,设置一个超时时间,在超时时间内,就正常返回结果,如果超过了这个时间,就不等了,中断这次远程调用,这个时候 openfeign 有两种形式的返回:
- 返回错误信息,提示我们远程服务超时
- 返回兜底数据,比如默认为 0,没有库存(需要配合熔断框架)
对于超时控制,我们还需要了解两个配置:
- connectTimeout: 连接超时(默认10s)
- readTimeout: 读取超时 (默认60s)
可以通过配置来修改这两个配置:
yaml
spring:
application:
name: small-product # 应用名称,会在 nacos 中显示
cloud:
openfeign:
client:
config:
default: # 这个是默认配置,也可以像下面一样进行精确配置
connect-timeout: 1000 # 连接超时时间 毫秒
read-timeout: 3000 # 读取超时时间 毫秒
small-product: # 你的feign客户端名字,可以在@FeignClient("small-order")这个注解中配置
connect-timeout: 1000 # 连接超时时间 毫秒
read-timeout: 3000 # 读取超时时间 毫秒
- 重试机制
远程调用失败后,还可以进行多次尝试,如果某次成功则返回ok,如果多次依然失败则结束调用,返回错误(默认不重试 )
在配置类中加入重试器的 bean :
typescript
@Bean
Retryer retryer() {
return new Retryer.Default(1000, 1000, 3);
}
- period: 间隔多少毫秒(第一次间隔,后面每次的间隔是前一次的1.5倍)
- maxPeriod: 最多间隔多少(每次超时重试的等待时间为 间隔 * 1.5 最多不超过这个参数的时长)
- maxAttempts: 最多尝试几次
- 拦截器
请求拦截器: openfeign 在远程服务发送请求之前,由请求拦截器对请求进行一些定制化的修改,比如向请求头里面加入一些字段
响应拦截器: 远程服务处理完请求以后,把响应数据交给响应拦截器,对响应进行一个预处理,再把真正的结果发给 openfeign
因为响应拦截器用的比较少,所以我这里只给出请求拦截器的实例代码,响应拦截器也差不多,不过是换一个接口实现即可
定义一个类:实现 RequestInterceptor ,重写apply方法,需要使用@Component
注解注入到容器中
typescript
@Component
public class TestRequestInterceptor implements RequestInterceptor {
/**
* 请求拦截器
* @param requestTemplate 这次请求的详细信息
*/
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("test", "test");
}
}
可以自己测试一下响应拦截器的用法
- Fallback(需要配合熔断框架才能实现)
返回兜底数据:
- 创建一个类实现我们的 feign 客户端接口,并加上
@Component
注解,注册到容器中:
less
@Slf4j
@Component
public class OrderFeignClientFallback implements OrderFeignClient {
@Override
public String test() {
log.info("这是兜底回调.......");
return "兜底数据";
}
}
实现接口中的方法,这个方法将会作为兜底的执行方案
- 在 feign 接口的注解写明对应的兜底的类
@FeignClient
注解中的 fallback 就是用来兜底的类,请求失败之后会从容器中拿到这个类型的bean执行兜底回调
kotlin
@FeignClient(value = "small-order",fallback = OrderFeignClientFallback.class) // feign 客户端
public interface OrderFeignClient {
@GetMapping("/order/test")
String test();
}
- 整合熔断框架
只是为了可以看到效果,这里不会具体介绍熔断相关的知识
导入sentinel的依赖
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2023.0.1.0</version>
</dependency>
添加配置
yaml
feign:
sentinel:
enabled: true