Spring Cloud中,Feign和Ribbon在整合了Hystrix后,可能会出现首次调用失败的问题,要如何解决该问题呢?
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}
造成该问题的原因
Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(由于Ribbon是懒加载的,在首次请求时,才会开始初始化相关类),这个响应时间可能就大于1秒了。知道原因后,我们来总结一下解决方案。以feign为例,解决方案有如下四种。
方法一、将Hystrix超时设长
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
该配置是让Hystrix的超时时间改为5秒,这是最容易想到的办法,不过有点治标不治本。
方法二、禁用Hystrix超时
hystrix.command.default.execution.timeout.enabled: false
方法三、为Feign禁用Hystrix该配置,用于禁用Hystrix的超时时间,一般不建议使用。
全局禁用
feign.hystrix.enabled: false
局部禁用索性禁用feign的hystrix,该做法比较极端,除非一些特殊场景,不推荐使用。
为名为microservice-provider-user
的Feign Client禁用Hystrix
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
@GetMapping("/users/{id}")
User findById(@PathVariable("id") Long id);
}
class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
方法四、Ribbon配置饥饿加载(最佳)
从Dalston开始,Ribbon支持配置eager load实现在启动时就初始化Ribbon相关类。
然后在调用方服务yml增加下面的配置(A调用B, 就在A配置):
ribbon:
eager-load:
enabled: true
clients: client1, client2, client3
Dalson之前版本可以通过一些Hack的机制实现eager load,不过成本略高,不建议,这里就不贴了。
但是这种方法肯存在一定的风险,feign的contributor有提到他们之所以用lazy creation是因为不这么做的话在某些特定场景下会存在问题。
方法五、模拟请求进行warm up
基本思路:在spring容器初始化后,找到所有实现了FeignClient 的bean,主动发起任意请求,该请求会导致feign client的真正初始化。
step1. 对feign client的接口添加方法
@GetMapping("/actuator/health")
String heartbeat();
step2. 添加ApplicationListener在spring context加载完后,找到所有的feign client,并通过反射执行一次heart beat,此时便会取巧地触发feign client的初始化。
@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!");
}
}
扩展思考
1.FeignClient的初始化过程;
2.FeignClient初始化各阶段的消耗时长,进而具体哪一步耗时最长(是否是注册和发现);
3.当FeignClient调用的服务不在线时,能否保证方法三仍旧有效;
4.切入到FeignClient真实创建过程的初始化,而非通过使用client发起请求模拟地达到这个目的。
参考文章链接:
https://www.itmuch.com/spring-cloud-feign-ribbon-first-request-fail/
https://blog.youkuaiyun.com/qq83833224/article/details/86680664