FeignClient 第一次调用耗时长响应慢

项目场景:

政务管理系统,采用spring-cloud微服务架构,模块间采用feign进行互相调用,在其他模块调用用户管理模块查询用户信息时,在应用启动后第一次调用与后续调用表现出巨大的耗时差异,第一次调用耗时很长。


问题描述

基于jdk-8环境,spring-boot2.x、spring-cloud微服务组件3.x版本框架下,在服务启动完成后,服务间的第一次调用耗时很长响应很慢,后续调用不存在该现象。该问题出现后,鉴于维护项目环境的稳定,未在项目的基础上进行该问题调试。于是从spring cloud openfeign拉取示例代码,在本地模拟该问题进行调试,由于原示例代码中没有eureka注册中心模块,本地调试在该示例基础上添加了注册中心模块,关键代码如下:

1. eureka注册中心

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

}

2. feign-server

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class HelloServerApplication {
    @Autowired
    DiscoveryClient client;

    @RequestMapping("/")
    public String hello() {
        List<ServiceInstance> instances = client.getInstances("HelloServer");
        ServiceInstance selectedInstance = instances
                .get(new Random().nextInt(instances.size()));
        return "Hello World: " + selectedInstance.getServiceId() + ":" + selectedInstance
                .getHost() + ":" + selectedInstance.getPort();
    }

    /**
     * 手动唤醒FeignClient接口
     *
     * @return
     */
    @RequestMapping("/warmup")
    public String warmup() {
        List<ServiceInstance> instances = client.getInstances("HelloServer");
        ServiceInstance selectedInstance = instances
                .get(new Random().nextInt(instances.size()));
        return "Hello World: warmup: " + selectedInstance.getServiceId() + ":" + selectedInstance
                .getHost() + ":" + selectedInstance.getPort();
    }

    public static void main(String[] args) {
        SpringApplication.run(HelloServerApplication.class, args);
    }
}

3. feign-client

@Slf4j
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@EnableFeignClients
public class HelloClientApplication {
    @Autowired
    HelloClient client;

    @RequestMapping("/")
    public String hello() {
        String hello = null;
        StopWatch stopWatch = new StopWatch();
        for (int i = 1; i < 10; i++) {
            stopWatch.start("hello::" + i);
            hello = client.hello();
            stopWatch.stop();
        }
        for (StopWatch.TaskInfo taskInfo : stopWatch.getTaskInfo()) {
            log.info("任务名称:{},执行耗时:{}", taskInfo.getTaskName(), taskInfo.getTimeMillis());
        }

        return hello;
    }

    public static void main(String[] args) {
        SpringApplication.run(HelloClientApplication.class, args);
    }

    @FeignClient(value = "HelloServer")
    interface HelloClient {
        @RequestMapping(value = "/", method = GET)
        String hello();


        /**
         * 手动唤醒FeignClient接口
         *
         * @return
         */
        @RequestMapping(value = "/warmup", method = GET)
        String warmup();
    }


}

4. 问题日志

2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::1,执行耗时:143
2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::2,执行耗时:3
2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::3,执行耗时:2
2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::4,执行耗时:2
2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::5,执行耗时:1
2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::6,执行耗时:1
2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::7,执行耗时:1
2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::8,执行耗时:1
2023-08-11 11:19:48.091  INFO 22560 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::9,执行耗时:1

原因分析:

通过查询spring-cloud-openfeign、spring-cloud-loadbalancer发现是spring-cloud对FeignClient执行的是“beans are lazy proxies that initialize”/“Spring Cloud LoadBalancer creates a separate Spring child context for each service id. By default, these contexts are initialised lazily, whenever the first request for a service id is being load-balanced”。除此之外,在spring-cloud-openfeign仓库中也有对于bean lazy initialize原因的部分说明


解决方案:

知道了FeignClient客户端bean是lazy initialize,可以采用如下两种方式手动激活FeignClient客户端。如果纯按效率来的话,推荐第二种;如果考虑同spring-data结合使用不出现其他问题的话,推荐第一种相对性能提升的方式。

1.手动初始化负载均衡器客户端:第一次请求性能提升平均能达到45%左右

@Configuration
@RequiredArgsConstructor
public class LoadBalancerClientsInitializer {
    private final LoadBalancerClientFactory loadBalancerClientFactory;

    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReadyEvent() {
        loadBalancerClientFactory.getInstance("HelloServer",
                HelloClientApplication.HelloClient.class);
    }
}
  • 1.2.执行日志
