【第五章】API服务网关(Zuul) 上

微服务场景下,每一个微服务对外暴露了一组细粒度的服务。客户端的请求可能会涉及到一串的服务调用,如果将这些微服务都暴露给客户端,那么客户端需要多次请求不同的微服务才能完成一次业务处理,增加客户端的代码复杂度。另外,对于微服务我们可能还需要服务调用进行统一的认证和校验等等。微服务架构虽然可以将我们的开发单元拆分的更细,降低了开发难度,但是如果不能够有效的处理上面提到的问题,可能会造成微服务架构实施的失败。

Zuul参考GOF设计模式中的Facade模式,将细粒度的服务组合起来提供一个粗粒度的服务,所有请求都导入一个统一的入口,那么整个服务只需要暴露一个api,对外屏蔽了服务端的实现细节,也减少了客户端与服务器的网络调用次数。这就是API服务网关(API Gateway)服务。我们可以把API服务网关理解为介于客户端和服务器端的中间层,所有的外部请求都会先经过API服务网关。因此,API服务网关几乎成为实施微服务架构时必须选择的一环。

Spring Cloud Netflix的Zuul组件可以做反向代理的功能,通过路由寻址将请求转发到后端的粗粒度服务上,并做一些通用的逻辑处理。

通过Zuul我们可以完成以下功能:

  • 动态路由
  • 监控与审查
  • 身份认证与安全
  • 压力测试: 逐渐增加某一个服务集群的流量,以了解服务性能;
  • 金丝雀测试
  • 服务迁移
  • 负载剪裁: 为每一个负载类型分配对应的容量,对超过限定值的请求弃用;
  • 静态应答处理

1. 构建网关

1.1 构建Zuul-Server

编写pom.xml文件

Zuul-Server是一个标准的Spring Boot应用,所以还是继承自我们之前的parent:

<?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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>twostepsfromjava.cloud</groupId>
        <artifactId>twostepsfromjava-cloud-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../parent</relativePath>
    </parent>

    <artifactId>zuul-server</artifactId>
    <name>Spring Cloud Sample Projects: Zuul Proxy Server</name>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

这里我们增加了spring-cloud-starter-zuul的依赖。

编写启动类

/**
 * TwoStepsFromJava Cloud -- Zuul Proxy 服务器
 *
 * @author CD826([email protected])
 * @since 1.0.0
 */
@EnableZuulProxy
@SpringBootApplication
public class Application {

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

}

这里我们增加了对主应用类增加了@EnableZuulProxy,用以启动Zuul的路由服务。

编写配置文件application.properties

server.port=8280

spring.application.name=ZUUL-PROXY

eureka.client.service-url.defaultZone=http://localhost:8260/eureka

这里定义服务名称为: ZUUL-PROXY,端口设为: 8280

1.2 构建User-Service

为了后面的则是我们再增加一个微服务: 用户服务。

编写pom.xml文件

同样继承自我们之前的parent:

<?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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>twostepsfromjava.cloud</groupId>
        <artifactId>twostepsfromjava-cloud-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../parent</relativePath>
    </parent>

    <artifactId>user-service</artifactId>
    <name>Spring Cloud Sample Projects: User Service Server</name>

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

        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>service-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

编写启动类

启动类和之前的Product-Service一样,所以这里不再列出来。

编写服务接口

示例的服务接口非常简单,就是根据给定的登录名称查询一个用户信息。如下:

/**
 * User API服务
 *
 * @author CD826([email protected])
 * @since 1.0.0
 */
@RestController
@RequestMapping("/users")
public class UserEndpoint {
    protected Logger logger = LoggerFactory.getLogger(UserEndpoint.class);

    @Value("${server.port:2200}")
    private int serverPort = 2200;

    @RequestMapping(value = "/{loginName}", method = RequestMethod.GET)
    public User detail(@PathVariable String loginName) {
        String memos = "I come form " + this.serverPort;
        return new User(loginName, loginName, "/avatar/default.png", memos);
    }
}

其中User类定义在之前的service-api项目中,代码如下:

/**
 * 用户信息DTO对象
 *
 * @author CD826([email protected])
 * @since 1.0.0
 */
public class User {
    private static final long serialVersionUID = 1L;

