springcloud 系列 -- 初識 zuul

RequestContext

Zuul 的上下文,繼承 ConcurrentHashMap。內置 ThreadLocal 變量。所以每一個線程都會有本身的 RequestContext 變量html

ZuulFilter

ZuulFilter 實現 IZuulFilter Comparable 接口。java

IZuulFilter 定義了 2 個方法數據庫

public interface IZuulFilter {
    // 是否該攔截
    boolean shouldFilter();
    // 攔截以後作什麼處理。注:若是返回值爲非 boolean,不會作處理,這其實表示,該方法返回值 無心義
    Object run() throws ZuulException;
}

來看 ZuulFilter 中 2 個抽象方法app

abstract public String filterType();
abstract public int filterOrder();

Zuul filterType 有如下幾個值:prerouteposterroride

  • pre

執行前攔截源碼分析

  • route

執行遠程服務post

  • post

執行後攔截this

  • error

發生錯誤時攔截spa

關於 filterOrder() 先看 compareTo() 方法線程

public abstract class ZuulFilter implements Comparable<ZuulFilter> {
    // 升序
    public int compareTo(ZuulFilter filter) {
        return Integer.compare(this.filterOrder(), filter.filterOrder());
    }
}

ZuulFilter 實現了 Comparable 接口。代表,每一個 ZuulFilter 之間是具有順序的,filterOrder() 方法返回的值 越小越靠前

再來看 runFilter() 方法

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;
}

對於每一個返回值,均被封裝爲 ZuulFiterResult 若是有異常,也不會被拋出,而是放入 ZuulFiterResult 中。

從 ZuulFilter 的實現原理來看,ZuulFilter 並不是是基於傳統的 Filter 實現。

ZuulProcessor

ZuulProcessorZuul 的處理器。先看 runFilters() 方法

public class FilterProcessor {
    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;
    }
}
public class FilterProcessor {
    public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        boolean bDebug = ctx.debugRouting();
        final String metricPrefix = "zuul.filter-";
        long execTime = 0;
        String filterName = "";
        try {
            long ltime = System.currentTimeMillis();
            filterName = filter.getClass().getSimpleName();
            
            RequestContext copy = null;
            Object o = null;
            Throwable t = null;

            if (bDebug) {
                Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
                copy = ctx.copy();
            }
            
            ZuulFilterResult result = filter.runFilter();
            ExecutionStatus s = result.getStatus();
            execTime = System.currentTimeMillis() - ltime;

            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);
                    if (bDebug) {
                        Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                        Debug.compareContextState(filterName, copy);
                    }
                    break;
                default:
                    break;
            }
            
            if (t != null) throw t;

            usageNotifier.notify(filter, s);
            return o;

        } catch (Throwable e) {
            if (bDebug) {
                Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
            }
            usageNotifier.notify(filter, ExecutionStatus.FAILED);
            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;
            }
        }
    }
}

runFilters 方法遍歷全部的 ZuulFilters 並執行 processZuulFilter 方法. processZuulFilter 方法主要是執行了 ZuulFilter.runFilter 方法。
能夠看到,只有在當 ZuulFilter.runFilter 返回 Boolean 時,纔會作處理

ZuulServlet、ZuulServletFilter

Zuul 爲了適配 HttpServlet,也所以有了 ZuulServletZuulServletFilter.
默認狀況下,啓用的是 ZuulServlet. 不過能夠經過 zuul.use-filter 值修改啓用類

public class ZuulServerAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(name = "zuulServlet")
    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false", matchIfMissing = true)
    public ServletRegistrationBean zuulServlet() {
        ServletRegistrationBean<ZuulServlet> 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;
    }

    @Bean
    @ConditionalOnMissingBean(name = "zuulServletFilter")
    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true", matchIfMissing = false)
    public FilterRegistrationBean zuulServletFilter() {
        final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
        filterRegistration.setUrlPatterns(
                Collections.singleton(this.zuulProperties.getServletPattern()));
        filterRegistration.setFilter(new ZuulServletFilter());
        filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
        // The whole point of exposing this servlet is to provide a route that doesn't
        // buffer requests.
        filterRegistration.addInitParameter("buffer-requests", "false");
        return filterRegistration;
    }
}

ZuulServletZuulServletFilter 本質區別是:前者是基於 HttpServlet 實現,後者是基於 Filter 實現。從 j2ee 層面來講,Filter 的執行順序在 HttpServlet 以前

由於 ZuulServletZuulServletFilter 核心實現差很少,這裏用 ZuulServlet 來講明

public class ZuulServlet extends HttpServlet {
    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            RequestContext context = RequestContext.getCurrentContext();
            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();
        }
    }
}

有幾個細節處理一下:

  1. 由於 RequestConext 是基於 ThreadLocal 實現,所以在 finally 中調用 unset(), 而 unset() 方法,則是在內部調用了 ThreadLocal.remove()
  2. 若是未發生異常,則正確的處理流程爲:pre -> route -> post
  3. pre 階段發生異常

pre -> error -> post

  1. route 階段發生異常

pre -> route -> error -> post

  1. post 階段發生異常

pre -> route -> post -> error

  1. 在執行 pre, route, post, error 時,內部藉助 zuulRunnerzuulRunner 由藉助 FilterProcessor 調用對應的 runFilters("pre" || "route" ...)

SpringCloud 動態路由

動態路由定義:不單單能夠從配置文件中加載路由,還能夠從別的地方加載,例如數據庫。當有新的路由添加後,須要能夠動態的刷新路由

先看下 RouteLocator 接口

public interface RouteLocator {
    Collection<String> getIgnoredPaths();
    List<Route> getRoutes();
    Route getMatchingRoute(String path);
}

RouteLocator 的繼承體系以下
image.png

其中 SimpleRouteLocator 不具有刷新功能,RefreshableRouteLocator 接口具有刷新功能。可是 SpringCloud 默認提供的實現類是 SimpleRouteLocator

這裏再看,Zuul 啓動後,如何加載路由
image.png

能夠看到,是經過事件監聽,從而刷新路由的。

動態路由食用 DEMO

public class MyRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
    
    public MyRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
    }
    
    @Override
    public void refresh() {
        super.doRefresh();
    }
    
    protected Map<String, ZuulRoute> locateRoutes() {
        Map<String, ZuulRoute> routesMap = super.locateRoutes();

        // TODO 實現你本身的加載邏輯
        return routesMap;
    }
}

@RestController
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
    
    @Lazy
    @Resource
    MyRouteLocator myRouteLocator;
    
    @Autowired
    ApplicationContext applicationContext;
    
    @GetMapping("/test")
    public Object test() {
        // 從源碼流程看,咱們須要發佈刷新事件。由於發佈刷新事件,dirty 會被設置爲  true, 設置爲 true 後,纔會從新註冊對應 URL 的 HandlerMapping
        applicationContext.publishEvent(new RoutesRefreshedEvent(myRouteLocator));
        return "12";
    }
}

參考

zuul源碼分析-探究原生zuul的工做原理

相關文章
相關標籤/搜索