使用Zuul构建API Gateway

一 微服务网关背景及简介

不一样的微服务通常有不一样的网络地址,而外部的客户端可能须要调用多个服务的接口才能完成一个业务需求。好比一个电影购票的收集APP,可能回调用电影分类微服务,用户微服务,支付微服务等。若是客户端直接和微服务进行通讯,会存在一下问题:java

  • 客户端会屡次请求不一样微服务,增长客户端的复杂性
  • 存在跨域请求,在必定场景下处理相对复杂
  • 认证复杂,每个服务都须要独立认证
  • 难以重构,随着项目的迭代,可能须要从新划分微服务,若是客户端直接和微服务通讯,那么重构会难以实施
  • 某些微服务可能使用了其余协议,直接访问有必定困难

上述问题,均可以借助微服务网关解决。微服务网关是介于客户端和服务器端之间的中间层,全部的外部请求都会先通过微服务网关,架构演变成:web

使用网关以后的架构

这样客户端只须要和网关交互,而无需直接调用特定微服务的接口,并且方便监控,易于认证,减小客户端和各个微服务之间的交互次数正则表达式

二 Zuul 简介

Zuul是Netflix开源的微服务网关,他能够和Eureka,Ribbon,Hystrix等组件配合使用。Zuul组件的核心是一系列的过滤器,这些过滤器能够完成如下功能:spring

  • 身份认证和安全: 识别每个资源的验证要求,并拒绝那些不符的请求
  • 审查与监控:
  • 动态路由:动态将请求路由到不一样后端集群
  • 压力测试:逐渐增长指向集群的流量,以了解性能
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
  • 静态响应处理:边缘位置进行响应,避免转发到内部集群
  • 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化

Spring Cloud对Zuul进行了整合和加强。目前,Zuul使用的默认是Apache的HTTP Client,也可使用Rest Client,能够设置ribbon.restclient.enabled=true.后端

三 编写 Zuul 微服务网关

构建网关

添加依赖

<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.M9</spring-cloud.version>
    </properties>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

对于 spring-cloud-starter-netflix-zuul 依赖,能够经过查看它的依赖内容了解到,该模块中不只包含了 Netflix Zuul 的核心依赖 zuul-core,还包含了 hystrix、ribbon、actuator 等依赖。api

