zuul 1.x 源码解析

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、求技术大佬们不要动不动就发展新技术,程序员能不能对同行友善一点?

后话

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

转载于:https://my.oschina.net/u/1589819/blog/1841793

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值