zuul 1.x 源碼解析

一、閱讀環境

基於spring cloud 的zuul組件,源碼亦是從demo中入手,方式以debug爲主。java

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

二、架構圖

image.png

核心類圖

image.png

核心類分析

從架構圖能夠在宏觀總體上把握Zuul的架構設計關鍵概要部分,而從核心類圖中能夠更加細粒度的看出zuul設計中的關鍵點以及設計意圖。程序員

ZuulConfiguration

ZuulConfiguration並不是是zuul-core的核心類,它是由spring cloud團隊開發。web

@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實例spring

// 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作了什麼事?app

@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,而是內部設計接口,命名上容易讓人產生疑惑,本質原理是不一樣,但它們的道理是相同的——>鏈式過濾器。負載均衡

該抽象類提供了幾個很是重要的方法。ide

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,設計模式滿天飛。

總結

-一、本文並未涉及熱加載等原理,詳細的加載機制能夠查閱相關類圖 0、本文並未涉及httpclient具體的源碼分析 一、本文並未涉及Ribbon具體的負載均衡算法 二、本文並未涉及Hystrix的熔斷策略分析 三、zuul已經出了2.0,應該考慮升級優質版本 四、求技術大佬們不要動不動就發展新技術,程序員能不能對同行友善一點?

後話

下一篇應該會考慮學習分析熔斷機制的原理