首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微服务网关:Spring Cloud Gateway —— Zuul

微服务网关:Spring Cloud Gateway —— Zuul

原创
作者头像
程序员架构进阶
修改2021-04-12 10:23:25
6180
修改2021-04-12 10:23:25
举报
文章被收录于专栏:架构进阶架构进阶架构进阶

系列文章:

微服务架构:网关概念与 zuul

一 摘要

关于服务网关,我们在微服务架构:网关概念与 zuul这篇文章中做了一个基础介绍。包括网关概念,Spring Cloud 体系内的 zuul1.x 和 zuul2.x(gateway)之间的相似和差别之处。本篇将通过官方示例来详细分析 Spring Cloud Gateway 的工作原理和使用方式。

二 Spring Cloud Gateway

2.1 定位

这里我们还是先引用官网Spring Cloud Gateway中对网关的描述:

This project provides a library for building an API Gateway on top of Spring WebFlux. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.

Spring Cloud Gateway 提供一个用于为 Spring WebFlux 顶层构建 API 网关的库。旨在提供一个简单且高效的方式来路由到 API,并为它们提供横切关注点例如:安全、监控/指标,和弹性。

2.2 特性

1)基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0

2)能够匹配任意请求属性上的路由

3)谓词和过滤器特定于路由

4)断路器集成

5)Spring Cloud 服务发现客户端集成

6)易于编写谓词和过滤器

7)请求限流

8)路径重写

三 Spring Cloud Gateway 示例

3.1 官方 demo

官方代码示例:spring-guides/gs-routing-and-filtering,使用方法在 官方指南中已有描述。依赖环境:

  • JDK 1.8 以上
  • Gradle 4+ 或 maven 3.2+

使用方法:

git clone https://github.com/spring-guides/gs-routing-and-filtering.git

如果要从头开始编写,那么可以参考 Set up a Microservice从头编写代码;

也可以直接进入目录:gs-routing-and-filtering/complete,导入 idea,自动加载依赖后,就可以启动工程。

3.2 导入时可能遇到的小问题

导入到 idea 后,run 时,可能会报如下错误(无效的目标发行版):

pom.xml 的 java-version 配置问题:

这两处的版本改为本地 jdk 版本即可。我这里是 jdk1.8,所以修改如下:

	<properties><!--		<java.version>11</java.version>-->		<java.version>8</java.version>	</properties>

复制代码

之后启动正常。

如果还有报错,看一下 compiler,是否如下所示:

如果是,那么改为选择 8 即可。

3.3 测试-使用方式

book 服务端口为 8090,内部提供了两个接口:

  @RequestMapping(value = "/available")  public String available() {    return "Spring in Action";  }
  @RequestMapping(value = "/checked-out")  public String checkedOut() {    return "Spring Boot in Action";  }

可以通过 http://localhost:8090/available 直接访问,或者通过 gateway+服务名访问:http://localhost:8080/books/available

服务名和地址配置在 application.properties:

zuul.routes.books.url=http://localhost:8090
ribbon.eureka.enabled=false
server.port=8080

请求成功后,在 gateway 的控制台日志中能够看到请求记录:

2021-04-09 21:30:44.178 INFO 46107 --- [nio-8080-exec-1] c.e.r.filters.pre.SimpleFilter : GET request to http://localhost:8080/books/available

三 Gateway 原理及过程分析

3.1 相关代码

回过头来看一下代码,结构非常简单,

1)book 服务只有 RoutingAndFilteringBookApplication.java 一个类,启动 SpringBootApplication 并暴露两个 Rest 接口;

2)gateway 除了 RoutingAndFilteringGatewayApplication.java 外,增加了一个 SimpleFilter:

public class SimpleFilter extends ZuulFilter {  private static Logger log = LoggerFactory.getLogger(SimpleFilter.class);
  @Override  public String filterType() {    return "pre";  }
  @Override  public int filterOrder() {    return 1;  }
  @Override  public boolean shouldFilter() {    return true;  }
  @Override  public Object run() {    RequestContext ctx = RequestContext.getCurrentContext();    HttpServletRequest request = ctx.getRequest();
    log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
    return null;  }
}

其中,run()方法中,我们可以看到获取到了调用的请求,并做了日志打印。

3.2 注解生效过程解析

在 gateway 工程,自定义的 SimpleFilter 没有加任何注解,只是继承了 ZuulFilter,那么请求时是通过怎样的调用链走到了这里的?应用入口文件 RoutingAndFilteringGatewayApplication 只是正常的 SpringApplication.run()方法,唯一与之前的差异是有 @EnableZuulProxy 的注解,那么显然网关使用的 Filter 就是通过注解来实现的过滤了。我们接下来详细分析这个过程。

3.2.1 EnableZuulProxy 注解

@EnableCircuitBreaker@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Import({ZuulProxyMarkerConfiguration.class})public @interface EnableZuulProxy {}

注解比较简单,前面还是标准的 Target、Retention,但引入了 ZuulProxyMarkerConfiguration.class,我们继续看一下这个类:

3.2.2 ZuulProxyMarkerConfiguration

@Configuration(    proxyBeanMethods = false)public class ZuulProxyMarkerConfiguration {    public ZuulProxyMarkerConfiguration() {    }
    @Bean    public ZuulProxyMarkerConfiguration.Marker zuulProxyMarkerBean() {        return new ZuulProxyMarkerConfiguration.Marker();    }
    class Marker {        Marker() {        }    }}

