关于springcloud:SpringCloud源码解析-Zuul实现原理

9次阅读

共计 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.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。

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

正文完
 0