启动类加上注解@EnableZuulProxy

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy //开启zuul网关服务功能
@@SpringBootApplication
public class ApiGatewayApplication {

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

application.properties

配置 Zuul 应用的基础信息,如应用名、服务端口号等,具体内容以下:跨域

spring.application.name=api-gateway
server.port=5555

完成上面的工做后,经过 Zuul 实现的 API 网关服务就构建完毕了。安全

请求路由

下面,咱们将经过一个简单的示例来为上面构建的网关服务增长请求路由的功能。为了演示请求路由的功能,咱们现将以前准备的 Eureka 服务注册中心和微服务应用都启动起来。此时,咱们在 Eureka信息面板中能够看到以下图所示的微服务应用已经被注册成功了。服务器

启动微服务应用

传统路由方式

传统路由方式没有与注册中心进行整合,须要维护各个路由path 与 url 的关系。网络

单实例配置

经过 zuul.routes.<route>.path 与 zuul.routes.<route>.url 参数对的方式进行配置。

zuul.routes.hello-service.path=/hello-service/**
zuul.routes.hello-service.url=http://localhost:8080/

该配置实现了对符合 /hello-service/** 规则的请求路径转发到 http://localhost:8080/ 地址的路由规则。好比一个请求 http://localhost:5555/hello-service/hello 被发送到 API 网关上,因为 /hello-service/hello 可以被上面配置的 path 规则匹配,因此 API 网关会被转发请求到 http://localhost:8080/hello 的地址。

多实例配置

经过 zuul.routes.hello-service.path 与 zuul.routes.hello-service.service-id 参数对的方式进行配置。

zuul.routes.hello-service.path=/hello-service/**
zuul.routes.hello-service.service-id=hello-service
ribbon.eureka.enabled=false
hello-service.ribbon.listOfServers=http://localhost:8080/,http://localhost:8081/

注意:

  • ribbon.eureka.enabled: 因为 zuul.<route>.serviceId 指定的是服务名称,默认状况下 Ribbon 会根据服务发现机制来获取配置服务名对应的实例清单。可是,该示例并无整合相似 Eureka 之类的服务治理框架,因此须要将该参数设置为 false,不然配置的 serviceId 获取不到对应实例的清单。
  • hello-service.ribbon.listOfServers: 该参数内容与 zuul.routes.<route>.serviceId 的配置相对应,开头的 hello-service 对应了 serviceId 的值,这两个参数的配置至关于在该应用内部手工维护了服务于实例的对应关系。

服务路由配置

Spring Cloud Zuul 经过与 Spring Cloud Eureka 的整合,实现了对服务实例的自动化维护,因此在使用服务路由配置的时候,不须要像传统路由配置方式那样为 serviceId 指定具体的服务实例地址,只须要经过 zuul.routes.<route>.path 与 zuul.<route>.serviceId 参数对的方式进行配置便可。

zuul.routes.hello-service.path=/hello-service/**
zuul.routes.hello-service.service-id=hello-service

也可使用一种更简洁的配置方式

zuul.routes.hello-service=/hello-service/**

在使用服务路由配置以前,先添加 eureka client 依赖

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

application.properties 中添加注册中心配置。

eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/

启动类上的 @SpringBootApplication 注解换成 @SpringCloudApplication 注解(@SpringCloudApplication 注解引入了 @SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker 注解)。

@EnableZuulProxy //开启zuul网关服务功能
@SpringCloudApplication
public class ApiGatewayApplication {

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

服务路由的默认规则

当咱们为 Spring Cloud Zuul 构建的 API 网关服务引入 Spring Cloud Eureka 以后,它为 Eureka 中的每一个服务都自动建立一个默认路由规则,这些默认规则的 path 会使用 serviceId 配置的服务名做为请求前缀,就如上面的例子那样。

zuul.routes.hello-service.path=/hello-service/**
zuul.routes.hello-service.service-id=hello-service

因为默认状况下全部Eureka 上的服务都会被 Zuul 自动地建立映射关系来进行路由,这会使得一些咱们不但愿对外开放的服务也可能被外部访问到。这个时候,咱们可使用 zuul.ignored-services 参数来设置一个服务名匹配表达式来定义不自动建立路由的规则。

# 对全部的服务都不自动建立路由
zuul.ignored-services=*

自定义路由映射规则

可使用 PatternServiceRouteMapper 对象来自定义服务于路由映射的生成关系。

例如,下面的例子为 hello-service-v1 的微服务的服务名建立相似 v1/hello-service/** 的路由规则。

@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
   // servicePattern: 指的是微服务的pattern
   // routePattern: 指的是路由的pattern
   return newPatternServiceRouteMapper(
         "(?<name>^.+)-(?<version>v.+$)",
         "${version}/${name}"
   );

}

注:

  • PatternServiceRouteMapper 使用的 java 1.7 中引入的命名捕获组来实现服务名匹配以及获取到匹配的内容的。关于命名捕获组能够参考:
    Java 正则表达式:捕获组、反向引用、捕获组命名
  • 若是没有匹配上 PatternServiceRouteMapper 定义的规则仍是会使用默认的路由映射规则,即采用完整服务名做为前缀的路径表达式

忽略表达式

Zuul 提供了一个忽略表达式参数 zuul.ignored-patterns,用于设置不但愿被 API 网关进行路由的 URL 表达式。

如西面的设置会使得路径中包含 /hello/ 的 url 不被路由。

zuul.ignored-patterns=/**/hello/**

路由前缀

zuul 提供了 zuul.prefix 参数来为网上上的路由的规则增长前缀信息。

如进行了下面的设置以后,访问 hello-service 的 hello 接口须要使用 http://localhost:5555/api/hello-service/hello。

spring.application.name=api-gateway
server.port=5555

zuul.prefix=/api
zuul.routes.hello-service=/hello-service/**

zuul 中默认会移除匹配到的 url 中的 serviceId 部分,如 对于下面的配置,当网关遇到 /hello-service/hello 的请求时,会去找请求 hello-service 服务的 hello 接口(没有服务名最为前缀)。

zuul.routes.hello-service=/hello-service/**

若是服务配置了 context-path,则由于 zuul 会移除 url 中的 serviceId,就会致使 404。这个时候,就须要使用 zuul.routes.<route>.strip-prefix 来设置路由时不去掉 serviceId。

# 默认状况下,在转发请求以前,从请求中删除serviceId(使用zuul.stripPrefix = false关闭此行为)
#zuul.stripPrefix=false
# 路由时不去掉 serviceId(若是调用的服务配置了 context-path,则不能去掉)
zuul.routes.hello-service.strip-prefix=false

zuul.stripPrefix 彷佛有点问题,没法实现 zuul.routes.<route>.strip-prefix 的效果。

本地跳转

在 zuul 实现的 API 网关路由功能中,还支持 forward 形式的服务端跳转配置。实现方式很是简单,只需经过使用 path 与 url 的配置方式就能完成。经过 url 中使用 forward 来指定须要跳转的服务器资源路径。

以下面的配置实现了将符合 /local/** 规则的请求在 API 网关上实现本地跳转。

# 本地跳转
zuul.routes.local.path=/local/**
zuul.routes.local.url=forward:/local

好比,当 API 网关接到请求 /local/hello ,会转发到网关的 /local/hello 请求上进行本地处理(须要在 API 网关上增长一个 /local/hello 接口)。

Hystrix 和 Ribbon 支持

因为 spring-cloud-starter-netflix-zuul 依赖,包含了 hystrix、ribbon 模块的依赖,因此 Zuul 天生就拥有线程隔离和客户端负载均衡功能。可是须要注意,当使用 path 与 url 的映射关系来配置路由规则的时候,对于路由转发的请求不会采用 HystrixCommand 来包装,因此这里路由请求没有线程隔离和断路器的保护,而且也不会有负载均衡的能力。所以,咱们在使用 Zuul 的时候尽可能使用 path 和 serviceId 的组合来进行配置,这样不只能够包装 API 网关的健壮和稳定,也能用到 Ribbon 的客户端负载均衡功能。

另外,关于 zuul 中各类超时时间等配置,能够参考: SpringCloud的各类超时时间配置效果

本文参考:使用Zuul构建API Gateway