SpringCloud学习记录——网关(Zuul)
Posted awsl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud学习记录——网关(Zuul)相关的知识,希望对你有一定的参考价值。
在微服务中网关的作用:
1.接收请求——与一般访问相同
2.转发请求
3.请求过滤
网关作用图示:
从上图可以看出,网关的作用实际上是对原始的请求方式插入了一层;在请求中间加入了一层网关,这样使得外部的所有请求都导向网关,再由网关来转发给具体服务处理。
加入网关的优势:
1.请求统一:原本的请求方式可能会因为访问的服务不同,出现访问地址也不同的问题,插入网关之后,访问地址统一指向网关;对于请求方而言,只需要记住一个访问地址就可以了,省事了;
2.通用功能的统一处理:与请求相关的各种操作都可以在网关这里先完成,例如请求过滤,权限控制等功能抽取出来进行统一处理;
3.负载均衡:网关本身是对请求进行了拦截的,拿到请求后自然就可以做一些处理,比如同一服务的多次请求就可以转发给不同的服务器处理;
缺点:
1.请求链路变长:从请求参与角色来看,加入网关的情况下,请求链路就增加了一个节点,提高了请求的复杂度,比如当一个请求出现问题时,就需要额外考虑是否网关出问题了,原本没有网关的时候自然不用考虑。
2.网关成为了微服务中的单点:通常分布式服务都会尽可能地避免服务单点现象,因为单点现象很容易造成个体影响整体的情况;比如单一网关的情况下,一旦网关服务挂掉,基本整个服务就瘫痪了;为了解决这个问题,可以设置网关集群,降低网关服务不可用的概率。
1.搭建网关服务
1.1SpringBoot项目
1.首先创建一个SpringBoot微服务(后面单独写,这里就不写了)
2.引入相关依赖
<!--zuul网关依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!--springcloud核心依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-core</artifactId> <version>2.0.0.M2</version> </dependency>
3.开启网关服务——启动类上加注解@EnableZuulProxy
开启网关注解后,网关服务可以使用
@EnableEurekaClient @EnableZuulProxy //开启Zuul的API网关服务功能 @SpringBootApplication public class GateOfZuul { public static void main(String[] args){ SpringApplication.run(GateOfZuul.class,args); } }
4.配置文件——application.yml
server: port: 5580 #端口号 spring: application: name: service-zuul #服务注册中心测试名 zuul: routes: api-a: path: /ribbon/** serviceId: service-ribbon api-b: path: /feign/** serviceId: service-feign eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ #服务注册中心
上述网关配置中;涉及网关的路由转发的配置是zuul.routes下的2个
其中api-a、api-b表示的是不同的微服务,可以自定义,但最好和微服务名称保持一致;path表示的是请求访问的路径;serviceId表示的则是微服务在注册中心的注册名称;path与serviceId是一 一对应关系,表示的是“ 如果是/ribbon/**路径下的请求,则跳转到service-ribbon”
该配置的主要作用就是定义好网关的路由规则,将不同的请求路由到不同的服务中;
2.网关服务功能探究
网关存在的意义是为了提供服务,那么身为一个网关,它所应该具有的能力有哪些呢?
1.接收请求:网关最终的能力就是接收请求,然后将请求转发出去;那么首先它就要有MVC的能力,则它需要实现servlet;
2.发出请求:网关需要将请求转发到其他服务,那么它就要有发送请求的能力,则它需要实现Http相关方法;
3.过滤请求:网关提供对请求的权限、日志等操作,那么他就要有过滤请求的能力,则它需要实现filter;
4.获取服务列表:网关提供路由功能,那么它就需要获取到路由地址,从微服务的架构设置来看,即它需要从注册中心拿到服务列表;
5.路由配置:网关实现路由操作,那么就需要设置请求路径与服务的对应关系;
2.1.网关接收请求
实际项目中,对于网关的开发,我们通常只需要配置过滤器以及配置文件中的路由转发,那么网关是如何做到接收请求的呢?
既然接收请求不需要我们来做,那么就肯定是已经有人帮我们做了,由此可以推断源码中应该已经写好了相关的处理。
那么现在就来分析源码中到底是如何做到的:
Servlet初始化
要实现网关访问,则首先要配置好servlet;
SpringBoot中通过ServletRegistrationBean 这个类实现了对servlet的注册;这里可能还是沿用的springMVC的那套DispatcherServlet;至于具体怎么注册的等分析SpringBoot时再研究!
下面通过源码来说明Zuul网关如何配置访问路径的
首先Zuul网关的配置类是ZuulServerAutoConfiguration;在该类中配置了Servlet及过滤规则
ZuulServerAutoConfiguration中配置了servlet及过滤规则;该类中通过ZuulServlet对根路径/进行过滤;
@Bean @ConditionalOnMissingBean( name = {"zuulServlet"} ) public ServletRegistrationBean zuulServlet() { ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean(new ZuulServlet(), new String[]{this.zuulProperties.getServletPattern()}); servlet.addInitParameter("buffer-requests", "false"); return servlet; } /*其中拦截规则是在zuulProperties配置的*/ public String getServletPattern() { String path = this.servletPath; if (!path.startsWith("/")) { path = "/" + path; } if (!path.contains("*")) { path = path.endsWith("/") ? path + "*" : path + "/*"; } return path; }
处理器映射器与处理器适配器为默认配置,无需处理
继续分析这个配置类,可以看到有ZuulController这个配置
@Bean public ZuulController zuulController() { return new ZuulController(); }
到这里可以看出,在网关这里,zuul组件将所有的请求都会走到ZuulController中,由它来统一处理;处理方法为handleRequest方法
当一个url请求访问网关时,服务器会来到ZuulController这个类中:
public class ZuulController extends ServletWrappingController { /*从这里可以看出,zuulController在初始化时,会传入zuulServlet这个实例*/ public ZuulController() { this.setServletClass(ZuulServlet.class); this.setServletName("zuul"); this.setSupportedMethods((String[])null); } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView var3; try { var3 = super.handleRequestInternal(request, response); } finally { RequestContext.getCurrentContext().unset(); } return var3; } }
在zuulController中,只有handleRequest这一个方法来处理请求,而该方法最终调用的是其父类ServletWrappingController中的方法
public class ServletWrappingController extends AbstractController implements BeanNameAware, InitializingBean, DisposableBean { @Nullable private Class<? extends Servlet> servletClass; @Nullable private String servletName; private Properties initParameters = new Properties(); @Nullable private String beanName; @Nullable private Servlet servletInstance; public ServletWrappingController() { super(false); } public void setServletClass(Class<? extends Servlet> servletClass) { this.servletClass = servletClass; } public void setServletName(String servletName) { this.servletName = servletName; } public void setInitParameters(Properties initParameters) { this.initParameters = initParameters; } public void setBeanName(String name) { this.beanName = name; } public void afterPropertiesSet() throws Exception { if (this.servletClass == null) { throw new IllegalArgumentException("‘servletClass‘ is required"); } else { if (this.servletName == null) { this.servletName = this.beanName; } this.servletInstance = (Servlet)ReflectionUtils.accessibleConstructor(this.servletClass, new Class[0]).newInstance(); this.servletInstance.init(new ServletWrappingController.DelegatingServletConfig()); } } /**zuulController处理请求最终调用的是这个方法
*其中servletInstance就是ZuulController实例化时一起实例化的zuulServlet
*可以看出最终请求做到了zuulServlet的service方法中
*/ protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { Assert.state(this.servletInstance != null, "No Servlet instance"); this.servletInstance.service(request, response); return null; } public void destroy() { if (this.servletInstance != null) { this.servletInstance.destroy(); } } private class DelegatingServletConfig implements ServletConfig { private DelegatingServletConfig() { } @Nullable public String getServletName() { return ServletWrappingController.this.servletName; } @Nullable public ServletContext getServletContext() { return ServletWrappingController.this.getServletContext(); } public String getInitParameter(String paramName) { return ServletWrappingController.this.initParameters.getProperty(paramName); } public Enumeration<String> getInitParameterNames() { return ServletWrappingController.this.initParameters.keys(); } } }
到这里位置,url请求最终走到了ZuulServlet的service中,现在来分析url请求在zuulServlet中时如何走下去的
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { try { this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try {
//前置过滤器pre this.preRoute(); } catch (ZuulException var12) {
// this.error(var12); this.postRoute(); return; } try {
// route过滤器 this.route(); } catch (ZuulException var13) { this.error(var13); this.postRoute(); return; } try {
//后置过滤器 this.postRoute(); } catch (ZuulException var11) {
//错误处理方法 this.error(var11); } } catch (Throwable var14) { this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
this.zuulRunner.init(servletRequest, servletResponse);
}
从上面代码可以看出,url请求会被init方法处理,而init方法实际调用的是zuulRunner的init方法;
之后,代码会执行preRoute(),route(),postRoute()方法,而这三个方法实际就是我们做路由以及过滤等功能的切入点。
现在来分析这3个方法是如何让我们实现过滤以及路由功能的;
首先进行代码追踪,看看这3个方法最后到底是如何运作的
/*在ZuulServlet中*/ void postRoute() throws ZuulException { this.zuulRunner.postRoute(); } void route() throws ZuulException { this.zuulRunner.route(); } void preRoute() throws ZuulException { this.zuulRunner.preRoute(); } void error(ZuulException e) { RequestContext.getCurrentContext().setThrowable(e); this.zuulRunner.error(); } /**可以看出最终这3个方法与init方法一样,最终都走到了zuulRunner中了*/ /**在ZuulRunner中*/ public void postRoute() throws ZuulException { FilterProcessor.getInstance().postRoute(); } public void route() throws ZuulException { FilterProcessor.getInstance().route(); } public void preRoute() throws ZuulException { FilterProcessor.getInstance().preRoute(); } public void error() { FilterProcessor.getInstance().error(); } /**可以看出方法统一由FilterProcessor来处理,而FilterProcessor是一个单例模式*/ /**在FilterProcessor类中*/ /**其他几个方法基本相似,只是参数不同而已,这里就不列出来了*/ public void postRoute() throws ZuulException { try { this.runFilters("post"); } catch (ZuulException var2) { throw var2; } catch (Throwable var3) { throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + var3.getClass().getName()); } } /** 这个方法通过参数判断filter的不同类型,返回不同的filter集合列表;并执行相应的filter方法*/ 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 = (ZuulFilter)list.get(i);
//过滤器处理过程 Object result = this.processZuulFilter(zuulFilter); if (result != null && result instanceof Boolean) { bResult |= ((Boolean)result).booleanValue(); } } } return bResult; } /**FilterLoader类是用来加载filter的*/ public List<ZuulFilter> getFiltersByType(String filterType) { List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType); if (list != null) { return list; } else {
//获取所有的过滤器;从filterRegistry List<ZuulFilter> list = new ArrayList(); Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters(); Iterator iterator = filters.iterator(); while(iterator.hasNext()) { ZuulFilter filter = (ZuulFilter)iterator.next();
//按照类型提取过滤器 if (filter.filterType().equals(filterType)) { list.add(filter); } } //过滤器重排序,按照过滤器filterOrder中定义的值
Collections.sort(list);
this.hashFiltersByType.putIfAbsent(filterType, list); return list;
}
}
/**FilterRegistry类————该类是用来存放filter的,通过spring,系统中的filter最终都会存放在这里*/
/**从这里可以看出,只有实现了ZuulFilter的过滤器才会被调用*/
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap();
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
自定义的filter的方法执行是在FilterProcessor类中的processZuulFilter方法中执行的
public Object processZuulFilter(ZuulFilter filter) throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); boolean bDebug = ctx.debugRouting(); String metricPrefix = "zuul.filter-"; long execTime = 0L; 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(); } // 本方法的核心代码就在这里,运行filter 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); } } if (t != null) { throw t; } else { this.usageNotifier.notify(filter, s); return o; } } catch (Throwable var15) { if (bDebug) { Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + var15.getMessage()); } this.usageNotifier.notify(filter, ExecutionStatus.FAILED); if (var15 instanceof ZuulException) { throw (ZuulException)var15; } else { ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType() + ":" + filterName); ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime); throw ex; } } } /**ZuulFilter类中的runFilter()方法*/ public ZuulFilterResult runFilter() { ZuulFilterResult zr = new ZuulFilterResult(); if (!this.isFilterDisabled()) { // 判断过滤器是否需要执行 if (this.shouldFilter()) { Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName()); try { //执行自定义过滤器中的run方法 Object res = this.run(); zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS); } catch (Throwable var7) { t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed"); zr = new ZuulFilterResult(ExecutionStatus.FAILED); zr.setException(var7); } finally { t.stopAndLog(); } } else { zr = new ZuulFilterResult(ExecutionStatus.SKIPPED); } } return zr; }
过滤器的分析就到此为止,现在来看看url在ZuulRunner类中是如何处理的
/**在ZuulRunner类中*/ /**在该方法中,url的请求与相应被封装在了RequestContext中 * 其中RequestContext是一个单例的,且使用ThreadLocal进行处理过,能够保证是线程安全的
* */ public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { RequestContext ctx = RequestContext.getCurrentContext(); if (this.bufferRequests) { ctx.setRequest(new HttpServletRequestWrapper(servletRequest)); } else { ctx.setRequest(servletRequest); } ctx.setResponse(new HttpServletResponseWrapper(servletResponse)); }
2.2路由配置
第一步:解析配置文件,将路由配置读取到程序中
以上是关于SpringCloud学习记录——网关(Zuul)的主要内容,如果未能解决你的问题,请参考以下文章
SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理