Spring Cloud 基础组件 - Zuul

May . 27 . 2021

前言

项目地址 spring-cloud-zuul-demo

      这篇文章我将使用 Spring CloudNetflix Zuul 来实现一个服务网关,服务网关充当着服务客户端和被调用的服务之间的中介, 服务客户端仅与服务网关管理的单个URL进行交互。有了服务网关, 服务客户端永远不会直接调用单个服务的URL, 而是将所有调用都放到服务网关上。如下图:

zuul2.jpg

      由于服务网关位于客户端到各个服务的所有调用之间, 因此它还充当服务调用的中央策略执行点(PEP)。使用集中式PEP意味着横切服务关注点可以在一个地方实现, 无需在每个服务中重复实现。举例来说, 可以在服务网关中实现的横切关注点包括以下几个。

  1. 静态路由 - 服务网关将所有的服务调用放置在单个 URL 和 API 路由的后面。
  2. 动态路由 - 服务网关可以检查传入的服务请求, 根据来自传入请求的数据和服务调用者的身份来智能路由。
  3. 验证和授权 - 由于所有服务调用都经过服务网关进行路由, 所以服务网关是检查服务调用者是否已经进行了验证的绝佳场所。
  4. 数据收集和日志记录 - 当服务调用通过服务网关时, 可以使用服务网关来收集数据和日志信息。



具体实现

      Spring Cloud 集成了 Netflix Zuul, Zuul 是一个服务网关, 它可以很容易的就和 Spring Cloud 集成, 他提供了以下功能:

  1. 将服务映射到一个URL上 - 使用服务网关构建一个单一的入口点, 所有客户端服务调用都将经过这个入口点。
  2. 对通过网关的请求进行过滤 - 开发人员可以使用过滤器来对请求进行过滤。

1. 配置

      新建 Spring Boot 工程, 并引入相关依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

      为项目的引导类添加 Zuul 注解, Zuul 提供类两种注解 @EnableZuulServer 与 @EnableZuulProxy, 前者不会加载任何 Zuul 反向代理过滤器, 也不会使用 Eureka 进行服务发现, 如果需要与 Eureka 之外的其它服务发现引擎集成时可以使用该注解,  这里我使用后者。

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

      对 Zuul 参数进行配置, 这里我禁用所有服务发现的路由, 使用静态的URL手动映射路由, 并为所有URL加上 '/api' 的前缀, 如下图所示:

zuul3.jpg

application.yml

server:
  port: 7019

zuul:
  # 禁用所有服务发现路由
  ignored-services: '*'
  # 前缀
  prefix: /api
  routes:
    clienta: /clienta/**
    clientb: /clientb/**

eureka:
  instance:
    # 注册服务的 IP 而不是名称
    prefer-ip-address: true
  client:
    # 注册自身到 eureka
    register-with-eureka: true
    # 在本地缓存注册表 每隔 30s 自动刷新服务
    fetch-registry: true
    service-url:
      # 配置注册中心地址
      defaultZone: http://localhost:7016/eureka/

     至此 Zuul 的基础配置就结束了, 访问对应的网关路由就可以调用指定的服务了。



1. 过滤器

      Zuul 允许开发人员使用网关内的过滤器构建自定义逻辑。过滤器可用与实现每个服务请求在执行时都会经过的业务逻辑链。

      Zuul 支持以下 3 种类型的过滤器: 

  1. 前置过滤器 - 在 Zuul 将实际请求发送到目的地之前被调用。
  2. 后置过滤器 - 在目标服务被调用并将响应发送回客户端后被调用。
  3. 路由过滤器 - 用于在调用目标服务之前拦截调用。

      下图详细介绍了各个过滤器的作用和位置:

zuul4.jpg

图片来自 Spring Microservices In Action 一书

a. 前置过滤器

TrackingFilter

@Component
@Slf4j
@AllArgsConstructor
public class TrackingFilter extends ZuulFilter {

    private static final int FILTER_ORDER = 1;
    private static final boolean SHOULD_FILTER = true;

    /**
     * 告诉 Zuul 该过滤器的类型 (前置 | 路由 | 后置)
     *
     * @return {@link String}
     * @createTime 2021/3/8 22:31
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    /**
     * 定义不同类型过滤器的执行顺序
     *
     * @return {@link int}
     */
    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    /**
     * 判断该过滤器是否要执行
     *
     * @return {@link boolean}
     */
    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    /**
     * 过滤器逻辑代码
     *
     * @return {@link Object}
     * @author nza
     * @createTime 2021/3/8 22:40
     */
    @Override
    public Object run() throws ZuulException {

        // 获取当前请求上下文
        RequestContext ctx = RequestContext.getCurrentContext();

        log.info("TrackingFilter 处理请求: {}", ctx.getRequest().getRequestURI());

        return null;
    }
}

b. 后置过滤器

ResponseFilter

@Component
@Slf4j
@AllArgsConstructor
public class ResponseFilter extends ZuulFilter {

    private static final int FILTER_ORDER = 1;
    private static final boolean SHOULD_FILTER = true;


    /**
     * 后置过滤器
     *
     * @return {@link String} 过滤器类型
     */
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }

    /**
     * 定义不同类型过滤器的执行顺序
     *
     * @return {@link int}
     */
    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    /**
     * 判断该过滤器是否要执行
     *
     * @return {@link boolean}
     */
    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    /**
     * 拦截器业务逻辑
     */
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        log.info("ResponseFilter 处理请求响应: {}", ctx.getRequest().getRequestURI());
        return null;
    }
}

      路由过滤器的实现方式大同小异, 这里就不再赘述。



小结

      Spring Cloud 使得构建服务网关十分的简单, 只需要简单的进行配置搭建起一个服务网关Zuul 默认与 Eureka 进行集成, 可以自动的通过注册的服务名来映射路由Zuul 提供了三种过滤器, 即前置过滤器, 后置过滤器和路由过滤器。关于 Zuul 的更多配置信息, 可以访问官网查看。