Zuul1.x核心源码解析

1、Zuul架构图

在这里插入图片描述

如上图,Zuul网关分为三个部分:过滤器管理模块(绿色模块)、过滤器加载模块(粉红色模块)、过滤器运行时模块(蓝色模块)

1)过滤器管理模块:让开发人员管理过滤器,过滤器管理主要是上传的动作和激活的动作。过滤器上传完是保存到数据库中的,开发人员可以通过界面管理过滤器的状态

2)过滤器加载模块:是过滤器加载模块。定期的扫描过滤器,看看过滤器有没有变更。过滤器管理器会先将变更的过滤器拉到本地的过滤器目录当中。如果变更了,就会执行Filter Loader进行加载,加载到网关运行时的Filter Runner中

3)过滤器运行时模块:网关过滤器运行时模块。这个运行时模块本身是一个Http Servlet。请求过来以后,首先交给ZuulServlet,ZuulServlet再将请求交给ZuulFilterRunner。ZuulFilterRunner是整个网关最核心的组件

请求会依次经过前置过滤器、路由过滤器和后置路由过滤器。经过过滤以后,请求会以response的形式响应给客户端,Zuul网关最核心的部分就是过滤链,依次运行过滤器。有一个很重要的组件RequestContext,当请求在Zuul网关过滤链中流转的时候,它们需要共享一些过滤信息。比如前置过滤器会设置一些信息给路由过滤器去读取,信息的交换是通过RequestContext,就像是过滤器之间可以共享的存储,而且是线程安全的,每个请求有一个局部的RequestContext

2、请求处理的生命周期

在这里插入图片描述

1)请求过来了,首先会进入一系列的前置过滤器pre filter

2)前置过滤器处理完了,进入routing filter路由过滤器,routing filter路由过滤器是真正的向后台服务发起请求,接收响应的过滤器

3)经过routing filter路由过滤器,最后会传递过post filter后置过滤器,进行一些后续的处理,这时候已经拿到响应了,然后在返回给客户端

4)在这三个过滤器过滤的过程中,任何一个环节发生错误,都会进入error filter,由error filter进行统一的错误处理。error filter错误过滤器会发送给post filter,也是以响应的方式发回给客户端

3、过滤器基本的概念

1)类型:它被定义在路由的流程中,过滤器被应用的阶段

pre filter前置过滤器:在请求被路由到源服务器前要执行的过滤器(认证、选路由、请求日志)

routing filter路由过滤器:处理将请求发送到源服务器的过滤器

post filter后置过滤器:在响应从源服务器返回时要被执行的过滤器(对响应增加http请求头、收集统计和度量、将响应以流的方式返回客户端)

error filter错误过滤器:上述阶段中出现错误要执行的过滤器

2)执行顺序:在同一个type中定义过滤器执行的顺序

每一种类型的过滤器都可能有一个或者多个过滤器。比如pre filter可能有多个,它们的执行顺序,不是乱序执行的,它们会有一个自定义的order顺序

3)条件:过滤器执行必须满足的条件

在过滤器被执行,流转的时候,某个过滤器到底要不要执行?满足什么条件执行?这就是过滤器条件定义的

4)动作:如果满足条件,过滤器将要执行的动作是什么

public class PreFilter extends ZuulFilter {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(PreFilter.class);

    /**
     * 定义过滤器的类型 pre、post、route、error
     *
     * @return
     */
    @Override
    public String filterType() {
    
    
        return "pre";
    }

    /**
     * 定义过滤器执行的顺序
     *
     * @return
     */
    @Override
    public int filterOrder() {
    
    
        return 1;
    }

    /**
     * 定义过滤器执行的条件
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
    
    
        return true;
    }

    /**
     * 定义过滤器执行的具体逻辑
     *
     * @return
     */
    @Override
    public Object run() {
    
    
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        LOGGER.info("Request Method : " + request.getMethod() + " Request URL : " + request.getRequestURL().toString());
        return null;
    }
}

4、源码分析

本文采用的SpringCloud版本为Edgware.SR3

使用Zuul时,需要在程序的启动类加上@EnableZuulProxy

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
    
    
}

@EnableZuulProxy这里通过Spring的Import机制加载了ZuulProxyMarkerConfiguration这个类