这里也没有很多业务逻辑,只是 1)定义了一个内部类 Marker,而且仅有无参构造方法;2)提供 zuulProxyMarkerBean()方法用于返回 Marker 实例作为 Bean。

3.2.3 ZuulProxyAutoConfiguration

从注解只能看到上面一步,那么接下来就考虑自动装配了。我们找到了 ZuulProxyAutoConfiguration 这个类,比较直接,从名字就能看出是负责 ZuulProxy 自动装配的:

@Configuration(    proxyBeanMethods = false)@Import({RestClientRibbonConfiguration.class, OkHttpRibbonConfiguration.class, HttpClientRibbonConfiguration.class, HttpClientConfiguration.class})@ConditionalOnBean({Marker.class})public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {

这里的 Marker,就是 ZuulProxyMarkerConfiguration.Marker

我们回顾一下自动装配,需要满足两个条件:1)spring.factories;2)@ConditionalOnxxx。我们在启动类加上了 @EnableZuulProxy 注解,之后 ZuulProxyAutoConfiguration 就会被自动装配。

接下来看一下 zuul 的属性配置是如何加载的,这里就涉及到了 ZuulProperties。

3.2.4 ZuulProperties

我们在.yml 或.properties 文件配置的参数都会被加载到这个类中。其中包含的大量参数和方法,这里不一一列举,重点看一下构造方法:

public ZuulProperties() {    this.ribbonIsolationStrategy = ExecutionIsolationStrategy.SEMAPHORE;    this.semaphore = new ZuulProperties.HystrixSemaphore();    this.threadPool = new ZuulProperties.HystrixThreadPool();    this.setContentLength = false;    this.includeDebugHeader = false;    this.initialStreamBufferSize = 8192;}

从中我们可以看出 zuul 和 ribbon 和 hystrix 集成的一些端倪,支持了 Hystrix 的信号量和线程池两种模式。

3.3 Filter 执行过程解析

接下来我们继续分析执行过滤器的过程。

3.3.1 ZuulServlet

ZuulServlet 在 init()中完成初始化流程并协调 ZuulFilter 的执行。

3.3.2 FilterProcessor

这是执行过滤器的核心类,定义了 4 个用来判断当前 filter 的 FilterType()返回类型的方法。

当客户端在发送请求时 Zuul 会开辟一个线程池 线程执行时 ZuulServlet.server 方法拦截到对应请求此时该方法会调用 FilterProcessor 中的方法依次初始化 zuul 的 pre,rout,post,error 各个阶段的过滤器。当过滤器执行过滤完请求后,请求响应 执行结束。

我们选择其中的preRoute()方法来进行说明:

try {
    this.runFilters("pre");
} catch (ZuulException var2) {
    throw var2;
} catch (Throwable var3) {
    throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
}

调用了类中的runFilters("pre"),我们继续来看:

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;
            }
        }
    }

    return bResult;
}

从这个方法中就可以了解各继承了ZuulFilter的Filter实现,是如何被组织构成调用链的了。

Filter列表获取,使用的是FilterLoader.getInstance().getFiltersByType(sType);

下面是FilterLoader相关的代码(已省略了无关部分):

public class FilterLoader {
    //省略了其他无关代码

    static final FilterLoader INSTANCE = new FilterLoader();
    
    public static FilterLoader getInstance() {
        return INSTANCE;
    }
    
    public List<ZuulFilter> getFiltersByType(String filterType) {
        List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType);
        if (list != null) {
            return list;
        } else {
            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);
                }
            }

            Collections.sort(list);
            this.hashFiltersByType.putIfAbsent(filterType, list);
            return list;
        }
    }
    
        //省略了其他无关代码    
}

FilterLoader的实例是采用单例模式,用的是饿汉模式的实现;

getFiltersByType()方法中,使用了hashFiltersByType.get()获取对应的Filter列表。hashFiltersByType是一个ConcurrentHashMap,定义如下:

ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap();

四 总结

本文基于 zuul2.2.6.RELEASE 版本,通过一个官方示例工程了解了 Spring Cloud Gateway(Zuul2)的结构、使用方法和主要执行过程。后续将结合 Spring Cloud 的配置中心、Hystrix 继续做深入分析,并加入与 nacos 等其他注册中心/网关的对比。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一 摘要
  • 二 Spring Cloud Gateway
    • 2.1 定位
      • 2.2 特性
      • 三 Spring Cloud Gateway 示例
        • 3.1 官方 demo
          • 3.2 导入时可能遇到的小问题
            • 3.3 测试-使用方式
            • 三 Gateway 原理及过程分析
              • 3.1 相关代码
                • 3.2 注解生效过程解析
                  • 3.2.1 EnableZuulProxy 注解
                  • 3.2.2 ZuulProxyMarkerConfiguration
                  • 3.2.3 ZuulProxyAutoConfiguration
                  • 3.2.4 ZuulProperties
                • 3.3 Filter 执行过程解析
                  • 3.3.1 ZuulServlet
                  • 3.3.2 FilterProcessor
              • 四 总结
              相关产品与服务
              API 网关
              腾讯云 API 网关(API Gateway)是腾讯云推出的一种 API 托管服务,能提供 API 的完整生命周期管理,包括创建、维护、发布、运行、下线等。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档