本文次要分享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-3pre检测申请是应用DispatcherServlet还是ZuulServlet
Servlet30WrapperFilter-2pre在Servlet 3.0下,转化RequestWrapper,Zull默认应用Servlet 2.5的RequestWrapper
FormBodyWrapperFilter-1pre将request转化为FormBodyRequestWrapper,它能够解析表单数据
SendErrorFilter0error解决流程中呈现的谬误
DebugFilter1pre设置申请过程是否开启debug
PreDecorationFilter5pre依据申请uri决定调用哪一个route过滤器
RibbonRoutingFilter10route如果通过ServiceId转发申请,则应用这个route过滤器
SimpleHostRoutingFilter100route如果通过url转发申请,则用这个route过滤
SendForwardFilter500route如果应用forward转发申请,则用这个route过滤
LocationRewriteFilter900post重写Http的Location头部到Zuul的URL
SendResponseFilter1000post发送响应数据到客户端

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.ConnectTimeoutribbon.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。

如果您感觉本文不错,欢送关注我的微信公众号,您的关注是我保持的能源!