2023-08-11 12:21:15.963  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::1,执行耗时:86
2023-08-11 12:21:15.964  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::2,执行耗时:4
2023-08-11 12:21:15.964  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::3,执行耗时:2
2023-08-11 12:21:15.964  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::4,执行耗时:3
2023-08-11 12:21:15.964  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::5,执行耗时:3
2023-08-11 12:21:15.964  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::6,执行耗时:2
2023-08-11 12:21:15.964  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::7,执行耗时:2
2023-08-11 12:21:15.964  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::8,执行耗时:1
2023-08-11 12:21:15.964  INFO 10292 --- [nio-7211-exec-1] demo.HelloClientApplication              : 任务名称:hello::9,执行耗时:2

2.FeignClient定义固定的唤醒接口,利用CommandLineRunner在应用启动完成后自动唤醒客户端。

  • 2.1.FeignClient添加手动唤醒方法
 @FeignClient(value = "HelloServer")
    interface HelloClient {
        @RequestMapping(value = "/", method = GET)
        String hello();


        /**
         * 手动唤醒FeignClient接口
         *
         * @return
         */
        @RequestMapping(value = "/warmup", method = GET)
        String warmup();
    }

  • 2.2.FeignClient添加springboot启动成功后,自动调用客户端唤醒方法的初始化类
@Slf4j
@Component
public class FeignClientWarmup implements CommandLineRunner {

    @Resource
    private ApplicationContext applicationContext;

    @Override
    public void run(String... args) throws BeansException {
        Map<String, Object> clients = applicationContext.getBeansWithAnnotation(FeignClient.class);
        clients.forEach((k, v) -> {
            Class<?> clazz = v.getClass();
            try {
                Method method = clazz.getMethod("warmup");
                method.invoke(v);
                log.warn("warmup feign client:  " + clazz.getName());
            } catch (Exception e) {
                log.warn(String.format("warmup feign client fail, className: {}", clazz.getName()), e);
                e.printStackTrace();
            }
        });
    }
}
  • 2.3.FeignServer添加唤醒方法实现
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class HelloServerApplication {
    @Autowired
    DiscoveryClient client;

    @RequestMapping("/")
    public String hello() {
        List<ServiceInstance> instances = client.getInstances("HelloServer");
        ServiceInstance selectedInstance = instances
                .get(new Random().nextInt(instances.size()));
        return "Hello World: " + selectedInstance.getServiceId() + ":" + selectedInstance
                .getHost() + ":" + selectedInstance.getPort();
    }

    /**
     * 手动唤醒FeignClient接口
     *
     * @return
     */
    @RequestMapping("/warmup")
    public String warmup() {
        List<ServiceInstance> instances = client.getInstances("HelloServer");
        ServiceInstance selectedInstance = instances
                .get(new Random().nextInt(instances.size()));
        return "Hello World: warmup: " + selectedInstance.getServiceId() + ":" + selectedInstance
                .getHost() + ":" + selectedInstance.getPort();
    }

    public static void main(String[] args) {
        SpringApplication.run(HelloServerApplication.class, args);
    }
}

  • 2.4.执行日志
# FeignClientWarmup唤醒执行日志 
2023-08-11 14:34:16.044  WARN 14316 --- [           main] demo.FeignClientWarmup                   : warmup feign client:  demo.$Proxy91
2023-08-11 14:34:16.609  INFO 14316 --- [)-192.168.22.48] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-08-11 14:34:16.609  INFO 14316 --- [)-192.168.22.48] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-08-11 14:34:16.611  INFO 14316 --- [)-192.168.22.48] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::1,执行耗时:5
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::2,执行耗时:4
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::3,执行耗时:2
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::4,执行耗时:2
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::5,执行耗时:2
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::6,执行耗时:2
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::7,执行耗时:2
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::8,执行耗时:4
2023-08-11 14:34:40.660  INFO 14316 --- [nio-7211-exec-3] demo.HelloClientApplication              : 任务名称:hello::9,执行耗时:2


特别说明

1. 关于本文档中相关的spring-cloud-openfeign仓库中记录两个问题已经被关闭,详情请查看:问题1问题2

2. 本文档中描述的问题和文档关联的 《feign-eureka》示例工程,都是基于jdk-8/spring-boot2.x/spring-cloud-openfeign3.x环境。如果用户环境已经是基于jdk-17/spring-boot3.x/spring-cloud-openfeign4.x,spring官方已经提供了预先装载客户端的相关配置,参照地址:Eager loading of LoadBalancer contexts

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mister-big

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值