Spring Cloud

Spring Cloud Zuul 分析(一)之ZuulConf

2021-03-07  本文已影响0人  Blog

随着业务范围越来越大,我们的微服务项目越来越多,这么多微服务如何管理、服务之间的鉴权等等成为重中之重,所以随着服务模块的增加,我们就必须引入网关服务,将流量统一转发到网关服务,在网关服务中进行鉴权操作、限流操作、请求追踪等等,所以本节我们重点分析基于Zuul框架的网关,Spring Cloud GateWay网关我们后续会进行分析!


Zuul请求流程图/过滤器

开局一张图,这里我们还是贴上Zuul的官方图,这里的Zuul流程图版本为1.x版本的,基于Servlet实现的,是BIO同步阻塞I/O模式,Zuul2.x版本则是基于Netty这种NIO同步非阻塞的I/O模型,本节我们还是基于Zuul1.x版本分析,针对上图,其实我们也可以简单理解为Servlet Filter拦截器,对请求前、请求中、请求后执行不同的拦截策略!


ZuulConfiguration配置类中主要包含一些基础的配置,比如路由器RouteLocator、pre filter&post filter拦截器、Application事件监听器、Url映射处理实现、Servlet拦截类。由于ZuulConfiguration比较长,所以只总结了重要的部分


ZuulConfiguration

@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulConfiguration {
    @Autowired
    protected ZuulProperties zuulProperties;
    @Autowired
    protected ServerProperties server;
    @Autowired(required = false)
    private ErrorController errorController;
    //注册Zuul特性描述
    @Bean
    public HasFeatures zuulFeature() {
        return HasFeatures.namedFeature("Zuul (Simple)", ZuulConfiguration.class);
    }
    //组合路由器,内部其实只有DiscoveryClientRouteLocator这个具备路由刷新的路由器
    @Bean
    @Primary
    public CompositeRouteLocator primaryRouteLocator(
            Collection<RouteLocator> routeLocators) {
        return new CompositeRouteLocator(routeLocators);
    }
    //简单的路由器,不具备路由刷新功能,只有静态路由功能
    @Bean
    @ConditionalOnMissingBean(SimpleRouteLocator.class)
    public SimpleRouteLocator simpleRouteLocator() {
        return new SimpleRouteLocator(this.server.getServletPrefix(),
                this.zuulProperties);
    }
    //Zuul的核心处理类
    //让ZuulServlet这个Servlet直接包装为一个Controller(这个是servlet.mvc下面的,不是我们的经常使用的@Controller注解,不要搞混了)
    //将请求交给ZuulServlet处理,默认处理的路径为 "/" 这个根目录,默认由DispatcherServlet分发的请求
    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }
    //注册zuul.routes.*.path的路由处理类为ZuulController(内部其实是ZuulServlet在处理逻辑)
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
        mapping.setErrorController(this.errorController);
        return mapping;
    }
    //监听一些事件,处理路由刷新功能
    @Bean
    public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
        return new ZuulRefreshListener();
    }
    //注册一个ZuulServlet, 处理的路径/zuul 开头的路径
    @Bean
    @ConditionalOnMissingBean(name = "zuulServlet")
    public ServletRegistrationBean zuulServlet() {
        ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
                this.zuulProperties.getServletPattern());
        servlet.addInitParameter("buffer-requests", "false");
        return servlet;
    }
    ......
}

代码片段中我们已经基本注释了每个Bean实例处理的逻辑,上面有一个地方可能有些疑惑,那就是ZuulHandlerMapping内部已经把Zuul配置的路由Url信息全部都映射给了ZuulController(内部也是ZuulServlet处理),为何还需要单独注册一个ZuulServlet?所以笔者这里也只是猜测可能单独注册这个也是为了更明确知道哪些Url路径是可以直接使用ZuulServlet进行处理的,明确知道这种Url信息就是需要Zuul转发的,而不需要经过DispatcherServlet层层处理,总之为了提高效率吧!
其中ZuulServlet可以说是Zuul的核心,外部访问之后都会经由ZuulServlet来做最终的转发处理,下面我们就单独分析下代码片段中非常重要的几个Bean实例(CompositeRouteLocator、ZuulHandlerMapping、ZuulController)


CompositeRouteLocator组合路由器

public class CompositeRouteLocator implements RefreshableRouteLocator {
    //此集合只有一个DiscoveryClientRouteLocator路由
    private final Collection<? extends RouteLocator> routeLocators;
    private ArrayList<RouteLocator> rl;

