Zuul源码阅读

原创
2018/10/02 14:31
阅读数 749

Motivation

说一下,为什么要阅读Netflix Zuul,最近在看alibaba/Sentinel 在网关方面的应用。发现网关的设计模式有很多通用的地方。Netflix Zuul 对比Zuul2 更能直接的看出网关的主要功能和核心设计,易于上手,所以选择这个。同时也再次实践一下怎么高效阅读源代码。

Design and Function

Zuul 的主要功能,包括,鉴权,路由,流量监控,负载,实时响应(动态配置和开关),错误处理。

zuul

这张图就是根据这些功能提出的设计,通过Filter 将整个过程连接起来,

可扩展性:

  1. 在不同阶段做不同的处理,做隔离。
  2. 通过groovy 脚本加载来实现动态加载新的filter。

ZuulFilter

下面我们就来看总重要的 ZuulFilter 的设计吧,首先是类结构图

先看最基本的IZuulFilter 接口, 定义了两个方法,这个filter 要不要执行,怎么执行。

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.
	 * @throws ZuulException if an error occurs during execution.
     */
    Object run() throws ZuulException;

}

这里暂时提出疑问,throw ZuulException 会怎样?,返回任意result 怎么处理。

下面来看 Abstratct ZuulFilter类:

/**
 * Base abstract class for ZuulFilters. The base class defines abstract methods to define:
 * filterType() - 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)
 * <p/>
 * 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.
 * <p/>
 * ZuulFilters may be disabled using Archius Properties.
 * <p/>
 * By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
 *
 * @author Mikey Cohen
 *         Date: 10/26/11
 *         Time: 4:29 PM
 */
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {

    private final AtomicReference<DynamicBooleanProperty> filterDisabledRef = new AtomicReference<>();

    /**
     * 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();
	
	public int compareTo(ZuulFilter filter) {
        return Integer.compare(this.filterOrder(), filter.filterOrder());
    }
}

根据设计图,我们知道Filter有三个阶段 分别是 pre, route,post, 这里的filterType 就是指定filter type(不同type的执行顺序不一样)。filterOrder 方法就是指定相同type 下filter的执行顺序了。同时也是实现 Comparable接口的原因了。

关于自定义 type,我们稍后再看。

那我们来看看 这些type的顺序是怎么指定的,

/**
 * Zuul Servlet filter to run Zuul within a Servlet Filter. The filter invokes pre-routing filters first,
 * then routing filters, then post routing filters. Handled exceptions in pre-routing and routing
 * call the error filters, then call post-routing filters. Errors in post-routing only invoke the error filters.
 * Unhandled exceptions only invoke the error filters
 *
 * @author Mikey Cohen
 *         Date: 10/12/11
 *         Time: 2:54 PM
 */
public class ZuulServletFilter implements Filter {

    private ZuulRunner zuulRunner;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        String bufferReqsStr = filterConfig.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;

        zuulRunner = new ZuulRunner(bufferReqs);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            try {
                preRouting();
            } catch (ZuulException e) {
                error(e);
                postRouting();
                return;
            }
            
            // Only forward onto to the chain if a zuul response is not being sent
            if (!RequestContext.getCurrentContext().sendZuulResponse()) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }
            
            try {
                routing();
            } catch (ZuulException e) {
                error(e);
                postRouting();
                return;
            }
            try {
                postRouting();
            } catch (ZuulException e) {
                error(e);
                return;
            }
        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
}	

现在 看到了,ZuulFilter是通过实现在Servlet Filter的生命周期中实现的。那现在我们可以猜想 preRouting() 里会把所有的pre Type 按顺序执行:

    /**
     * runs all "pre" filters. These filters are run before routing to the orgin.
     *
     * @throws ZuulException
     */
    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 {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }

从代码可以看出process根据列表里的filter顺序执行,顺序在返回前就按order 排序了,这样,当一个 preFilter 执行抛出 ZuulException 之后,pre阶段就结束了,进到最初ServletFilter的后续阶段。

所以到这里为止,我们就知道Zuul是怎么工作和扩展的了,大家就可以根据自己的需要进行扩展了。

展开阅读全文
打赏
0
6 收藏
分享
加载中
更多评论
打赏
0 评论
6 收藏
0
分享
返回顶部
顶部