OpenFeign介绍
1、定义
openFeign 是 Spring Cloud 提供的声明式 HTTP 客户端,核心作用是简化微服务之间的 HTTP 远程调用开发:
-
开发者只需定义接口并添加
@FeignClient注解,无需手动编写 HTTP 请求(如 HttpClient、RestTemplate),Feign 会自动生成接口的实现类,完成请求的发送与响应解析。 -
内置了负载均衡(整合 Ribbon/Nacos)、请求重试、拦截器等能力,是微服务间调用的主流方案。
2、优势
声明式编程:通过接口 + 注解定义调用规则,代码简洁易维护;
自动集成负载均衡 :无需额外配置,可直接调用微服务名称(如 lb://item-service),自动选择实例;
可扩展:支持自定义拦截器、编码器 / 解码器,适配不同的调用场景。
实现步骤
下面介绍项目中使用OpenFeign的步骤:
1、在调用方微服务的 pom.xml 中引入 OpenFeign 依赖(以 Spring Cloud Alibaba 为例) 2、开启 Feign 功能 在调用方微服务的启动类上添加 @EnableFeignClients 注解,开启 Feign 客户端扫描 3、定义 Feign 客户端接口 在公共 API 模块(如 hm-api)中定义 Feign 接口,通过注解声明调用规则 4、业务中直接注入使用 在调用方微服务的 Service/Controller 中,直接注入 Feign 接口并调用,Spring 会自动生成实现类:
无需手动处理 HTTP 请求、响应解析,Feign 会自动完成:
-
根据服务名
cart-service从注册中心获取实例列表; -
自动实现负载均衡(默认整合 Ribbon);
-
发起 HTTP 请求,解析响应为指定返回类型(如
ItemDTO)。
自定义 Feign 请求拦截器
微服务间通过 OpenFeign 调用时,默认不会携带用户信息(如用户 ID),导致下游微服务的 UserInfoInterceptor 无法从请求头读取用户信息,进而无法识别当前登录用户。
解决方案:自定义 Feign 请求拦截器(RequestInterceptor) 通过实现 Feign 的 RequestInterceptor 接口,让所有 Feign 调用自动携带用户信息到请求头,下游微服务仍通过原有拦截器 + ThreadLocal 读取,实现用户信息的全链路传递。
java
@Configuration
public class DefaultFeignConfig {
// 注册Feign请求拦截器,自动携带用户信息
@Bean
public RequestInterceptor userInfoRequestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
// 1. 从ThreadLocal中获取当前线程的用户ID(由UserInfoInterceptor存入)
Long userId = UserContext.getUser();
if(userId == null) {
// 无用户信息,直接跳过
return;
}
// 2. 将用户ID写入请求头(key与微服务拦截器约定为user-info)
template.header("user-info", userId.toString());
}
};
}
}
线程复用问题
一、Feign 本身是声明式调用框架,只负责定义调用规则(接口 + 注解),不直接实现 HTTP 请求发送,而是通过「适配不同的 HTTP 客户端」完成实际的请求发送。
Feign 默认使用 JDK 自带的 HttpURLConnection 作为底层 HTTP 客户端,是不支持连接词的,所以每次请求都要新建tcp连接(3次握手),性能比较低,而微服务间调用通常是高频次的(比如订单服务调用商品服务、购物车服务、支付服务)。从而我们总结它的不足是:
网络开销大:TCP 三次握手 / 四次挥手的耗时会累加,降低接口响应速度;
资源浪费:大量临时连接会占用服务器端口、内存等资源,高并发下易引发端口耗尽、GC 频繁等问题;
性能瓶颈:默认实现无连接池,无法应对高并发的微服务调用场景。
二、故而我们可以使用OKHttp来替代HttpURLConnection作为底层的http客户端,这是行业主流实践,核心优势:
-
连接池复用 :默认开启
ConnectionPool,默认保持 5 个空闲连接,超时时间 5 分钟,可自定义; -
性能更优:OKHttp 基于 NIO 实现,比 Apache HttpClient 更轻量,且支持 HTTP/2、缓存等高级特性;
-
易用性高:配置简洁,Spring Cloud 对 OKHttp 有完善的集成支持。
三、实现
1、引入 OKHttp 依赖 在调用方微服务的 pom.xml 中引入依赖(替换默认的 HttpURLConnection) 2、开启 OKHttp 并配置连接池 在 application.yml 中配置 Feign 使用 OKHttp,并自定义连接池参数:
bash
feign:
okhttp:
enabled: true # 开启OKHttp作为底层客户端
client:
config:
default: # 全局配置(也可指定单个Feign客户端)
connectTimeout: 1000 # 连接超时时间(1秒)
readTimeout: 3000 # 读取超时时间(3秒)
httpclient:
enabled: false # 关闭默认的HttpClient(避免冲突)
max-connections: 200 # 连接池最大连接数
max-connections-per-route: 50 # 每个域名的最大连接数
3、自定义 OKHttp 连接池(可选,精细化配置) 即自定义public class FeignOkHttpConfig 类
OpenFeign 日志配置
OpenFeign 的日志功能是微服务间调用排障的核心工具,但默认处于关闭状态,需通过「日志级别配置 + 日志框架级别调整」两步才能生效。
1、 Feign 自身的日志级别(4 级)
Feign 定义了 4 种日志粒度,控制要记录哪些请求 / 响应信息,默认是 NONE(无日志):
| 级别 | 记录内容 | 适用场景 |
|---|---|---|
| NONE | 不记录任何日志(默认值) | 生产环境(减少日志开销) |
| BASIC | 仅记录请求方法、URL、响应状态码、执行时间 | 快速排查调用是否成功 / 耗时 |
| HEADERS | 在 BASIC 基础上,额外记录请求 / 响应头信息(如 user-info、Content-Type) | 排查请求头传递问题 |
| FULL | 记录所有明细(请求头、请求体、响应头、响应体、元数据) | 开发 / 测试阶段排障(核心场景) |
2、日志框架的级别控制
Feign 日志依赖 Slf4j 等日志框架(如 Logback/Log4j2),只有将 FeignClient 所在包的日志级别设为 DEBUG,Feign 日志才能真正输出(即使 Feign 级别设为 FULL,日志框架级别不够也看不到)。
1、义 Feign 日志级别配置类(hm-api 模块) 2、让配置生效(二选一) 方式 1:局部生效(仅对指定 FeignClient 生效) 方式 2:全局生效(对所有 FeignClient 生效) 3、配置日志框架级别(关键!) 在调用方微服务的 application.yml 中,将 FeignClient 所在包的日志级别设为 DEBUG:
bash
logging:
level:
# 格式:FeignClient接口所在包 = DEBUG
com.hmall.api.client: DEBUG
# 若想更精准,可指定单个FeignClient类
# com.hmall.api.client.ItemClient: DEBUG
讨论
1、为什么被调用微服务不能直接读取调用方的 ThreadLocal? 核心原因是「微服务间调用是全新的 HTTP 请求,对应全新的线程和 ThreadLocal」。
举例:订单服务的 ThreadLocal 存了用户 ID=101,调用购物车服务时,购物车服务的处理线程是全新的,它的 ThreadLocal 是空的,必须通过请求头传递用户 ID,再由购物车服务的拦截器存入自己的 ThreadLocal。
2、关于 FeignClient 携带用户信息的理解 FeignClient 是 Spring 自动生成的 HTTP 客户端,默认不会携带任何用户信息(因为它不知道用户信息存在哪里),所以需要通过RequestInterceptor拦截器,在请求发送前把用户信息写入请求头。
调用方微服务ThreadLocal(已有用户ID) → Feign拦截器读取 → 写入请求头 → 被调用方微服务接收请求 → 被调用方拦截器读取请求头 → 存入自己的ThreadLocal
下面介绍Feign请求拦截器(RequestInterceptor)和被调用者拦截器(UserInfoInterceptor)的区别:
| 维度 | Feign 请求拦截器(RequestInterceptor) |
被调用者拦截器(UserInfoInterceptor) |
|---|---|---|
| 所属框架 | OpenFeign(HTTP 客户端) | SpringMVC(HTTP 服务端) |
| 执行位置 | 调用方微服务(发起请求前) | 被调用方微服务(接收请求后) |
| 核心作用 | 读取调用方 ThreadLocal 的用户信息,写入请求头 | 读取请求头的用户信息,写入自己的 ThreadLocal |
| 执行时机 | Feign 发起 HTTP 请求前 | 被调用方处理请求前(preHandle) |
| 核心目标 | 传递用户信息 | 解析并存储用户信息 |
4、微服务和拦截器的对应关系?
1)所有微服务共用同一个拦截器(代码层面),每个微服务启动后生成自己的拦截器实例。
2(拦截器代码复用:UserInfoInterceptor 和 Feign 的RequestInterceptor 都写在公共模块(hm-common/hm-api),所有微服务引入该模块后,都会加载这个拦截器代码。 3)实例独立:每个微服务启动时,Spring 会为自己的上下文创建拦截器实例(比如订单服务有一个UserInfoInterceptor实例,购物车服务也有一个独立的实例),但逻辑完全一致。
5、一个微服务通常有多个业务模块(如商品服务有 "商品查询""库存扣减""分类管理"),建议按业务模块拆分 Feign 接口。拆分后更易维护(比如库存接口变更,只改 StockClient 即可)。 6、 Feign 接口放公共模块的意义
比如 ItemClient(商品服务的 Feign 接口),订单服务、购物车服务、支付服务都需要调用商品服务,只需引入 hm-api 模块,直接注入 ItemClient 即可使用,无需重复编写。
总结:本文介绍了OpenFeigin的定义和优势,并介绍项目中使用OpenFeign的步骤;并介绍自定义 Feign 请求拦截器,实现让所有 Feign 调用自动携带用户信息到请求头;横向对比了Feign 请求拦截器(RequestInterceptor)和被调用者拦截器(UserInfoInterceptor);还介绍了OpenFeign线程复用问题(提出用OKhttp来代替默认的 HttpURLConnection);最后介绍了 OpenFeign 的日志配置; 讨论了一些常见的思考。