    // ========================================================================
    // fields =================================================================
    private String loginName;                                   // 用户登陆名称
    private String name;                                        // 用户姓名
    private String avatar;                                      // 用户头像
    private String memos;                                       // 信息备注

    // ========================================================================
    // constructor ============================================================
    public User() {
    }

    public User(String loginName, String name, String avatar, String memos) {
        this.loginName = loginName;
        this.name = name;
        this.avatar = avatar;
        this.memos = memos;
    }

    // ==================================================================
    // setter/getter ====================================================
    // ... 省略,请自行补充 ...
}

编写配置文件application.properties

server.port=2200

spring.application.name=USER-SERVICE

eureka.client.service-url.defaultZone=http://localhost:8260/eureka

这里定义服务名称为: USER-SERVICE,默认端口设为: 2200

代码修改,就是这么多,下面让我们启动进行测试。

1.3 启动测试

启动各服务

请按照下面的顺序启动各服务器:

  1. Service-discovery
  2. Product-Service
  3. User-Service(2200)
  4. User-Service(2300): java -jar user-service-1.0.0-SNAPSHOT.jar --server.port=2300
  5. Zuul-Server

Ok, 服务启动后我们可以在Eureka服务器看到如下界面:

这里我们启动两个User-Service主要是为了后面进行负载均衡测试使用。

测试路由服务

首先,我们在浏览器中输入以下地址: http://localhost:8280/product-service/products,将会显示以下界面:

然后,我们在浏览器中输入以下地址: http://localhost:8280/user-service/users/admin,将会显示以下界面:

可见,Zuul-Server已经帮我们路由到相应的微服务。

负载均衡测试

接下来我们测试一下负载均衡是否可以正常工作。前面我们已经启动了两个User-Service微服务,端口分别为:2200和2300。我们多次在浏览器中输入以下地址: http://localhost:8280/user-service/users/admin进行请求,我们将会看到以下信息会在屏幕中交替输出:

{"loginName":"admin","name":"admin","avatar":"/avatar/default.png","memos":"I come form 2200"}
{"loginName":"admin","name":"admin","avatar":"/avatar/default.png","memos":"I come form 2300"}

可见,负载均衡也是正常工作的。

Hystrix容错与监控测试

之前我们是在Mall-Web项目中集成Hystrix的监控,那么我们启动该服务。然后在Hystrix Dashboard中输入: http://localhost:8280/hystrix.stream,然后进行监控,那么我们将看到如下界面:

这说明,Zuul已经整合了Hystrix。

spring-cloud-starter-zuul本身已经集成了hystrix和ribbon,所以Zuul天生就拥有线程隔离和断路器的自我保护能力,以及对服务调用的客户端负载均衡功能。但是,我们需要注意,当使用path与url的映射关系来配置路由规则时,对于路由转发的请求则不会采用HystrixCommand来包装,所以这类路由请求就没有线程隔离和断路器保护功能,并且也不会有负载均衡的能力。因此,我们在使用Zuul的时候尽量使用path和serviceId的组合进行配置,这样不仅可以保证API网关的健壮和稳定,也能用到Ribbon的客户端负载均衡功能。

2. Zuul配置

2.1 路由配置详解

或许你会觉得神奇,之前我们什么也没有配置,通过http://localhost:8280/product-service/productshttp://localhost:8280/user-service/users/admin已经可以正确的访问到我们的微服务了,这就是Zuul的默认路由映射功能在起作用,那么接下来具体来看看Zuul是怎么进行路由配置的。

1) 服务路由默认规则

当我们构建API服务网关时引入Eureka时,那么Zuul会自动为每个服务都创建一个默认路由规则: 访问路径的前缀为serviceId配置的服务名称,也就是之前为什么我们能够所使用:

http://localhost:8280/product-service/products

来访问Product-Service中所提供的products服务端点的原因。

2) 自定义微服务访问路径

配置格式为: zuul.routes.微服务Id = 指定路径,如:

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

这样,我们后面就可以通过/user/来访问user-service所提供的服务,比如之前的访问可以更改为: http://localhost:8280/user/users/admin

