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,该类型的过滤器是将最终的请求结果以流的形式输出给客户端