总结学习Spring Cloud系列之深入理解Zuul
Posted 在路上的德尔菲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了总结学习Spring Cloud系列之深入理解Zuul相关的知识,希望对你有一定的参考价值。
简介
网关(gateway)是一种外部网络和内部网络服务之间的关卡,可以最先得到外部的请求,属于软件网卡。软件网卡主要有两个作用,一个是请求过滤,另一个是路由分发
特点
功能
- 身份验证
- 校验和安全
- 限流
- 动态路由
- 压力测试
- 静态响应处理
- 多区域弹性
使用
@EnableZuulProxy
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableCircuitBreaker
@EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ZuulServerMarkerConfiguration.class)
public @interface EnableZuulServer {
}
spring-cloud-starter-netflix-zuul
实际会加载Hystrix相关组件,注意Zuul会使用信号量的方式使用Hystrix,而非线程池的方式。
@EnableZuulProxy 和@EnableZuulServer相比,@EnableZuulServer少了PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter过滤器
原理
本质是一套Servlet的API,ZuulServlet是核心的Servlet,它将接收各类请求,另ZuulServletFilter是一个拦截器,可以拦截各类请求。ZuulServlet和ZuulServletFilter是Zuul的核心内容,在此基础上定义了自己的过滤器——ZuulFilter。
过滤器
ZuulFilter是一个抽象类,实现了IZuulFilter接口
ZuulFilter里有几个关键方法:
filterType
过滤器的类型,分别为pre、route、post、error,根据字面意思大概可知道每个类型过滤器执行顺序,下面具体讲解。filterOrder
设置过滤器执行顺序,返回一个数字,数字越小,在过滤器中就越优先执行runFilter
采用责任链模式,将多个过滤器连在一起依次执行,
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
private final DynamicBooleanProperty filterDisabled =
DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);
/**
* to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
* We also support a "static" type for static responses see StaticResponseFilter.
* Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
*
* @return A String representing that type
*/
abstract public String filterType();
/**
* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
* important for a filter. filterOrders do not need to be sequential.
*
* @return the int order of a filter
*/
abstract public int filterOrder();
/**
* If true, the filter has been disabled by archaius and will not be run
*
* @return
*/
public boolean isFilterDisabled() {
return filterDisabled.get();
}
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;
}
}
我们可以看到上图红框中为spring cloud包中自带的具体过滤器,
篮框中可以看到过滤器所在位置:pre、route、post以及error
- pre:在路由到源服务器(具体微服务应用)前执行的逻辑,如鉴权、请求参数解析、请求体解析
- route:执行路由到源服务器的逻辑,如Apache HttpClient和Ribbon
- post:在路由到源服务器后执行的过滤器,常见的用法把标准的HTTP响应头添加到响应中(SendResponseFilter),统计数据
- error:如果在整个路由源服务器的执行过程中发生异常,就会进入error过滤器,全局的响应逻辑处理错误。
将上面的具体Zuul过滤器画成图
过滤器名称 | 过滤器类型 | 说明 |
---|---|---|
ServletDetectionFilter | pre | Detects whether a request is ran through the DispatcherServlet or ZuulServlet |
Servlet30WrapperFilter | pre | wraps requests in a Servlet 3.0 compliant wrapper. Zuul’s default wrapper is only Servlet 2.5 compliant. |
FormBodyWrapperFilter | pre | parses form data and reencodes it for downstream services |
DebugFilter | pre | sets RequestContext debug attributes to true |
PreDecorationFilter | pre | determines where and how to route based on the supplied RouteLocator,Also sets various proxy related headers for downstream requests |
RibbonRoutingFilter | route | uses Ribbon, Hystrix and pluggable http clients to send requests. |
SimpleHostRoutingFilter | route | sends requests to predetermined URLs via apache HttpClient |
SendHostRoutingFilter | route | forwards requests using the RequestDispatcher,Forwarding location is located in the RequestContext attribute forward.to |
SendResponseFilter | post | writes responses from proxied requests to the current response |
ServletDetectionFilter | error | forwards to /error if RequestContext.getThrowable() is not null |
IZuulFilter
IZuulFilter接口只有两个方法,shouldFilter
指这个过滤器逻辑是否执行,如果为true则执行run
方法,run
方法为这个过滤器逻辑执行的具体内容,将返回一个Object对象,倘若返回为null,则表示继续后续的正常逻辑。
public interface IZuulFilter {
/**
* a "true" return from this method means that the run() method should be invoked
*
* @return true if the run() method should be invoked. false will not invoke the run() method
*/
boolean shouldFilter();
/**
* if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
*
* @return Some arbitrary artifact may be returned. Current implementation ignores it.
*/
Object run();
}
ZuulServlet
上面讲了过滤器,注意到过滤器定义的四个抽象方法都是没有参数的,如何获得请求的内容呢?研究一下ZuulServlet,其中引入了ZuulRunner
对象
public class ZuulServlet extends HttpServlet {
private ZuulRunner zuulRunner;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
zuulRunner = new ZuulRunner(bufferReqs);
}
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
//RequestContext对象的初始化,这个对象将保存HTTP请求和响应对象,并且使用线程副本ThreadLocal保存
/*
* ZuulRunner
* protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
@Override
protected RequestContext initialValue() {
try {
return contextClass.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
};
**/
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// 从线程副本中获取RequestContext对象 RequestContext context = threadLocal.get();
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
//过滤器流程,执行各个阶段都会捕捉异常,如果有异常将执行error过滤器
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();
}
}
void postRoute() throws ZuulException {
zuulRunner.postRoute();
}
void route() throws ZuulException {
zuulRunner.route();
}
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
zuulRunner.init(servletRequest, servletResponse);
}
/**
* sets error context info and executes "error" filters
*
* @param e
*/
void error(ZuulException e) {
RequestContext.getCurrentContext().setThrowable(e);
zuulRunner.error();
}
}
ZuulRunner
下面我们看下ZuulRunner
public class ZuulRunner {
public void postRoute() throws ZuulException {
FilterProcessor.getInstance().postRoute();
}
public void route() throws ZuulException {
FilterProcessor.getInstance().route();
}
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
public void error() {
FilterProcessor.getInstance().error();
}
}
FilterProcessor
FilterProcessor.getInstance()
实例做的具体操作,继续看FilterProcessor
饿汉模式生成FilterProcessor()单例对象
public class FilterProcessor {
static FilterProcessor INSTANCE = new FilterProcessor();
/**
* @return the singleton FilterProcessor
*/
public static FilterProcessor getInstance() {
return INSTANCE;
}
/**
* sets a singleton processor in case of a need to override default behavior
*
* @param processor
*/
public static void setProcessor(FilterProcessor processor) {
INSTANCE = processor;
}
public void postRoute() throws ZuulException {
try {
runFilters("post");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
}
}
public void error() {
try {
runFilters("error");
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
public void route() throws ZuulException {
try {
runFilters("route");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
}
}
public void preRoute() throws ZuulException {
try {
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
/**
* runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
*
* @param sType the filterType.
* @return
* @throws Throwable throws up an arbitrary exception
*/
public Object runFilters(String sType) throws Throwable {
//log
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
//责任链,获取每个具体过滤器ZuulFilter
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
/**
* Processes an individual ZuulFilter. This method adds Debug information. Any uncaught Thowables are caught by this method and converted to a ZuulException with a 500 status code.
*
* @param filter
* @return the return value for that filter
* @throws ZuulException
*/
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
//debug
final String metricPrefix = "zuul.filter-";
long execTime = 0;
String filterName = "";
try {
//time
filterName = filter.getClass().getSimpleName();
RequestContext copy = null;
Object o = null;
Throwable t = null;
//log
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
switch (s) {
case FAILED:
t = result.getException();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
break;
case SUCCESS:
o = result.getResult();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
//log
break;
default:
break;
}
if (t != null) throw t;
usageNotifier.notify(filter, s);
return o;
} catch (Throwable e) {
//log
if (e instanceof ZuulException) {
throw (ZuulException) e;
} else {
ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
throw ex;
}
}
}
}
动态路由
public interface RouteLocator {
/**
* Ignored route paths (or patterns), if any.
*/
Collection<String> getIgnoredPaths();
/**
* A map of route path (pattern) to location (e.g. service id or URL).
*/
List<Route> getRoutes();
/**
* Maps a path to an actual route with full metadata.
*/
Route getMatchingRoute(String path);
}
public interface RefreshableRouteLocator extends RouteLocator {
void refresh();
}
以上是关于总结学习Spring Cloud系列之深入理解Zuul的主要内容,如果未能解决你的问题,请参考以下文章