SpringCloud组件--Zuul入门解析
一、Zuul简介
1、网关的作用
分布式架构中,服务节点数量较多,而对于客户端而言,多个服务节点暴露出来的API应该是统一的,否则每个节点地址不同,客户端就需要维护所有服务节点地址然后再选择一个访问,很明显客户端的维护成本就很高。
此时就需要有一个暴露统一API的网关服务将多服务节点封装,客户端访问网关,网关再将客户端的请求分发给被封装的服务节点,这样就避免了客户端和服务端的直接交互。
另外网关除了可以实现动态路由功能外,还可以实现参数校验、鉴权、日志、缓存、负载均衡、监控、限流等多种功能。总之对于非业务需求的统一功能都可以交给网关来实现,设计思想有点类似于Spring的面向切面编程。
2、Zuul是什么
Zuul是Netfilx开源的微服务系统的网关组件。
Zuul的作用有很多,如下几个方面
1.配合Ribbon、Eureka可以实现智能动态路由和负载均衡功能,将请求按策略分发到集群中合适的服务实例;
2.将服务和客户端之间进行解耦,客户端访问的是网关暴露出来的统一API,客户端不需要关系服务的运行情况;
3.统一鉴权,监控,日志,缓存,限流,参数校验等功能;
4.通过流量监控,可以根据流量进行服务降级;
3、Zuul工作流程
Zuul本质是一个Servlet来对请求进行控制,核心是创建了一系列的过滤器。Zuul包括以下四种过滤器:
1、PRE过滤器:请求路由到具体的服务之前执行,可以用作安全校验、身份校验、参数校验等前置工作;
2、ROUTING过滤器:用于将请求路由到具体的服务实例,默认使用Http Client进行网络请求;
3、POST过滤器:在请求已经被路由到微服务后执行,通常用于收集统计信息、指标并将响应返回给客户端;
4、ERROR过滤器:在其他过滤器出现异常时执行;
各个类型的过滤器执行顺序依次为PRE过滤器->ROUTING过滤器->POST过滤器,如果出现异常就执行ERROR过滤器
二、Zuul实践
Zuul也是一个服务,所以需要构建Zuul-Server服务
添加Zuul相关依赖
12 3 7org.springframework.cloud 4spring-cloud-starter-eureka 51.4.7.RELEASE 68 12org.springframework.cloud 9spring-cloud-starter-zuul 101.4.7.RELEASE 1113 17org.springframework.boot 14spring-boot-starter-web 152.5.0 1618 22org.springframework.boot 19spring-boot-starter-test 20test 21
添加Zuul相关配置application.yml
1 server: 2 port: 5000 3 spring: 4 application: 5 name: zuul-server 6 eureka: 7 client: 8 serviceUrl: 9 defaultZone: http://localhost:8761/eureka/ 10 zuul: 11 routes: 12 goods: 13 path: /goods/** 14 serverId: goods 15 order: 16 path: /order/** 17 serverId: order
其中server表示Zuul服务配置,eureka表示Zuul也是一个Eureka客户端需要配置Eureka,最后就是网关配置zuul,routes是路由配置,需要配置服务的serverId表示路由的服务名称,path表示路由的服务地址,配置之后zuul可以将指定serverId的请求分发到指定的服务。
添加启动类,并添加相关注解
1 @SpringBootApplication 2 @EnableEurekaClient 3 @EnableZuulProxy 4 public class ZuulApplication { 5 6 public static void main(String[] args){ 7 SpringApplication.run(ZuulApplication.class); 8 System.out.println("Zuul starting..."); 9 } 10 }
@EnableEurekaClient注解表示ZuulServer是一个Eureka客户端,@EnableZuulProxy注解表示当前是一个Zuul服务器,开启Zuul功能。
启动ZuulApplication类,此时就可以完成请求路由功能了,访问http://localhost:5000/goods/xxxx就可以将请求转发到goods服务的API
添加不同逻辑的过滤器,如进行参数校验,自定义CheckFilter过滤器继承父类ZuulFilter,实现ZuulFilter的抽象方法。
@Component public class CheckFilter extends ZuulFilter{ @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { /**处理路由过滤等逻辑,根据filterType在不同的时机执行具体的逻辑 */ //1.获取请求上下文 RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); Iterator> iterator = request.getParameterMap().entrySet().iterator(); while (iterator.hasNext()){ Map.Entry entry = iterator.next(); if(entry.getValue()==null || entry.getValue().length==0){ System.out.println("参数校验失败:参数为空:" + entry.getKey()); return null; } } return null; } }
实现filterType方法表示当前是一个PRE过滤器,filterOrder方法返回过滤器顺序,值越小优先级越高, run方法就是过滤器的具体处理逻辑。
三、Zuul实现原理
3.1、@EnableZuulProxy注解
Zuul使用时比较简单,只需要在启动类添加@EnableZuulProxy注解即可,所以Zuul的所有功能都围绕该注解进行,@EnableZuulProxy的作用是加载ZuulProxyMarkerConfiguration的实例,@EnableZuulProxy定义如下:
1 @EnableCircuitBreaker 2 @Target(ElementType.TYPE) 3 @Retention(RetentionPolicy.RUNTIME) 4 @Import(ZuulProxyMarkerConfiguration.class) 5 public @interface EnableZuulProxy { 6 }
而ZuulProxyMarkerConfiguration的作用是注入一个ZuulProxy的标记实例,定义如下:
1 @Configuration 2 public class ZuulProxyMarkerConfiguration { 3 @Bean 4 public Marker zuulProxyMarkerBean() { 5 return new Marker(); 6 } 7 8 class Marker { 9 } 10 }
内部类Marker唯一的作用就是用于标记,所以需要看哪里需要用到该标记,通过引用搜索发现引用的地方是ZuulProxyAutoConfiguration,父类是ZuulServerAutoConfiguration,定义如下:
@Configuration @EnableConfigurationProperties({ ZuulProperties.class }) @ConditionalOnClass(ZuulServlet.class) @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class) // Make sure to get the ServerProperties from the same place as a normal web app would // FIXME @Import(ServerPropertiesAutoConfiguration.class) public class ZuulServerAutoConfiguration {
父类ZuulServerAutoConfiguration中会向Spring容器中注入ServletRegistrationBean实例,该实例中创建了一个ZuulServlet。而Zuul的核心功能就在于这个ZuulServlet,Zuul接收到的所有请求最终都会由于ZuulServlet的service方法来完成
@Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet() { ServletRegistrationBeanservlet = 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; }
3.2、ZuulServlet
ZuulServlet继承之HttpServlet,初始化init方法创建了ZuulRunner对象,而核心功能在于service方法,所有请求都会先执行到service方法,源码如下:
@Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { /** 1.初始化ZuulRunner 对象*/ init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); /** 2.获取HttpRequest请求上下文*/ RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { /** 3.执行pre过滤器*/ preRoute(); } catch (ZuulException e) { /** 异常时执行error过滤器和post过滤器*/ error(e); postRoute(); return; } try { /** 4.执行route过滤器*/ route(); } catch (ZuulException e) { /** 异常时执行error过滤器和post过滤器*/ error(e); postRoute(); return; } try { /** 5.执行post过滤器*/ postRoute(); } catch (ZuulException e) { /** 异常时执行error过滤器*/ error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { /** 清理本地缓存RequestContext*/ RequestContext.getCurrentContext().unset(); } }
整体流程比较清晰,依次执行 PRE过滤器 -> ROUTE过滤器 -> POST过滤器, 如果有异常就执行 ERROR过滤器, 另外如果POST过滤器执行之前异常同样也会执行POST过滤器, 所以可以保证POST过滤器肯定会被执行,
而ERROR过滤器只会在其他过滤器异常时才会执行。另外在执行过滤器逻辑之前会初始化上下文RequestContext,过滤器执行完之后清理RequestContext。
ZuulServlet的preRoute、route、postRoute、error方法都没有具体实现,都委托给ZuulRunner执行对应的方法,而ZuulRunner也不是具体的执行者,而是交给了FilterProcessor来执行。
3.3、FilterProcessor
FilterProcessor是过滤器的具体执行者,是一个单例对象。ZuulServlet的各种过滤器最终分别执行了FilterProcessor的preRoute()、route()、postRoute()、error()方法,而这四个方法最终都是执行了FilterProcessor的内部方法runFilters(String filterType)方法。
runFilter源码如下:
1 public Object runFilters(String sType) throws Throwable { 2 if (RequestContext.getCurrentContext().debugRouting()) { 3 Debug.addRoutingDebug("Invoking {" + sType + "} type filters"); 4 } 5 boolean bResult = false; 6 /** 1.获取指定类型的过滤器列表*/ 7 Listlist = FilterLoader.getInstance().getFiltersByType(sType); 8 if (list != null) { 9 for (int i = 0; i < list.size(); i++) { 10 ZuulFilter zuulFilter = list.get(i); 11 /** 2.遍历执行过滤器*/ 12 Object result = processZuulFilter(zuulFilter); 13 if (result != null && result instanceof Boolean) { 14 bResult |= ((Boolean) result); 15 } 16 } 17 } 18 return bResult; 19 }
首先调用FilterLoader对象的getFilterByType方法根据过滤器类型查询过滤器列表,然后执行processZuulFilter方法执行过滤器逻辑
3.3.1、获取过滤器
所有Zuul的过滤器都需要实现ZuulFilter接口,并且需要被@Component注解修饰从而注入到Spring容器中,而ZuulServerAutoConfiguration类中有一个静态内部类ZuulFilterConfiguration类,该类的作用是将Spring容器中所有的ZuulFilter实例取出来,代码如下:
/** ZuulServerAutoConfiguration内部类 */ @Configuration protected static class ZuulFilterConfiguration { @Autowired private Mapfilters; @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); } }
ZuulFilterConfigruation首先注入所有的ZuulFilter对象,采用Map存储,而ZuulFilterInitiailizer的作用就是初始化过滤器注册器,代码如下:
@PostConstruct public void contextInitialized() { log.info("Starting filter initializer"); TracerFactory.initialize(tracerFactory); CounterFactory.initialize(counterFactory); for (Map.Entryentry : this.filters.entrySet()) { filterRegistry.put(entry.getKey(), entry.getValue()); } }
FilterRegistry是过滤器注册器,内部采用Map
再回到ZuulRunner类的runFilters方法,通过FilterLoader的getFiltersByType方法获取指定类型过滤器,所以FilterLoader的作用就是将FilterRegistry中过滤器按不同类型进行归类,采用Map
所以获取过滤器的逻辑比较简单,就是从Spring容器中获取所有ZuulFilter实例,然后按不同的类型进行分组存在Map中缓存起来即可。
3.3.2、执行过滤器
1 /** 执行过滤器 */ 2 public Object processZuulFilter(ZuulFilter filter) throws ZuulException { 3 /** 1.获取请求上下文 */ 4 RequestContext ctx = RequestContext.getCurrentContext(); 5 boolean bDebug = ctx.debugRouting(); 6 final String metricPrefix = "zuul.filter-"; 7 long execTime = 0; 8 String filterName = ""; 9 try { 10 long ltime = System.currentTimeMillis(); 11 filterName = filter.getClass().getSimpleName(); 12 13 RequestContext copy = null; 14 Object o = null; 15 Throwable t = null; 16 17 if (bDebug) { 18 Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName); 19 copy = ctx.copy(); 20 } 21 /** 2.执行过滤器的runFilter方法 */ 22 ZuulFilterResult result = filter.runFilter(); 23 ExecutionStatus s = result.getStatus(); 24 execTime = System.currentTimeMillis() - ltime; 25 26 switch (s) { 27 /** 3.将执行结果存在RequestContext上下文中 */ 28 case FAILED: 29 t = result.getException(); 30 ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime); 31 break; 32 case SUCCESS: 33 o = result.getResult(); 34 ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime); 35 if (bDebug) { 36 Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms"); 37 Debug.compareContextState(filterName, copy); 38 } 39 break; 40 default: 41 break; 42 } 43 44 if (t != null) throw t; 45 46 usageNotifier.notify(filter, s); 47 /** 4.返回执行结果 */ 48 return o; 49 50 } catch (Throwable e) { 51 if (bDebug) { 52 Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage()); 53 } 54 usageNotifier.notify(filter, ExecutionStatus.FAILED); 55 if (e instanceof ZuulException) { 56 throw (ZuulException) e; 57 } else { 58 ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName); 59 ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime); 60 throw ex; 61 } 62 } 63 }
整体逻辑就是先获取全局请求上下文RequestContext,然后调用ZuulFilter的runFilter方法执行过滤器逻辑,最后将zuulFilter执行的结果存入上下文RequestContext中并返回执行结果即可。
3.4、Zuul内置过滤器
Zuul过滤器可以通过自定义ZuulFilter实现类来添加,同时Zuul还提供了内置的众多过滤器,分别如下图示,比较核心的就是RibbonRoutingFilter,这个过滤器负载路由转发并且集成了Ribbon的负载均衡功能.
总结:
Zuul本质上就是一个Servlet,并且通过Spring容器管理了一系列的过滤器ZuulFilter实例,各个ZuulFilter有一个类型,包括preRoute、route、postRoute、error四种类型,不同的类型执行的顺序和时机不同。当请求进入Zuul时,由ZuulServlet接收并通过service方法处理。service方法逻辑就是从Spring容器中找到各种类型的ZuulFilter过滤器实例,然后遍历按顺序和时机来执行过滤器的处理逻辑。使用时可以自定义ZuulFilter来对请求的不同时间短进行功能扩展。