所要配置的路径可以指定一个正则表达式来匹配路径,因此,/user/*只能匹配一级路径,但是通过/user/**可以匹配所有以/user/开头的路径。

3) 忽略指定微服务

配置格式为: zuul.ignored-services=微服务Id1,微服务Id2...,多个微服务之间使用逗号分隔。如:

zuul.ignored-services=user-service,product-service

4) 同时指定微服务Id和对应路径

zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A

zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B

5) 同时指定微服务Url和对应路径

zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.url=http://localhost:8080/api-a

如之前所述,通过url配置的路由不会由HystrixCommand来执行,自然,也就得不到Ribbon的负载均衡、降级、断路器等功能。所以在实施尽量使用serviceId进行配置,也可以采用下面的配置方式。

6) 指定多个服务实例及负载均衡

如果需要配置多个服务实例,则配置如下:

zuul.routes.user.path: /user/**
zuul.routes.user.serviceId: user

ribbon.eureka.enabled=false
user.ribbon.listOfServers: http://192.168.1.10:8081, http://192.168.1.11:8081

7) forward跳转到本地url

zuul.routes.user.path=/user/**
zuul.routes.user.url=forward:/user

8) 路由前缀

可以通过zuul.prefix可为所有的映射增加统一的前缀。如: /api。默认情况下,代理会在转发前自动剥离这个前缀。如果需要转发时带上前缀,可以配置: zuul.stripPrefix=false来关闭这个默认行为。例如:

zuul.routes.users.path=/myusers/**
zuul.routes.users.stripPrefix=false

注意: zuul.stripPrefix只会对zuul.prefix的前缀起作用。对于path指定的前缀不会起作用。

9) 路由配置顺序

如果想按照配置的顺序进行路由规则控制,则需要使用YAML,如果是使用propeties文件,则会丢失顺序。例如:

zuul:
  routes:
    users:
      path: /myusers/**
    legacy:
      path: /**

上例如果是使用properties文件进行配置,则legacy就可能会先生效,这样users就没效果了。

10) 自定义转换

我们也可以一个转换器,让serviceId和路由之间使用正则表达式来自动匹配。例如:

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>v.+$)",
        "${version}/${name}");
}

这样,serviceId为“users-v1”的服务,就会被映射到路由为“/v1/users/”的路径上。任何正则表达式都可以,但是所有的命名组必须包括servicePattern和routePattern两部分。如果servicePattern没有匹配一个serviceId,那就会使用默认的。在上例中,一个serviceId为“users”的服务,将会被映射到路由“/users/”中(不带版本信息)。这个特性默认是关闭的,而且只适用于已经发现的服务。

2.2 Zuul的Header设置

敏感Header设置

同一个系统中各个服务之间通过Headers来共享信息是没啥问题的,但是如果不想Headers中的一些敏感信息随着HTTP转发泄露出去话,需要在路由配置中指定一个忽略Header的清单。

默认情况下,Zuul在请求路由时,会过滤HTTP请求头信息中的一些敏感信息,默认的敏感头信息通过zuul.sensitiveHeaders定义,包括CookieSet-CookieAuthorization。配置的sensitiveHeaders可以用逗号分割。

对指定路由的可以用下面进行配置:

# 对指定路由开启自定义敏感头
zuul.routes.[route].customSensitiveHeaders=true
zuul.routes.[route].sensitiveHeaders=[这里设置要过滤的敏感头]

设置全局:

zuul.sensitiveHeaders=[这里设置要过滤的敏感头]

忽略Header设置

如果每一个路由都需要配置一些额外的敏感Header时,那你可以通过zuul.ignoredHeaders来统一设置需要忽略的Header。如:

zuul.ignoredHeaders=[这里设置要忽略的Header]

在默认情况下是没有这个配置的,如果项目中引入了Spring Security,那么Spring Security会自动加上这个配置,默认值为: Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expries

此时,如果还需要使用下游微服务的Spring Security的Header时,可以增加下面的设置:

zuul.ignoreSecurityHeaders=false

2.3 Zuul Http Client

Zuul的Http客户端支持Apache Http、Ribbon的RestClient和OkHttpClient,默认使用Apache HTTP客户端。可以通过下面的方式启用相应的客户端:

# 启用Ribbon的RestClient
ribbon.restclient.enabled=true

# 启用OkHttpClient
ribbon.okhttp.enabled=true

如果需要使用OkHttpClient需要注意在你的项目中已经包含com.squareup.okhttp3相关包。

3. Zuul容错与回退

我们再来仔细看一下之前Hystrix的监控界面:

请注意,Zuul的Hystrix监控的粒度是微服务,而不是某个API,也就是所有经过Zuul的请求都会被Hystrix保护起来。假如,我们现在把Product-Service服务关闭,再来访问会出现什么结果呢?结果可能不是我们所想那样,如下:

呃,比较郁闷是么!那么如何为Zuul实现容错与回退呢?

Zuul提供了一个ZuulFallbackProvider接口,通过实现该接口就可以为Zuul实现回退功能。那么让我们改造之前的Zuul-Server

3.1 实现回退方法

代码如下:

/**
 * Product Service服务失败回退处理
 *
 * @author CD826([email protected])
 * @since 1.0.0
 */