@Configuration
public class ZuulProxyMarkerConfiguration {
    
    
	@Bean
	public Marker zuulProxyMarkerBean() {
    
    
		return new Marker();
	}

	class Marker {
    
    
	}
}

可以看到ZuulProxyMarkerConfiguration只是向IOC容器中注入了Marker

在相同的包路径下找到了ZuulProxyAutoConfiguration,该类在Marker这个Bean存在时会进行实例化,它主要注入了用作负载均衡相关的DiscoveryClient、RibbonCommandFactoryConfiguration,还注入了一系列的filters

@Configuration
@Import({
    
     RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
		RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
		RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
		HttpClientConfiguration.class })
//@ConditionalOnBean当给定的Bean存在时,则实例化当前Bean
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
    
    

它的父类ZuulServerAutoConfiguration在缺失zuulServlet Bean的情况下注入了ZuulServlet,该类是Zuul的核心类

	@Bean
	@ConditionalOnMissingBean(name = "zuulServlet")
	public ServletRegistrationBean zuulServlet() {
    
    
		ServletRegistrationBean 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;
	}

ZuulServlet在过滤器运行时模块我们在进行详细的分析,我们还是继续看ZuulServerAutoConfiguration

1)、过滤器加载模块

ZuulServerAutoConfiguration还初始化了ZuulFilterInitializer类,将所有的filters向FilterRegistry注册

	@Configuration
	protected static class ZuulFilterConfiguration {
    
    

    //将IOC容器中所有ZuulFilter的子类注入到Map中
		@Autowired
		private Map<String, ZuulFilter> filters;

		@Bean
		public ZuulFilterInitializer zuulFilterInitializer(
				CounterFactory counterFactory, TracerFactory tracerFactory) {
    
    
			FilterLoader filterLoader = FilterLoader.getInstance();
			FilterRegistry filterRegistry = FilterRegistry.instance();
			return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
		}

	}

FilterRegistry管理了一个ConcurrentHashMap,用作存储过滤器的,并有一些基本的CURD过滤器的方法

public class FilterRegistry {
    
    

    private static final FilterRegistry INSTANCE = new FilterRegistry();

    public static final FilterRegistry instance() {
    
    
        return INSTANCE;
    }

    private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();

    private FilterRegistry() {
    
    
    }

    public ZuulFilter remove(String key) {
    
    
        return this.filters.remove(key);
    }

    public ZuulFilter get(String key) {
    
    
        return this.filters.get(key);
    }

    public void put(String key, ZuulFilter filter) {
    
    
        this.filters.putIfAbsent(key, filter);
    }

    public int size() {
    
    
        return this.filters.size();
    }

    public Collection<ZuulFilter> getAllFilters() {
    
    
        return this.filters.values();
    }

}

ZuulFilterInitializer类持有FilterLoader和FilterRegistry,在contextInitialized()方法中向FilterRegistry注册所有的filters

public class ZuulFilterInitializer {
    
    

	private static final Log log = LogFactory.getLog(ZuulFilterInitializer.class);

	private final Map<String, ZuulFilter> filters;
	private final CounterFactory counterFactory;
	private final TracerFactory tracerFactory;
	private final FilterLoader filterLoader;
	private final FilterRegistry filterRegistry;

	public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
								 CounterFactory counterFactory,
								 TracerFactory tracerFactory,
								 FilterLoader filterLoader,
								 FilterRegistry filterRegistry) {
    
    
		this.filters = filters;
		this.counterFactory = counterFactory;
		this.tracerFactory = tracerFactory;
		this.filterLoader = filterLoader;
		this.filterRegistry = filterRegistry;
	}

	@PostConstruct
	public void contextInitialized() {
    
    
		log.info("Starting filter initializer");

		TracerFactory.initialize(tracerFactory);
		CounterFactory.initialize(counterFactory);

		for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
    
    
			filterRegistry.put(entry.getKey(), entry.getValue());
		}
	}

	@PreDestroy
	public void contextDestroyed() {
    
    
		log.info("Stopping filter initializer");
		for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
    
    
			filterRegistry.remove(entry.getKey());
		}
		clearLoaderCache();

		TracerFactory.initialize(null);
		CounterFactory.initialize(null);
	}

	private void clearLoaderCache() {
    
    
		Field field = ReflectionUtils.findField(FilterLoader.class, "hashFiltersByType");
		ReflectionUtils.makeAccessible(field);
		@SuppressWarnings("rawtypes")
		Map cache = (Map) ReflectionUtils.getField(field, filterLoader);
		cache.clear();
	}

}

