美文网首页
zuul 1.x 源码解析

zuul 1.x 源码解析

作者: 丑人林宗己 | 来源:发表于2018-07-07 09:42 被阅读51次

1、阅读环境

基于spring cloud 的zuul组件,源码亦是从demo中入手,方式以debug为主。

  • zuul-core-1.3.0.jar
  • spring-cloud-netflix-core-1.3.0.RC1,jar

2、架构图

image.png

核心类图

image.png

核心类分析

从架构图可以在宏观整体上把握Zuul的架构设计关键概要部分,而从核心类图中可以更加细粒度的看出zuul设计中的关键点以及设计意图。

ZuulConfiguration

ZuulConfiguration并非是zuul-core的核心类,它是由spring cloud团队开发。

@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulConfiguration {
  ///
}

ZuulConfiguration加载了ZuulProperties实例,其实例是基于ZuulProperties方式的配置类,同时查阅源码还可以看到一些关键信息类的实例,比如ZuulServlet类实例。

@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
    ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
            this.zuulProperties.getServletPattern());
    // The whole point of exposing this servlet is to provide a route that doesn't
    // buffer requests.
    servlet.addInitParameter("buffer-requests", "false");
    return servlet;
}

ZuulProxyConfiguration

ZuulProxyConfiguration继承自ZuulProxyConfiguration,内部创建了PreDecorationFilterRibbonRoutingFilterSimpleHostRoutingFilter 三个非常重要的ZuulFilter实例

    // pre filters
    @Bean
    public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {
        return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties,
                proxyRequestHelper);
    }

    // route filters
    @Bean
    public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
            RibbonCommandFactory<?> ribbonCommandFactory) {
        RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers);
        return filter;
    }

    @Bean
    public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) {
        return new SimpleHostRoutingFilter(helper, zuulProperties);
    }

几乎将所有的核心类的实例都在Configuration类中创建,代码阅读起来非常清晰明朗,这种方式就是基于Java config的bean实例化方式,上一代是基于XML方式。

ZuulServlet

ZuulServlet的实例创建与ZuulConfiguration中,该设计实现思路完全可以类比SpringMVC中的DispatchServlet

ZuulServlet中,zuul做了什么事?

    @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();//创建request上下文对象,内部基于ThreadLocal实现上下文线程隔离
            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();
        }
    }

ZuulFilter 过滤器抽象类

首先,查看 过滤器中顶级接口IZuulFilter可以发现,zuul中所指的并非是javax.servlet.filter,而是内部设计接口,命名上容易让人产生疑惑,本质原理是不同,但它们的道理是相同的——>链式过滤器。

该抽象类提供了几个非常重要的方法。

abstract public String filterType();// 过滤器类型

abstract public int filterOrder();// 过滤器排序值

boolean shouldFilter();// 是否过滤

Object run(); // 继承自IZuulFilter接口

// 通过模板方法设计模式定义了过滤器的执行方式
public ZuulFilterResult runFilter() {
       ZuulFilterResult zr = new ZuulFilterResult();
        if (!isFilterDisabled()) {
            if (shouldFilter()) {
                Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
                try {
                    Object res = run();
                    zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
                } catch (Throwable e) {
                    t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                    zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                    zr.setException(e);
                } finally {
                    t.stopAndLog();
                }
            } else {
                zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
            }
        }
        return zr;
}

PreDecorationFilter 前置过滤器

// 该方法决定了是否经过这个过滤器
// 条件是当前的RequestContext不能包含`forward.to`跟`serviceid`的值
// 注意注解信息,
@Override
public boolean shouldFilter() {
    RequestContext ctx = RequestContext.getCurrentContext();
    return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
            && !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
}
 
// 这个过滤器的处理方式是,取出请求路径,通过请求路径找到配置项中的配置信息
// 详细可以查看Router类中的信息
// 通过routerhose 或者 serviceId 来决定routerFilter
@Override
public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
    Route route = this.routeLocator.getMatchingRoute(requestURI);
    
    // 注意看这段代码,设置了routerhost
    if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {
        ctx.setRouteHost(getUrl(location));
        ctx.addOriginResponseHeader(SERVICE_HEADER, location);
    }

    // 注意这段代码,设置了serviceId
    ctx.set(SERVICE_ID_KEY, location);
    ctx.setRouteHost(null);
    ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
}

SimpleHostRoutingFilter && RibbonRoutingFilter 转发过滤器

