总结学习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过滤器画成图

过滤器名称过滤器类型说明
ServletDetectionFilterpreDetects whether a request is ran through the DispatcherServlet or ZuulServlet
Servlet30WrapperFilterprewraps requests in a Servlet 3.0 compliant wrapper. Zuul’s default wrapper is only Servlet 2.5 compliant.
FormBodyWrapperFilterpreparses form data and reencodes it for downstream services
DebugFilterpresets RequestContext debug attributes to true
PreDecorationFilterpredetermines where and how to route based on the supplied RouteLocator,Also sets various proxy related headers for downstream requests
RibbonRoutingFilterrouteuses Ribbon, Hystrix and pluggable http clients to send requests.
SimpleHostRoutingFilterroutesends requests to predetermined URLs via apache HttpClient
SendHostRoutingFilterrouteforwards requests using the RequestDispatcher,Forwarding location is located in the RequestContext attribute forward.to
SendResponseFilterpostwrites responses from proxied requests to the current response
ServletDetectionFiltererrorforwards 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的主要内容,如果未能解决你的问题,请参考以下文章

总结学习Spring Cloud系列之深入理解Zuul

总结学习Spring Cloud系列之深入理解Ribbon

总结学习Spring Cloud系列之深入理解Ribbon

总结学习Spring Cloud系列之深入理解Ribbon

Spring Cloud 系列之 Apollo 配置中心

Spring Cloud 系列之 Apollo 配置中心