ZUUL
#网关
zuul:
ribbon:
eager-load:
enabled: true #zuul饥饿加载
host:
max-total-connections: 200
max-per-route-connections: 20
#以下两个配置也是解决zuul超时的
#和使用ribbon.ReadTimeout的区别是,如果路由配置使用service-id的方式,那么ribbon.ReadTimeout生效,如果使用url的方式,此配置生效
connect-timeout-millis: 10000
socket-timeout-millis: 10000
#配置Ribbon的超时时间
ribbon:
ReadTimeout: 10000
ConnectTimeout: 10000
# MaxAutoRetries: 1
# MaxAutoRetriesNextServer: 1
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
#配置hystrix的超时时间
thread:
timeoutInMilliseconds: 20000
访问的时候提示错误:
The Hystrix timeout of 20000ms for the command uaa-service is set lower than the combination of the Ribbon read and connect timeout, 40000ms.
分析:
Ribbon 总超时时间计算公式如下:
ribbonTimeout = (RibbonReadTimeout + RibbonConnectTimeout) * (MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)
其中,MaxAutoRetries 默认为0,MaxAutoRetriesNextServer 默认为1,所以我这里的具体值为:(10000+10000)(0+1)(1+1)=40000。
而 Hystrix 超时时间为 20000 < 40000,从逻辑上来讲,hystrixTimeout 要大于 ribbonTimeout,否则 hystrix 熔断了以后,ribbon 的重试就都没有意义了。
为什么不自己是手动重新加载Locator.dorefresh?非要用事件去刷新?这牵扯到内部的zuul内部组件的工作流程,不仅仅是Locator本身的一个变量,具体想要了解的还得去看源码。下面我们就来分析下zuul的源码看看为什么要这样做? 要讲清楚zuul的事件驱动模型,还得知道spring的事件驱动模型,因为zuul的实现正是利用了spring的事件驱动模型实现的。下面看看spring提供的事件模型图:
在zuul中有这样一个实现了ApplicationListener的监听器ZuulRefreshListener ,代码如下
private static class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> {
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent
|| event instanceof RefreshScopeRefreshedEvent
|| event instanceof RoutesRefreshedEvent) {
this.zuulHandlerMapping.setDirty(true);
}
else if (event instanceof HeartbeatEvent) {
if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {
this.zuulHandlerMapping.setDirty(true);
}
}
}
}
由此可知在发生ContextRefreshedEvent和RoutesRefreshedEvent事件时会执行this.zuulHandlerMapping.setDirty(true);
public void setDirty(boolean dirty) {
this.dirty = dirty;
if (this.routeLocator instanceof RefreshableRouteLocator) {
((RefreshableRouteLocator) this.routeLocator).refresh();
}
}
这样在spring容器启动完成后就刷新了路由规则。因此我们如果要主动刷新路由规则,只需要发布一个RoutesRefreshedEvent事件即可,代码如下
public void refreshRoute() {
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
this.publisher.publishEvent(routesRefreshedEvent);
logger.info("刷新了路由规则......");
}
我们知道spring-cloud-zuul是依赖springMVC来注册路由的,而springMVC又是在建立在servlet之上的),在servlet3.0之前使用的是thread per connection方式处理请求,就是每一个请求需要servlet容器为其分配一个线程来处理,直到响应完用户请求,才被释放回容器线程池,如果后端业务处理比较耗时,那么这个线程将会被一直阻塞,不能干其他事情,如果耗时请求比较多时,servlet容器线程将被耗尽,也就无法处理新的请求了,所以Netflix还专门开发了一个熔断的组件Hystrix 来保护这样的服务,防止其因后端的一些慢服务耗尽资源,造成服务不可用。 不过在servlet3.0出来之后支持异步servlet了,可以把业务操作放到独立的线程池里面去, 这样可以尽快释放servlet线程,springMVC本身也支持异步servlet了