蚂蚁一面:Feign 第一次调用为什么会很慢?

今天咱们来聊一个很多人面试经常被问,但答得稀里糊涂的问题——**Feign 第一次调用为什么会很慢?**你可能也经历过:接口调试好好的,第一次请求突然卡住,等了好几秒,第二次、第三次秒回,咋回事?是不是Feign有bug?其实不是,它背后有一些机制,咱们今天就给它扒一扒。

1 开门见山:第一次真的慢!

先说结论:Feign 第一次调用慢,通常是因为动态代理、懒加载、类初始化以及网络连接预热等多个因素叠加造成的。

我自己也在几个微服务项目里踩过这个坑,尤其是使用 Spring Cloud 的时候,服务刚启动完马上测试接口,第一次请求那是真的能等好几秒,特别是用 RestTemplate 调用不会慢,Feign 调用就卡一下。

那到底卡哪了?

2 你以为的Feign,其实是Spring的“把戏”

咱们先来捋清楚Feign的核心逻辑,它本质是用 Java 的动态代理,帮你在接口上织入逻辑,然后配合 Spring Boot 的注入机制,把它变成一个可调用的 bean。

简单点说,你写了个接口:

@FeignClient(name = "user-service")
public interface UserClient {
    @GetMapping("/user/{id}")
    User getUserById(@PathVariable("id") Long id);
}

你以为这个接口马上就能调用?其实不是,Spring Boot 启动的时候并没有马上实例化这个接口,而是等你第一次用到它的时候才开始执行一系列操作:找注册中心、解析配置、生成代理、连接目标服务……这几个步骤加起来,一慢就慢在第一发请求。

3 关键点:JDK动态代理+懒加载

这事其实跟“懒人策略”有关。Feign 默认的行为是懒加载(懒得加载),它会在真正调用的时候才去生成实际的代理对象。而代理对象是怎么来的呢?Feign 使用了 JDK 的动态代理机制,这玩意儿在第一次生成的时候会触发一系列反射操作,比如生成代理类、构建 MethodHandler、包装 HTTP 客户端调用等等。

这部分初始化逻辑,在你第一次 userClient.getUserById(1L) 的时候才开始跑。

那这一步到底干了些啥?看看简化版源码:

Feign.Builder builder = Feign.builder()
    .decoder(new JacksonDecoder())
    .encoder(new JacksonEncoder())
    .target(UserClient.class, "http://user-service");

这一串代码其实就是在帮你动态生成 UserClient 的代理类,它要做的事情挺多:

  • 编码请求参数

  • 组装URL

  • 调用底层HTTP客户端(默认是OkHttp或HttpURLConnection)

  • 处理响应,反序列化为User对象

第一次这些逻辑都要初始化、加载class、创建对象,必然慢。

4 再慢一点:底层HTTP连接也要“热身”

如果你用了OkHttp或者Apache HttpClient,那第一次请求很可能还会卡在连接池建立DNS解析阶段。

这两个动作都是懒执行的,比如第一次访问 http://user-service,DNS 要解析、TCP 要连接、SSL(如果是https)还得握个手,这个成本都在第一次请求里了。

OkHttp 为例:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("http://user-service/user/1")
    .build();
Response response = client.newCall(request).execute();

这段代码你多跑几次就知道,第一次最慢,后面就快了,原因同上。

如果你在 Spring Cloud 里配了 ribbon 或 loadbalancer,其实还多了一层负载均衡客户端初始化的逻辑,也得考虑进去。

5 真正解决之道:提前初始化

如果你真想解决这个“首发慢”的问题,可以从两个角度下手:

1)预热机制我测试项目里最常用的做法,就是在服务启动后自动发一次请求,比如用 CommandLineRunner 写个请求逻辑,调用一次常用的Feign接口,让它“醒醒”。

@Component
public class FeignPreloader implements CommandLineRunner {

    @Autowired
    private UserClient userClient;

    @Override
    public void run(String... args) {
        try {
            userClient.getUserById(1L);
        } catch (Exception ignored) {
        }
    }
}

别小看这一发请求,它能触发代理初始化、类加载、连接建立,真正的“热身动作”。

2)禁用懒加载(不推荐)有些人喜欢暴力解决,直接关掉懒加载,比如在配置里加上:

feign:
  lazy-initialization: false

但这个方案我个人不太建议,理由很简单:如果你有几十个Feign Client,一次全初始化,会大大拉长服务启动时间,启动时间动不动多几秒,对 CI/CD 或线上故障恢复都不友好。

6 说点细节

对了,还有些朋友喜欢用 OpenFeign + Spring Boot 最新版,那注意了,从某些版本开始(Spring Cloud 2021以后)Feign 默认开启懒加载,这个跟你用的Spring版本有关系。如果你遇到特别明显的第一次卡顿,不妨看看是不是升级版本引起的。

另外,如果你底层 HTTP 客户端换成了 WebClient、或者用了某些支持连接复用的增强库,也可能能减轻这个问题,但本质原理还是一样:初始化很贵,预热能救命。

好了,今天这个问题咱们就聊到这。如果你之前也被这个“第一次调用慢”搞懵了,那现在应该清楚了:不是Feign本身慢,而是它在第一次调用之前“事太多”——生成代理、建立连接、初始化配置,一起扎堆上。

想优化?预热就是最好用的方法之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值