Spring Cloud項目啓動後,首次使用 FeignClient 請求每每會消耗大量時間,並有必定機率所以致使請求超時。java
基本就是兩個問題:git
仔細觀察日誌,會發現本質上是由於FeignClient 的初始化花費了大量時間。github
2019-01-28 16:19:46.074 INFO 3740 --- [nio-9790-exec-2] s.c.a.AnnotationConfigApplicationContext : Refreshing SpringClientFactory-ms-dyh-manufacturer: startup date [Mon Jan 28 16:19:46 CST 2019]; parent: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@15b986cd 2019-01-28 16:19:46.411 INFO 3740 --- [nio-9790-exec-2] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring 2019-01-28 16:19:46.671 INFO 3740 --- [nio-9790-exec-2] c.netflix.config.ChainedDynamicProperty : Flipping property: ms-dyh-manufacturer.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2019-01-28 16:19:46.715 INFO 3740 --- [nio-9790-exec-2] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-ms-dyh-manufacturer 2019-01-28 16:19:46.776 INFO 3740 --- [nio-9790-exec-2] c.netflix.loadbalancer.BaseLoadBalancer : Client: ms-dyh-manufacturer instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ms-dyh-manufacturer,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null 2019-01-28 16:19:46.783 INFO 3740 --- [nio-9790-exec-2] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater 2019-01-28 16:19:46.810 INFO 3740 --- [nio-9790-exec-2] c.netflix.config.ChainedDynamicProperty : Flipping property: ms-dyh-manufacturer.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2019-01-28 16:19:46.811 INFO 3740 --- [nio-9790-exec-2] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client ms-dyh-manufacturer initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ms-dyh-manufacturer,current list of Servers=[192.168.0.114:8360],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:192.168.0.114:8360; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] ]}ServerList:ConsulServerList{serviceId='ms-dyh-manufacturer', tag=null}
事實上在15年的時候已經有人提出這個問題 issue,當前這個issue還處於open狀態。
能夠發現,這哥們判斷的緣由跟咱們判斷的一致:儘管feign client是在啓動時被建立,但真正的初始化倒是在首次使用feign client的時候進行的。web
### Hystrix 配置 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 5000
理論上這是一個治標的辦法,這樣處理可以解決超時的問題,但沒法解決首次花費時間長的問題。同時由於須要將熔斷器的超時時間設置得更長,等價於在必定程度上限制了熔斷器的適用範圍。
因此可用這個方法,但不推薦這個方法。spring
這個方法簡直就是傻子才選的,不描述了。app
基本思路:在spring容器初始化後,找到全部實現了FeignClient 的bean,主動發起任意請求,該請求會致使feign client的真正初始化。ide
step1. 對feign client的接口添加方法svg
@GetMapping("/actuator/health") String heartbeat();
step2. 添加ApplicationListener在spring context加載完後,找到全部的feign client,並經過反射執行一次heart beat,此時便會取巧地觸發feign client的初始化。ui
@Component public class EarlyInitFeignClientOnContextRefresh implements ApplicationListener<ContextRefreshedEvent> { Logger logger = LoggerFactory.getLogger(EarlyInitFeignClientOnContextRefresh.class); @Autowired() @Qualifier("cachingLBClientFactory") CachingSpringLoadBalancerFactory factory; @Override public void onApplicationEvent(ContextRefreshedEvent event) { ApplicationContext applicationContext = event.getApplicationContext(); Map<String, Object> beans = applicationContext.getBeansWithAnnotation(FeignClient.class); for (Map.Entry<String, Object> entry : beans.entrySet()) { Class<?> clazz = entry.getValue().getClass(); try { Method method = null; method = clazz.getMethod("heartbeat"); method.invoke(entry.getValue()); logger.warn("init feign client: " + clazz.getName()); } catch (NoSuchMethodException e) { logger.warn("init feign client fail: no method of heartbeat in " + clazz.getName()); } catch (IllegalAccessException e) { logger.warn("init feign client fail: IllegalAccessException of " + clazz.getName()); } catch (InvocationTargetException e) { logger.warn("init feign client fail: InvocationTargetException of " + clazz.getName()); } catch (Exception e){ logger.error(e.getMessage()); } } logger.info("init feign client done!"); } }
可是這種方法肯存在必定的風險,feign的contributor有提到他們之因此用lazy creation是由於不這麼作的話在某些特定場景下會存在問題。
.net