SpringCloud源码解析 -- RestTemplate与@LoadBalanced
SpringCloud源码解析 -- Spring Cloud Config与@RefreshScope
SpringCloud源码解析 -- Zuul实现原理
SpringCloud源码解析 -- Spring Cloud Sleuth原理探究
SpringCloud源码解析 -- Eureka原理探究
本文通过阅读源码,分享Zuul的实现原理。源码分析基于Spring Cloud Hoxton
因为工作需要,我第一个深入了解的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
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。
如果您觉得本文不错,欢迎关注我的微信公众号,您的关注是我坚持的动力!