这里还有一个扩展点FilterFileManager,Netflix Zuul支持动态加载Groovy脚本,但在SpringCloud Zuul去掉了动态过滤器加载

FilterLoader类持有FilterRegistry,FilterFileManager类持有FilterLoader,所以最终是由FilterFileManager注入filterFilterRegistry的ConcurrentHashMap。FilterFileManager开启了轮询机制,定时的去加载过滤器

public class FilterFileManager {
    
    

    private static final Logger LOG = LoggerFactory.getLogger(FilterFileManager.class);

    String[] aDirectories;
    int pollingIntervalSeconds;
    Thread poller;
    boolean bRunning = true;

    static FilenameFilter FILENAME_FILTER;

    static FilterFileManager INSTANCE;

    private FilterFileManager() {
    
    
    }

    public static void setFilenameFilter(FilenameFilter filter) {
    
    
        FILENAME_FILTER = filter;
    }

    /**
     * Initialized the GroovyFileManager.
     *
     * @param pollingIntervalSeconds the polling interval in Seconds
     * @param directories            Any number of paths to directories to be polled may be specified
     * @throws IOException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static void init(int pollingIntervalSeconds, String... directories) throws Exception, IllegalAccessException, InstantiationException {
    
    
        if (INSTANCE == null) INSTANCE = new FilterFileManager();

        INSTANCE.aDirectories = directories;
        INSTANCE.pollingIntervalSeconds = pollingIntervalSeconds;
        INSTANCE.manageFiles();
        INSTANCE.startPoller();

    }

    public static FilterFileManager getInstance() {
    
    
        return INSTANCE;
    }

    /**
     * Shuts down the poller
     */
    public static void shutdown() {
    
    
        INSTANCE.stopPoller();
    }


    void stopPoller() {
    
    
        bRunning = false;
    }

