共计 8026 个字符,预计需要花费 21 分钟才能阅读完成。
本文次要分享 Zuul 的应用和原理。
因为工作须要,我第一个深刻理解的 SpringCloud 组件其实是 Zuul,心愿这篇文章能说分明 Zuul 的相干实现原理。
Zuul 通过 ZuulFilter 对申请进行拦挡,过滤,转发等操作。ZuulFilter 也是提供给咱们扩大的接口。
ZuulFilter 有四种类型
pre:在申请被路由之前调用,次要负责过滤,request 申请解决等工作
route:负责申请路由,转发工作
post:负责发送响应到客户端
error:下面流程产生谬误时被调用,做一些异样善后工作
Zuul 的整体流程在 ZuulServlet 或 ZuulServletFilter,这两个类性能根本一样,默认应用的是 ZuulServlet,在 ZuulServerAutoConfiguration 初始化。
ZuulServlet#service
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {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();}
}
整体流程如下
pre --> route --> post --> 客户端
| | |
| | |
error error error
| |
| |
post post
留神:ZuulServlet#init -> ZuulRunner#init,该办法会为以后线程结构一个 RequestContext,并设置 Request,Response。
咱们能够增加新的 ZuulFilter 实现咱们须要的性能,不过理解 Zuul 自带的 ZuulFilter 能够帮忙咱们更深刻理解 Zuul
过滤器 | order | 类型 | 形容 |
---|---|---|---|
ServletDetectionFilter | -3 | pre | 检测申请是应用 DispatcherServlet 还是 ZuulServlet |
Servlet30WrapperFilter | -2 | pre | 在 Servlet 3.0 下,转化 RequestWrapper,Zull 默认应用 Servlet 2.5 的 RequestWrapper |
FormBodyWrapperFilter | -1 | pre | 将 request 转化为 FormBodyRequestWrapper,它能够解析表单数据 |
SendErrorFilter | 0 | error | 解决流程中呈现的谬误 |
DebugFilter | 1 | pre | 设置申请过程是否开启 debug |
PreDecorationFilter | 5 | pre | 依据申请 uri 决定调用哪一个 route 过滤器 |
RibbonRoutingFilter | 10 | route | 如果通过 ServiceId 转发申请,则应用这个 route 过滤器 |
SimpleHostRoutingFilter | 100 | route | 如果通过 url 转发申请,则用这个 route 过滤 |
SendForwardFilter | 500 | route | 如果应用 forward 转发申请,则用这个 route 过滤 |
LocationRewriteFilter | 900 | post | 重写 Http 的 Location 头部到 Zuul 的 URL |
SendResponseFilter | 1000 | post | 发送响应数据到客户端 |
Zuul 中反对三种转发配置
# serviceId 转发
zuul.routes.goods-service.path=/goods-service/**
zuul.routes.goods-service.serviceId=goods-service
# url 转发
zuul.routes.user-service.path=/user-service/**
zuul.routes.user-service.url=http://localhost:9002/
# forward 转发
zuul.routes.config.path=/config/**
zuul.routes.config.url=forward:/config
别离由 RibbonRoutingFilter,SimpleHostRoutingFilter,SendForwardFilter 解决。
上面看一下外围 ZuulFilter 的实现。
先看一下 PreDecorationFilter。
PreDecorationFilter#run -> CompositeRouteLocator#getMatchingRoute -> SimpleRouteLocator#getMatchingRoute
protected Route getSimpleMatchingRoute(final String path) {
...
String adjustedPath = adjustPath(path);
// #1
ZuulRoute route = getZuulRoute(adjustedPath);
// #2
return getRoute(route, adjustedPath);
}
#1
SimpleRouteLocator#routes 是一个 Map 援用,键值对为下面配置中的 path 和 ZuulRoute,ZuulRoute 中蕴含了 serviceId,url,stripPrefix 等配置信息
getZuulRoute 办法中应用 AntPathMatcher 匹配申请 url 与配置 path。#2
应用 ZuulRoute#getRoute 结构对应的 Route
咱们也能够继承 SimpleRouteLocator 并重写 getRoute 做一些个性化解决。
RibbonRoutingFilter 负责解决 serviceId 转发,它集成了 Ribbon 和 hystrix 组件,提供负载平衡和熔断等性能。
RibbonRoutingFilter#run -> forward
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {Map<String, Object> info = this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
// #1
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
// #2
ClientHttpResponse response = command.execute();
this.helper.appendDebug(info, response.getRawStatusCode(),
response.getHeaders());
return response;
}
catch (HystrixRuntimeException ex) {return handleException(info, ex);
}
}
#1
RibbonCommand 继承了 HystrixExecutable 接口,有 RestClientRibbonCommand,OkHttpRibbonCommand,HttpClientRibbonCommand 实现类,都继承于 AbstractRibbonCommand。
RibbonCommandFactory 是工厂类,对应实现类为 RestClientRibbonCommandFactory,OkHttpRibbonCommandFactory,HttpClientRibbonCommandFactory,别离结构对应的 RibbonCommand,都继承于 AbstractRibbonCommandFactory,默认应用 HttpClientRibbonCommandFactory,在 RibbonCommandFactoryConfiguration 中初始化。#2
执行 RibbonCommand
HttpClientRibbonCommandFactory#create
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
FallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId());
final String serviceId = context.getServiceId();
// #1
final RibbonLoadBalancingHttpClient client = this.clientFactory
.getClient(serviceId, RibbonLoadBalancingHttpClient.class);
client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
// #2
return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties,
zuulFallbackProvider, clientFactory.getClientConfig(serviceId));
}
#1
结构一个 RibbonLoadBalancingHttpClient,实现了 IClient 接口,负责真正发动申请的操作,有对应子类 OkHttpLoadBalancingClient,RestClient,RibbonLoadBalancingHttpClient,都继承于 AbstractLoadBalancerAwareClient。默认应用的是 RibbonLoadBalancingHttpClient,在 HttpClientRibbonConfiguration 初始化。
RibbonCommand 通过实现 HystrixExecutable 实现熔断,而负载平衡性能则是通过 AbstractLoadBalancerAwareClient 实现的。
留神 client 是与 serviceId 绑定的。所以 ribbon.ConnectTimeout
,ribbon.ReadTimeout
能够配置在一个 serviceId 上,如goods-service.ribbon.ReadTimeout
。
#2
结构一个 HttpClientRibbonCommand
留神这里应用 serviceId 作为 hystrix 的 commandkey,也就是说 Zuul 反对对利用级别做熔断,但不反对 url 级别的熔断。
回到 RibbonRoutingFilter#forward 办法 #2
步骤,HystrixExecutable#execute -> AbstractRibbonCommand#run -> AbstractLoadBalancerAwareClient#executeWithLoadBalancer
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
// #1
return command.submit(new ServerOperation<T>() {public Observable<T> call(Server server) {
// #2
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
// #3
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {return Observable.error(e);
}
}
})
.toBlocking()
.single();}
...
}
#1
LoadBalancerCommand#submit 生成一个 Observable,它是 RxJava 提供的类,示意一个可察看对象,它能够产生数据,最初执行 toBlocking().single()会阻塞直到产生第一个后果才返回。#2
通过 server,获取真正申请的 url#3
通过 IClient#execute 调用上游服务
LoadBalancerCommand#submit
public Observable<T> submit(final ServerOperation<T> operation) {
...
// #1
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {public Observable<T> call(Server server) {context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
// #2
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {public Observable<T> call(final Server server) {
...
// #3
return operation.call(server).doOnEach(new Observer<T>() {...});
}
});
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
if (maxRetrysNext > 0 && server == null)
o = o.retry(retryPolicy(maxRetrysNext, false));
return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {...});
}
#1
这里生成一个 Observable,这个 Observable 每次重试都应用 selectServer 办法从新抉择上游的一个服务实例,再发动申请。#2
这里也生成一个 Observable,这个 Observable 每次重试都在同一个 Server 内发动申请。#3
operation 是 AbstractLoadBalancerAwareClient#executeWithLoadBalancer 办法 #1
步骤中 submit 办法传递的匿名类,这里获取到 server 后便可通过该匿名类发动 Http 申请
Observable.just(…).concatMap(…)也是 RxJava 提供的语法,just 办法生成只有一个数据的 Observable,concatMap 办法对该 Observable 数据进行转化,返回另一个 Observable,有趣味的同学也能够理解一下 RxJava 的常识。
最初看一下如何通过 url 转发
SimpleHostRoutingFilter#run -> forward
private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
String uri, HttpServletRequest request, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity)
throws Exception {
// #1
...
InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
contentType);
HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params,
request);
try {log.debug(httpHost.getHostName() + "" + httpHost.getPort() +" "
+ httpHost.getSchemeName());
// #2
CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost,
httpRequest);
this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
revertHeaders(zuulResponse.getAllHeaders()));
return zuulResponse;
}
finally {}}
#1
对 url,contentType 做一些解决,结构一个新的 HttpRequest
留神,这里须要读取原 request 的 InputStream,如果在该步骤前曾经读取了 InputStream,这里就读取不到了,会导致转发的 http body 为空。#2
通过 CloseableHttpClient(httpclient)转发申请
CloseableHttpClient 通过 newClient 办法结构,会设置 timeout 等配置。
能够看到,serviceId,url 的转发机制不同,所以对应超时等配置也不同。
Zuul 的解析就说到这里。Spring Cloud Gateway 是 Spring 提供的新一代网关,基于 webflux 实现异步申请,前面分享 Spring Reactive 时再写文章解析 Spring Cloud Gateway。
如果您感觉本文不错,欢送关注我的微信公众号,您的关注是我保持的能源!