OpenFeign(声明式HTTP客户端)

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客户端,这是行业主流实践,核心优势:

  1. 连接池复用 :默认开启 ConnectionPool,默认保持 5 个空闲连接,超时时间 5 分钟,可自定义;

  2. 性能更优:OKHttp 基于 NIO 实现,比 Apache HttpClient 更轻量,且支持 HTTP/2、缓存等高级特性;

  3. 易用性高:配置简洁,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 的日志配置; 讨论了一些常见的思考。

相关推荐
鹏北海5 小时前
micro-app 微前端项目部署指南
前端·nginx·微服务
心.c5 小时前
TCP协议深入解析
网络·网络协议·tcp/ip
摇滚侠5 小时前
HTTP 404 - No response body available
网络·网络协议·http
全栈工程师修炼指南5 小时前
Nginx | stream content 阶段:TCP 协议四层反向代理浅析与实践
运维·网络·网络协议·tcp/ip·nginx
Trouvaille ~6 小时前
【Linux】应用层协议设计实战(一):自定义协议与网络计算器
linux·运维·服务器·网络·c++·http·应用层协议
CSCN新手听安6 小时前
【linux】网络基础(三)TCP服务端网络版本计算器的优化,Json的使用,服务器守护进程化daemon,重谈OSI七层模型
linux·服务器·网络·c++·tcp/ip·json
REDcker6 小时前
埋点系统设计:从成熟工具到自建方案
运维·服务器·网络·用户分析·埋点·埋点系统
任白7 小时前
OSI参考模型&&TCP/IP模型
网络协议
不做菜鸟的网工7 小时前
OSPF协议笔记整理
网络协议