  	// 定时加载过滤器
    void startPoller() {
    
    
        poller = new Thread("GroovyFilterFileManagerPoller") {
    
    
            public void run() {
    
    
                while (bRunning) {
    
    
                    try {
    
    
                        sleep(pollingIntervalSeconds * 1000);
                        manageFiles();
                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        };
        poller.setDaemon(true);
        poller.start();
    }

    /**
     * Returns the directory File for a path. A Runtime Exception is thrown if the directory is in valid
     *
     * @param sPath
     * @return a File representing the directory path
     */
    public File getDirectory(String sPath) {
    
    
        File  directory = new File(sPath);
        if (!directory.isDirectory()) {
    
    
            URL resource = FilterFileManager.class.getClassLoader().getResource(sPath);
            try {
    
    
                directory = new File(resource.toURI());
            } catch (Exception e) {
    
    
                LOG.error("Error accessing directory in classloader. path=" + sPath, e);
            }
            if (!directory.isDirectory()) {
    
    
                throw new RuntimeException(directory.getAbsolutePath() + " is not a valid directory");
            }
        }
        return directory;
    }

    /**
     * Returns a List<File> of all Files from all polled directories
     *
     * @return
     */
    List<File> getFiles() {
    
    
        List<File> list = new ArrayList<File>();
        for (String sDirectory : aDirectories) {
    
    
            if (sDirectory != null) {
    
    
                File directory = getDirectory(sDirectory);
                File[] aFiles = directory.listFiles(FILENAME_FILTER);
                if (aFiles != null) {
    
    
                    list.addAll(Arrays.asList(aFiles));
                }
            }
        }
        return list;
    }

    /**
     * puts files into the FilterLoader. The FilterLoader will only addd new or changed filters
     *
     * @param aFiles a List<File>
     * @throws IOException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException {
    
    

        for (File file : aFiles) {
    
    
            FilterLoader.getInstance().putFilter(file);
        }
    }

    void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
    
    
        List<File> aFiles = getFiles();
        processGroovyFiles(aFiles);
    }

}

2)、过滤器运行时模块

刚才我们说过ZuulServerAutoConfiguration在缺失zuulServlet Bean的情况下注入了ZuulServlet,而ZuulServlet起到了前端控制器的作用,所有的请求都由它接管。它的核心代码如下:

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

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            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();
        }
    }

跟踪init(),可以发现这个方法为每个请求生成了RequestContext,RequestContext继承了ConcurrentHashMap<String, Object>,在请求结束时销毁掉该RequestContext,RequestContext的生命周期为请求到ZuulServlet开始处理,直到请求结束返回结果

RequestContext类在存储了很多重要的信息,包括HttpServletRequest、HttpServletResponse、ResponseDataStream、ResponseStatusCode等。 RequestContext对象在处理请求的过程中,一直存在,所以这个对象为所有filters共享

从ZuulServlet的service()方法可知,它是先处理pre类型的处理器,然后在处理route类型的处理器,最后再处理post类型的处理器

首先来看一看preRoute()的处理过程,它会进入到ZuulRunner,该类的作用是将请求的HttpServletRequest、HttpServletRespons放在RequestContext类中,并包装了一个FilterProcessor,代码如下:

public class ZuulServlet extends HttpServlet {
    
    

		/**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
    
    
        zuulRunner.preRoute();
    }
public class ZuulRunner {
    
    

     /**
     * sets HttpServlet request and HttpResponse
     *
     * @param servletRequest
     * @param servletResponse
     */
    public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
    
    

        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
    
    
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
    
    
            ctx.setRequest(servletRequest);
        }

        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
    }
  
		/**
     * executes "pre" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void preRoute() throws ZuulException {
    
    
        FilterProcessor.getInstance().preRoute();
    }

而FilterProcessor类为调用filters的类,跟踪runFilters()方法,可以发现,它最终调用了FilterLoader的getFiltersByType(String filterType)方法来获取同一类的过滤器,然后用for循环遍历所有的ZuulFilter,执行了processZuulFilter(ZuulFilter filter)方法,跟踪该方法可以发现最终是执行了ZuulFilter的方法,最终返回了该方法返回的Object对象

 public class FilterProcessor {
    
      

		/**
     * 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;
    }
       /**
     * 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();
        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;
            }
        }
    }
   

route、post类型的过滤器的执行过程和pre执行过程类似

5、Zuul默认过滤器

Zuul默认注入的过滤器,它们的执行顺序在FilterConstants类

过滤器 order 描述 类型
ServletDetectionFilter -3 检测请求是用DispatcherServlet还是ZuulServlet pre
Servlet30WrapperFilter -2 在Servlet 3.0下,包装requests pre
FormBodyWrapperFilter -1 解析表单数据 pre
SendErrorFilter 0 如果中途出现错误 error
DebugFilter 1 设置请求过程是否开启debug pre
PreDecorationFilter 5 根据uri决定调用哪一个route过滤器 pre
RibbonRoutingFilter 10 如果写配置的时候用ServiceId则用这个route过滤器,该过滤器可以用Ribbon做负载均衡,用Hystrix做熔断 route
SimpleHostRoutingFilter 100 如果写配置的时候用url则用这个route过滤 route
SendForwardFilter 500 用RequestDispatcher请求转发 route
SendResponseFilter 1000 用RequestDispatcher请求转发 post

过滤器的order值越小,就越先执行,并且在执行过滤器的过程中,它们共享了一个RequestContext对象,该对象的生命周期贯穿于请求,可以看出优先执行了pre类型的过滤器,并将执行后的结果放在RequestContext中,供后续的filter使用,比如在执行PreDecorationFilter的时候,决定使用哪一个route,它的结果的是放在RequestContext对象中,后续会执行所有的route的过滤器,如果不满足条件就不执行该过滤器的run方法。最终达到了就执行一个route过滤器的run()方法

而error类型的过滤器,是在程序发生异常的时候执行的

post类型的过滤,在默认的情况下,只注入了SendResponseFilter,该类型的过滤器是将最终的请求结果以流的形式输出给客户端

猜你喜欢

转载自blog.csdn.net/qq_40378034/article/details/109546412