    public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
        Assert.notNull(routeLocators, "'routeLocators' must not be null");
        rl = new ArrayList<>(routeLocators);
        AnnotationAwareOrderComparator.sort(rl);
        this.routeLocators = rl;
    }
    //获取忽略的路径url,也就是让部分url对应的Service不暴露给外面
    @Override
    public Collection<String> getIgnoredPaths() {
        List<String> ignoredPaths = new ArrayList<>();
        for (RouteLocator locator : routeLocators) {
            ignoredPaths.addAll(locator.getIgnoredPaths());
        }
        return ignoredPaths;
    }
    //获取路由映射集合,url等等信息
    @Override
    public List<Route> getRoutes() {
        List<Route> route = new ArrayList<>();
        for (RouteLocator locator : routeLocators) {
            route.addAll(locator.getRoutes());
        }
        return route;
    }
    //根据实际匹配的路径返回一个Route
    @Override
    public Route getMatchingRoute(String path) {......}
    //刷新路由器
    @Override
    public void refresh() {......}
}

组合路由器主要就是用于分发作用,获取每个RouteLocator对应的操作!


ZuulController

public class ZuulController extends ServletWrappingController {
    public ZuulController() {
        //设置ZuulServlet作为处理类
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null); // 支持所有方法
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            //调用ZuulServlet.service方法
            return super.handleRequestInternal(request, response);
        }
        finally {
            //释放当前请求上下文
            RequestContext.getCurrentContext().unset();
        }
    }
}

ZuulController作用就是将ZuulServlet包装为一个Servlet Controller,让进入到DispatcherServlet的请求通过分发,最终到ZuulController的请求都交由ZuulServlet这个Servlet来处理。


ZuulHandlerMapping

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
    ......
    //查找urlPath对应的处理实例
    @Override
    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        //urlPath与errorController匹配则忽略
        if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
            return null;
        }
        //若匹配到配置的忽略地址则忽略
        String[] ignored = this.routeLocator.getIgnoredPaths().toArray(new String[0]);
        if (PatternMatchUtils.simpleMatch(ignored, urlPath)) {
            return null;
        }
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.containsKey("forward.to")) {
            return null;
        }
        //重点,注册Zuul配置的路由url对应的处理实例为ZuulController
        if (this.dirty) {
            synchronized (this) {
                if (this.dirty) {
                    registerHandlers();
                    this.dirty = false;
                }
            }
        }
        //返回当前urlPath对应的处理实例
        return super.lookupHandler(urlPath, request);
    }
    //注册路由对应的处理实例
    private void registerHandlers() {
        Collection<Route> routes = this.routeLocator.getRoutes();
        if (routes.isEmpty()) {
            this.logger.warn("No routes found from RouteLocator");
        }
        else {
            for (Route route : routes) {
                //注册zuul.routes.*.path的处理实例(ZuulController)
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }
}

ZuulHandlerMapping继承了AbstractUrlHandlerMapping之后通过重写lookupHandler查找实例方法,注册Zuul的路由处理实例为ZuulController,最后在返回当前urlPath对应的处理实例,请求下游服务的一次完整的请求流程(除开/zuul的路径,这个路径直接由ZuulServlet处理)大致为HttpServlet->FrameworkServlet->DispatcherServlet->DispatcherServlet#getHandler()->ZuulHandlerMapping->ZuulController->ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->RibbonRoutingFilter,DispatcherServlet#getHandler()这个步骤里面就会查找这个urlPath对应的处理实例!


ZuulServlet

上面我们总结和分析了各种Bean实例的作用,其实都是在围绕着ZuulServlet这个Servlet,ZuulServlet作为Zuul的一个非常核心的功能,作为Zuul的请求入口处理类,那都做了什么事情呢,我们接着往下看!

public class ZuulServlet extends HttpServlet {
    @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);
    }
    //执行Zuul的filter阶段
    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            //设置zuul的RequestContext请求上下文参数
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                //执行pre filters阶段
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //执行routing filters阶段
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //执行post filters阶段
                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 {......}
    //请求中的拦截器,请求下游服务在这个拦截器中
    void route() throws ZuulException {......}
    //请求前的拦截器
    void preRoute() throws ZuulException {......}
    //初始化阶段,设置请求上下文参数
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {......}
    //失败时候的拦截器
    void error(ZuulException e) {......}
    ......
}

至此我们大致分析了ZuulConfiguration配置类中比较重要的一部分,其中ZuulConfiguration#ZuulFilterConfiguration配置我们放到下一节一起分析和总结,下一节我们将分析ZuulFilter(pre filters、routing filters、post filters...)的初始化以及调用过程,即调用链中的ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->RibbonRoutingFilter部分!

上一篇下一篇

猜你喜欢

热点阅读