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;
    }              
processZuulFilter(ZuulFilter filter)

过滤器的分析就到此为止,现在来看看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)

SpringCloud学习系列-zuul路由网关

SpringCloud 学习 --- Zuul基本概念配置

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

springCloud学习05之api网关服务zuul过滤器filter

微服务SpringCloud之服务网关zuul一