概述
2019独角兽企业重金招聘Python工程师标准>>>
1、阅读环境
基于spring cloud 的zuul组件,源码亦是从demo中入手,方式以debug为主。
- zuul-core-1.3.0.jar
- spring-cloud-netflix-core-1.3.0.RC1,jar
2、架构图
核心类图
核心类分析
从架构图可以在宏观整体上把握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
,内部创建了PreDecorationFilter
,RibbonRoutingFilter
,SimpleHostRoutingFilter
三个非常重要的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
最后
以上就是高高服饰为你收集整理的zuul 1.x 源码解析的全部内容,希望文章能够帮你解决zuul 1.x 源码解析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复