目的
源码分析
现象到本质的分析
从第一次请求的那个提示开始分析, 对DynamicServerListLoadBalancer 进行分析debug 找到入口
2019-08-02 15:40:26.655 INFO 5964 --- [nio-8769-exec-1] s.c.a.AnnotationConfigApplicationContext : Refreshing SpringClientFactory-service-feign: startup date [Fri Aug 02 15:40:26 CST 2019]; parent: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@ceb4bd2
2019-08-02 15:40:26.727 INFO 5964 --- [nio-8769-exec-1] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2019-08-02 15:40:26.949 INFO 5964 --- [nio-8769-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: service-feign.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-08-02 15:40:26.972 INFO 5964 --- [nio-8769-exec-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-service-feign
2019-08-02 15:40:26.998 INFO 5964 --- [nio-8769-exec-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: service-feign instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=service-feign,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-08-02 15:40:27.005 INFO 5964 --- [nio-8769-exec-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2019-08-02 15:40:27.033 INFO 5964 --- [nio-8769-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: service-feign.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-08-02 15:40:27.035 INFO 5964 --- [nio-8769-exec-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client service-feign initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=service-feign,current list of Servers=[peer1:8765],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:peer1:8765; Zone:defaultZone; 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:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@2cb46fd5
2019-08-02 15:40:28.290 INFO 5964 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: service-feign.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
debug分析到调用链
其实zuul思路就简单了ServletWrappingController将请求交给ZuulServlet
ZuulServlet集成了HttpServlet 实现了自己的service方法从而接管spring自己的spring mvc service 内部的业务逻辑
所以重点分析ZuulServlet就可以了 (spring boot auto configuration 的就不分析了)
service 方法分为几个阶段 post,route,pre,error ; ZuulFilter 里面的属性filterType定义的类型;
public class ZuulServlet extends HttpServlet {
private static final long serialVersionUID = -3374242278843351500L;
//内部维护的是FilterProcessor.getInstance() 一个实例对象进行方法的调用
private ZuulRunner zuulRunner;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
zuulRunner = new ZuulRunner(bufferReqs);
}
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
//可以理解为拦截器要preinteceptor执行的业务
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
//进行方法的调用的地方,重点关注这个地方
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
}
class FilterProcessor{
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
//获取过滤器的集合
//pre [ServletDetecationFilter,Servlet30WrapperFilter,FormBodyWrapperFilter,MyFilter,DebugFilter,
// PreDecoreationFilter] 等等一系系列的过滤器
//route [RibbonRoutingFilter, SimpleHostRoutingFilter,SendForwardFilter]
//每个过滤器会根据当前获取器定义的filterType来决定这个过滤器在那个过程执行
//例如MyFilter 设置的filterType设置了为“pre” 那么会在ZuulServlet.service()方法里面的pre对应的方法里面进行执行;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
//不同的filter开始调度执行run方法
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
}
//如果pre = route , filter 为RibbonRoutingFilter的时候会去调用我们的目标方法
class RibbonRoutingFilter extends ZuulFilter {
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
//创建CommandContext
RibbonCommandContext commandContext = buildCommandContext(context);
//调用forward执行请求
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
Map<String, Object> info = this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
//执行create的时候道理和fein是一样的, 由于上线文没有创建, 所以需要去调用NamedContextFactory创建context环境实例
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
//通过RibbonCommond执行请求
ClientHttpResponse response = command.execute();
this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
return response;
}
catch (HystrixRuntimeException ex) {
return handleException(info, ex);
}
}
}
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-ribbon
api-b:
path: /api-b/**
serviceId: service-feign
通过测试发现zull是远程调用的,也就是跨服务调用的, 通过ribbon进行客户端的接口的调用;
请求的链:
org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter#forward
com.netflix.hystrix.AbstractCommand#toObservable
com.netflix.hystrix.HystrixCommand#getExecutionObservable
org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand#run
com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)
org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient#execute
org.apache.http.impl.client.CloseableHttpClient#execute(org.apache.http.client.methods.HttpUriRequest)
org.apache.http.impl.client.CloseableHttpClient#determineTarget
回过头思考为什么请求会到达ZuulServlet呢?
结果debug发现
@EnableZuulProxy这是一个标记型的注解,会在spring的上下文注入一个zuulProxyMarkerBean,
autoConfiguration会更加是否有这个标记从而决定某些bean是否会进行自动的配置加载;
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
//如果没有开启@EnableZuulProxy那么就不加载这个类, 说明zuul接管spring mvc默认的请求就是在这个类进行了配置
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
// FIXME @Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
@Autowired
protected ZuulProperties zuulProperties;
@Autowired
protected ServerProperties server;
@Autowired(required = false)
private ErrorController errorController;
@Bean
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
}
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServlet().getServletPrefix(),
this.zuulProperties);
}
//zuul 控制器
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
//请求mapping映射器, 将请求经过DispacherServlet.doDispach映射到ZuulController进行处理
//mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
}
总结
zuul智能路由是通过ZuulServlet接管了HttpServlet的请求[zuul 注入了zuulController,以及自己的zuulHandlerMapping],
实现了自己的service方法制定自己的业务逻辑, 经过不同的Filter (ZuulFilter的实现类)
这些过滤器会在post,route,pre,error 等好几个阶段来在目标方法的前后进行执行;
最终是通过HystrixCommand来执行请求的发送获取结果;