@Component
public class ProductServiceFallbackProvider implements ZuulFallbackProvider {
    protected Logger logger = LoggerFactory.getLogger(ProductServiceFallbackProvider.class);

    @Override
    public String getRoute() {
        // 注意: 这里是route的名称,不是服务的名称,
        // 如果这里写成大写PRODUCT-SERVICE将无法起到回退作用
        return "product-service";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("商品服务暂不可用,请稍后重试!".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
                return headers;
            }
        };
    }
}

需要说明的是:

  • getRoute方法返回了我们要为那个微服务提供回退。这里需要注意的返回的值是route的名称,不是服务的名称,不能够写为: PRODUCT-SERVICE,否则该回退将不起作用;
  • fallbackResponse方法返回ClientHttpResponse对象,作为我们的回退响应。这里实现非常简单仅仅是返回:商品服务暂不可用,请稍后重试! 的提示。

3.2 重启测试

重启Zuul-Server,再重复上面的实验,将会看到以下界面:

说明,回退方法已经起作用了。如果你的没有起作用,那么仔细检查一下getRoute的返回是否正确。

原文地址:http://www.jianshu.com/p/be5b26a9fa42

原文地址:https://www.cnblogs.com/sunny3096/p/8175647.html

时间: 2024-10-16 04:40:16

【第五章】API服务网关(Zuul) 上的相关文章

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

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

Spring Boot + Spring Cloud 构建微服务系统(七):API服务网关(Zuul)

技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢. 当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问题的,但出于种种原因,这并不是一个好的选择. 让客户端直接与各个微服务通讯,会有以下几个问题: 客户端会多次请求不同的微服务,增加了客户端的复杂性. 存在跨域请求,在一定场景下处理会变得相对比较复杂. 实现认证复杂,每个微服务都需要独立认证. 难以重构,项目迭代可能导致微服务重新划分.如果客户端直接

API服务网关(Zuul)

技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢. 当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问题的,但出于种种原因,这并不是一个好的选择. 让客户端直接与各个微服务通讯,会有以下几个问题: 客户端会多次请求不同的微服务,增加了客户端的复杂性. 存在跨域请求,在一定场景下处理会变得相对比较复杂. 实现认证复杂,每个微服务都需要独立认证. 难以重构,项目迭代可能导致微服务重新划分.如果客户端直接

Spring Cloud(十一):服务网关 Zuul(过滤器)【Finchley 版】

Spring Cloud(十一):服务网关 Zuul(过滤器)[Finchley 版] 发表于 2018-04-23 |  更新于 2018-05-07 | 在上篇文章中我们了解了 Spring Cloud Zuul 作为网关所具备的最基本功能:路由(Router).本文我们将关注 Spring Cloud Zuul 的另一核心功能:过滤器(Filter). Filter 的作用 我们已经能够实现请求的路由功能,所以我们的微服务应用提供的接口就可以通过统一的 API 网关入口被客户端访问到了.但

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

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

服务网关zuul之七:zuul中的动态刷新路由配置

Spring cloud使用/refresh端点手动刷新配置 一 介绍很多场景下,需要在运行期间动态调整配置.如果配置发生了修改,微服务也应该实现配置的刷新.下面实现配置的手动刷新.二 新建项目microservice-config-client-refresh三 为项目添加spring-boot-starter-actuator依赖,该依赖包含了/refresh端点,用于配置的刷新. <dependencies> <dependency> <groupId>org.s

史上最简单的SpringCloud教程 | 第五篇: 路由网关(zuul)(Finchley版本)

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

史上最简单的SpringCloud教程 | 第五篇: 路由网关(zuul)

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

SpringCloud (Finchley版本)教程(五):路由网关(zuul)

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