SpringCloud组件之网关Zuul(Hoxton版本)

1,599 阅读8分钟

1.Zuul简介

API网关为微服务架构中的服务提供了统一的访问入口,客户端通过API网关访问相关服务。API网关的定义类似于设计模式中的门面模式,它相当于整个微服务架构中的门面,所有客户端的访问都通过它来进行路由及过滤。它实现了请求路由、负载均衡、校验过滤、服务容错、服务聚合等功能。

2.创建Spring Cloud Zuul工程

2.1在pom中添加相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-demo</artifactId>
        <groupId>com.hxmec</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-zuul</artifactId>

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


</project>

2.2 增加配置文件

bootstrap.yml配置如下

server:
  port: 9200
spring:
  application:
    name: zuul-proxy
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8888/eureka/

application.yml配置如下:

zuul:
  routes: #给服务配置路由
    eureka-client-provider:
      path: /provider/**
    eureka-client-consumer:
      path: /consumer/**

2.3 创建启动类ZuulApplication

启动类代码如下:

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulApplication {

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

2.4启动程序,进行测试

启动之前文中创建的spring-cloud-eureka-server,spring-cloud-eureka-client-provider,spring-cloud-eureka-client-consumer,以及网关Zuul工程。

测试地址如下:

3.Zuul常用功能

3.1 配置路由规则

我们可以通过修改application.yml中的配置来配置路由规则,这里我们将匹配/provider/** 的请求路由到eureka-client-provider服务上去,匹配/consumer/**的请求路由到eureka-client-consumer上去。

具体配置如下:

zuul:
  #关闭默认路由配置
  ignored-services: eureka-client-provider,eureka-client-consumer
  routes: #给服务配置路由
    eureka-client-provider:
      path: /provider/**
    eureka-client-consumer:
      path: /consumer/**

访问http://localhost:9200/provider/demo/hello 路由到eureka-client-provider上; 访问http://localhost:9200/consumer/test/callHello 同样可以路由到了eureka-client-consumer上了。

3.2 配置访问前缀

配置如下

zuul:
  #配置统一访问前缀
  prefix: /zuul
  routes: #给服务配置路由
    eureka-client-provider:
      path: /provider/**
    eureka-client-consumer:
      path: /consumer/**

所有的请求统一前缀需要加上/zuul 访问http://localhost:9200/zuul/provider/demo/hello 路由到eureka-client-provider上;

3.3 Header过滤及重定向添加Host

  • Zuul在请求路由时,默认会过滤掉一些敏感的头信息,以下配置可以防止路由时的Cookie及Authorization的丢失:
zuul:
  sensitive-headers: Cookie,Set-Cookie,Authorization #配置过滤敏感的请求头信息,设置为空就不会过滤
  • Zuul在请求路由时,不会设置最初的host头信息,以下配置可以解决:
zuul:
  add-host-header: true #设置为true重定向是会添加host请求头

3.4 查看路由信息

可以通过SpringBoot Actuator来查看Zuul中的路由信息。

  • 在pom.xml中添加相关依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

  • 增加开启查看路由的端点配置
#路由的端点
management:
  endpoints:
    web:
      exposure:
        include: 'routes'

4.过滤器

路由与过滤是Zuul的两大核心功能,路由功能负责将外部请求转发到具体的服务实例上去,是实现统一访问入口的基础,过滤功能负责对请求过程进行额外的处理,是请求校验过滤及服务聚合的基础。

4.1 过滤器类型

zuul过滤器有如下几种:

  • pre:在请求被路由到目标服务前执行,比如权限校验、打印日志等功能;
  • routing:在请求被路由到目标服务时执行,这是使用Apache HttpClient或Netflix Ribbon构建和发送原始HTTP请求的地方;
  • post:在请求被路由到目标服务后执行,比如给目标服务的响应添加头信息,收集统计数据等功能;
  • error:请求在其他阶段发生错误时执行。

4.2 过滤器的生命周期

Filter的生命周期有4个,分别是 “PRE”、“ROUTING”、“POST” 和“ERROR”,整个生命周期可以用下图来表示

Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。

  • PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • ERROR:在其他阶段发生错误时执行该过滤器。

4.3 自定义过滤器

在Zuul网关中,我们需要自定义一个类来继承ZuulFilter抽象类并实现4个相应的抽象方法即可。

如下示例中,我们自定义一个过滤器AccessTokenFilter验证请求有没有accessToken参数:

@Component
@Slf4j
public class AccessTokenFilter extends ZuulFilter {

    /**
     * 过滤器类型 pre 表示在 请求之前进行拦截
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 过滤器的执行顺序。当请求在一个阶段的时候存在多个多个过滤器时,需要根据该方法的返回值依次执行
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 判断过滤器是否生效
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // 获取上下文
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String accessToken = request.getParameter("accessToken");
        if (StringUtils.isEmpty(accessToken)) {
            //setSendZuulResponse(false)令zuul过滤该请求,不进行路由
            currentContext.setSendZuulResponse(false);
            //设置返回的错误码
            currentContext.setResponseStatusCode(401);
            currentContext.setResponseBody("AccessToken is null");
            return null;
        }
        log.info("获取到AccessToken为{}",accessToken);
        // 否则正常执行业务逻辑.....
        return null;
    }

}

重启zuul服务后,验证是否有效

在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写了下面的四个方法来实现自定义的过滤器。这四个方法分别定义了:

  • filterType():过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
  • filterOrder():过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。通过数字指定,数字越大,优先级越低。
  • shouldFilter():判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。
  • run():过滤器的具体逻辑。这里我们通过currentContext.setSendZuulResponse(false)令 Zuul 过滤该请求,不对其进行路由,然后通过currentContext.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如通过currentContext.setResponseBody(body)对返回 body 内容进行编辑等。

4.4 核心过滤器

在 Zuul 中提供了一些默认的 Filter

类型顺序过滤器功能
pre-3ServletDetectionFilter标记处理Servlet的类型
pre-2Servlet30WrapperFilter包装HttpServletRequest请求
pre-1FormBodyWrapperFilter包装请求体
pre5PreDecorationFilter对当前请求进行预处理以便执行后续操作。
pre1DebugFilter标记调试标志
route10RibbonRoutingFilterserviceId请求转发
route500SendForwardFilterforward请求转发
route100SimpleHostRoutingFilterurl请求转发
post900LocationRewriteFilter将Location header重写为Zuul的URL
post0SendErrorFilter处理有错误的请求响应
post1000SendResponseFilter处理正常的请求响应

4.5 Ribbon和Hystrix的支持

由于Zuul已经集成了Ribbon和Hystrix,所以Zuul本身就具有负载均衡和服务容错能力,我们可以通过Ribbon和Hystrix的配置来配置Zuul中的相应功能。

  • 可以使用Hystrix的配置来设置路由转发时HystrixCommand的执行超时时间:
hystrix:
  command: #用于控制HystrixCommand的行为
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000 #配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理
  • 可以使用Ribbon的配置来设置路由转发时请求连接及处理的超时时间:
ribbon: #全局配置
  ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
  ReadTimeout: 3000 #服务请求处理超时时间(毫秒)

4.6 使用 Zuul 进行限流

添加相关依赖:

<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>${latest-version}</version>
</dependency>

本文中使用redis作为数据存储, 使用其他存储参考:github.com/marcosbarbe…

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

spring-cloud-zuul-ratelimit 是和zuul整合提供分布式限流策略的扩展,只需在yml中配置几行配置,就可使应用支持限流:

ratelimit:
  enabled: true
  repository: REDIS #使用redis存储,一定要大写!
  policies:
    eureka-client-provider: #针对上面那个服务的限流
      limit: 100 #每秒多少个请求
      quota: 20 #quota 单位时间内允许访问的总时间(统计每次请求的时间综合)
      refreshInterval: 60 #刷新时间窗口的时间,默认值 (秒)
      type:
        - ORIGIN #这里一定要大写,类型说明:URL通过请求路径区分,ORIGIN通过客户端IP地址区分,USER是通过登录用户名进行区分,也包括匿名用户

ratelimit 支持的限流粒度:

  • 服务粒度 (默认配置,当前服务模块的限流控制)
  • 用户粒度 (详细说明,见文末总结
  • ORIGIN粒度 (用户请求的origin作为粒度控制)
  • 接口粒度 (请求接口的地址作为粒度控制)
  • 以上粒度自由组合,又可以支持多种情况
  • 如果还不够,自定义RateLimitKeyGenerator实现。

支持的存储方式:

  • InMemoryRateLimiter - 使用 ConcurrentHashMap作为数据存储
  • ConsulRateLimiter - 使用 Consul 作为数据存储
  • RedisRateLimiter - 使用 Redis 作为数据存储
  • SpringDataRateLimiter - 使用 数据库 作为数据存储

5.测试项目地址

github.com/ty197287300…