public class SimpleHostRoutingFilter extends ZuulFilter {
      // 套接字超时默认时间
    private static final DynamicIntProperty SOCKET_TIMEOUT = DynamicPropertyFactory
            .getInstance()
            .getIntProperty(ZuulConstants.ZUUL_HOST_SOCKET_TIMEOUT_MILLIS, 10000);
// 连接超时默认时间
    private static final DynamicIntProperty CONNECTION_TIMEOUT = DynamicPropertyFactory
            .getInstance()
            .getIntProperty(ZuulConstants.ZUUL_HOST_CONNECT_TIMEOUT_MILLIS, 2000);

    @Override
    public String filterType() {
        return ROUTE_TYPE;
    }

    @Override
    public int filterOrder() {
        return SIMPLE_HOST_ROUTING_FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        // RequestContext上下文中包含了routehost信息并sendZuulResponse为true
        return RequestContext.getCurrentContext().getRouteHost() != null
                && RequestContext.getCurrentContext().sendZuulResponse();
    }

    @Override
    public Object run() {

        ....

        try {
            // 从源码可以看到,本质上,请求也是通过httpclient处理的
            CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
                    headers, params, requestEntity);
            setResponse(response);
        }
        catch (Exception ex) {
            throw new ZuulRuntimeException(ex);
        }
        return null;
    }
}


//注意网飞公司的项目有一个叫Ribbon的负载均衡项目,那么这里的过滤器是否也支持负载均衡呢?
public class RibbonRoutingFilter extends ZuulFilter {

    private static final Log log = LogFactory.getLog(RibbonRoutingFilter.class);

    
    @Override
    public String filterType() {
        return ROUTE_TYPE;
    }

    @Override
    public int filterOrder() {
        return RIBBON_ROUTING_FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        // RequestContext上下文中没有routehost,且serviceId不为空,并sendZuulResponse为true
        RequestContext ctx = RequestContext.getCurrentContext();
        return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
                && ctx.sendZuulResponse());
    }

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        this.helper.addIgnoredHeaders();
        try {
            // 通过查看RibbonCommand接口,可以发现该接口扩展了HystrixExecutable接口,Hystrix是网飞的一个熔断器项目
            // 也就说RibbonRoutingFilter过滤器是支持熔断隔离,负载均衡等策略的转发器
            RibbonCommandContext commandContext = buildCommandContext(context);
            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());

        // 默认HttpClientRibbonCommandFactory
        // HttpClientRibbonCommand
        // AbstractRibbonCommand 
        //  ->HystrixCommand  熔断降级等策略
        //  ->AbstractLoadBalancerAwareClient 
        //      ->LoadBalancerContext
        //          ->ILoadBalancer#chooseServer 负载均衡策略
        RibbonCommand command = this.ribbonCommandFactory.create(context);
        try {
            ClientHttpResponse response = command.execute();
            this.helper.appendDebug(info, response.getStatusCode().value(),
                    response.getHeaders());
            return response;
        }
        catch (HystrixRuntimeException ex) {
            return handleException(info, ex);
        }

    }

}

通过源码分析可以非常明显的了解到,通过配置可以决定用哪个转发器,不同的转发决定了转发的策略。
SimpleHostRoutingFilter内置httpclient的普通请求方式,自有一套适用的场景,而RibbonRoutingFilter相对功能更加齐全完善,具备负载均衡/降级熔断的策略,首选上自然是RibbonRoutingFilter

SendResponseFilter

SendResponseFilter该类主要是处理header跟response

@Override
public boolean shouldFilter() {
    RequestContext context = RequestContext.getCurrentContext();
    return context.getThrowable() == null
            && (!context.getZuulResponseHeaders().isEmpty()
                || context.getResponseDataStream() != null
            || context.getResponseBody() != null);
}

@Override
public Object run() {
    try {
        addResponseHeaders();
        writeResponse();
    }
    catch (Exception ex) {
        ReflectionUtils.rethrowRuntimeException(ex);
    }
    return null;
}

核心类基本上介绍自此,其他的更多可以参照类图自行去了解,看源码处理学习项目基础架构,基本原理,同时也需要去关注他们的代码架构,,很多国外工程师写的项目在代码追求上更优质,比如大名鼎鼎的spring,设计模式满天飞。

总结

-1、本文并未涉及热加载等原理,详细的加载机制可以查阅相关类图
0、本文并未涉及httpclient具体的源码分析
1、本文并未涉及Ribbon具体的负载均衡算法
2、本文并未涉及Hystrix的熔断策略分析
3、zuul已经出了2.0,应该考虑升级优质版本
4、求技术大佬们不要动不动就发展新技术,程序员能不能对同行友善一点?

后话

下一篇应该会考虑学习分析熔断机制的原理

相关文章

网友评论

      本文标题:zuul 1.x 源码解析

      本文链接:https://www.haomeiwen.com/subject/mtvruftx.html