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路由配置

第一步:解析配置文件,将路由配置读取到程序中

原文地址:https://www.cnblogs.com/AWSL/p/10608802.html

时间: 2024-08-29 17:21:11

SpringCloud学习记录——网关(Zuul)的相关文章

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

前言 在上篇中介绍了SpringCloud Zuul路由网关的基本使用版本,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的路由网关的过滤器Filter以及异常处理的教程. SpringCloud Zuul Filter 介绍 过滤器概述 Zuul的中心是一系列过滤器,能够在HTTP请求和响应的路由过程中执行一系列操作. 以下是Zuul过滤器的主要特征: 类型:通常在应用过滤器时在路由流程中定义阶段(尽管它可以是任何自定义字

微服务SpringCloud之服务网关zuul一

前面学习了Eureka.Feign.Hystrix.Config,本篇来学习下API网关zuul.在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务.当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端. 为什么需要API Gateway 1.简化客户端调用复杂度 在微服务架构模式下后端服务的实例数一般是动态的,对于客户端而言很难发现动态改变的服务实例的访问地址信息

九、springcloud之服务网关zuul(二)

一.路由熔断 当我们的后端服务出现异常的时候,我们不希望将异常抛出给最外层,期望服务可以自动进行一降级.Zuul给我们提供了这样的支持.当某个服务出现异常时,直接返回我们预设的信息. 我们通过自定义的fallback方法,并且将其指定给某个route来实现该route访问出问题的熔断处理.主要继承ZuulFallbackProvider接口来实现,ZuulFallbackProvider默认有两个方法,一个用来指明熔断拦截哪个服务,一个定制返回内容. public interface ZuulF

SpringCloud 学习--路由网关

在微服务架构中,需要几个基础的微服务,包括服务的注册与发现,服务消费,负载均衡,断路器,咋能路由,配置管理等,由这几几个基础组件相互协作,共用组建一个简单的微服务系统. -Zuul 简介 Zuul是NetFlix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使用.Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能: 1,身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求 2,审查与监控: 3,动态路由:动态将请求路由到不同后端集群 4,负

SpringCloud之路由网关zuul(五)

在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现.服务消费.负载均衡.断路器.智能路由.配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统.一个简答的微服务系统如下图: 注意:A服务和B服务是可以相互调用的,作图的时候忘记了.并且配置服务也是注册到服务注册中心的. 在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul.Ngnix),再到达服务网关(zuul集群),然后再到具体的服.,服务统一注册到高可用的服务注册

spring cloud 学习笔记--网关zuul学习

微服务是多个服务共同完成一件事情,那么"一致对外"就很有必要,就像我们去买面包,不可能先去找农民买小麦,再.... 盗图 spring cloud 引入zuul方式来实现这一功能 添加依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </depend

银联网关跳转支付 学习记录1

最近在做一个项目,里面涉及到了支付功能,使用的是银联的在线支付功能(网关跳转支付).银联给的例子很坑爹,简单的代码只是了一大堆,关键的部分一点儿注释都没有,很多工具类还没有源码.所以学习起来比较吃力.而网上这方面的资料有相对比较少,仅有的一些资料也比较陈旧.所以我打算记录一下我的学习记录,说不定会对别人也有一些帮助. 下载银联Demo 我使用的是银联的网关跳转支付功能,需要完成在线支付,支付完跳转,并接受银联的通知更新自己数据库的功能. 银联技术服务的首页地址是https://open.unio

白话SpringCloud | 第十一章:路由网关(Zuul):利用swagger2聚合API文档

前言 通过之前的两篇文章,可以简单的搭建一个路由网关了.而我们知道,现在都奉行前后端分离开发,前后端开发的沟通成本就增加了,所以一般上我们都是通过swagger进行api文档生成的.现在由于使用了统一路由网关了,都希望各微服务的api文档统一的聚合在网关服务中,也方便前端用户查阅,不需要每个服务单独查看.当然了,也是可以做一个文档索引网页进行各微服务的api文档链接的.今天,我们就来讲下使用swagger实现自动化聚合微服务文档功能. 注:关于Swagger的介绍和使用,由于在之前的Spring

跟我学SpringCloud | 第九篇:服务网关Zuul初

SpringCloud系列教程 | 第九篇:服务网关Zuul初探 前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散,Spring Cloud Config服务集群配置中心,似乎一个微服务框架已经完成了. 我们还是少考虑了一个问题,外部的应用如何来访问内部各种各样的微服务呢?在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务.当添加API网关后,在第三方调用端