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

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(CD826Dong@gmail.com)
 * @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(CD826Dong@gmail.com)
 * @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(CD826Dong@gamil.com)
 * @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(CD826Dong@gmail.com)
 * @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

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

  1. 一起来学Spring Cloud | 第六章:服务网关 ( Zuul)

    本章节,我们讲解springcloud重要组件:微服务网关Zuul.如果有同学从第一章看到本章的,会发现我们已经讲解了大部分微服务常用的基本组件. 已经讲解过的: 一起来学Spring Cloud | ...

  2. 【Dalston】【第六章】API服务网关(Zuul) 下

    Zuul给我们的第一印象通常是这样:它包含了对请求的路由和过滤两个功能,其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础.过滤器功能则负责对请求的处理过程进行干预,是实 ...

  3. Spring Cloud系列(五):服务网关Zuul

    在前面的篇章都是一个服务消费者去调用一个服务提供者,但事实上我们的系统基本不会那么简单,如果真的是那么简单的业务架构我们也没必要用Spring Cloud,直接部署一个Spring Boot应用就够了 ...

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

    前言 通过之前的两篇文章,可以简单的搭建一个路由网关了.而我们知道,现在都奉行前后端分离开发,前后端开发的沟通成本就增加了,所以一般上我们都是通过swagger进行api文档生成的.现在由于使用了统一 ...

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

    技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢. 当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问 ...

  6. API服务网关(Zuul)

    技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢. 当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问 ...

  7. Spring Cloud(十):服务网关 Zuul(路由)【Finchley 版】

    Spring Cloud(十):服务网关 Zuul(路由)[Finchley 版]  发表于 2018-04-23 |  更新于 2018-05-09 |  通过之前几篇 Spring Cloud 中 ...

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

    Spring Cloud(十一):服务网关 Zuul(过滤器)[Finchley 版]  发表于 2018-04-23 |  更新于 2018-05-07 |  在上篇文章中我们了解了 Spring ...

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

    SpringCloud系列教程 | 第九篇:服务网关Zuul初探 前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散 ...

随机推荐

  1. Spark学习之路 (二十七)图简介

    一.图 1.1 基本概念 图是由顶点集合(vertex)及顶点间的关系集合(边edge)组成的一种数据结构. 这里的图并非指代数中的图.图可以对事物以及事物之间的关系建模,图可以用来表示自然发生的连接 ...

  2. golang学习笔记9 beego nginx 部署 nginx 反向代理 golang web

    golang学习笔记9 beego nginx 部署 nginx 反向代理 golang web Nginx 部署 - beego: 简约 & 强大并存的 Go 应用框架https://bee ...

  3. read 命令详解

    read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量 语法选项 -p read –p “提示语句”,则屏幕就会输出提示语句,等待输入,并将输入存储在REPLY中 -n ...

  4. WordConuts

    import java.io.File; import java.io.FileNotFoundException; import java.util.HashMap; import java.uti ...

  5. java之定时任务

    package com.financial.server.util; import java.text.SimpleDateFormat; import java.util.Date; import ...

  6. GoldenGate for bigdata 12.3.1.1

    GoldenGate for big data 12.3.1.1在8.18已经发布,主要新特性如下: 1. 新目标:Amazon Kinesis 2. 新目标:使用Kafka Connect API及 ...

  7. ubuntu_查看software

    感谢原博主的分享 ubuntu安装和查看已安装 说明:由于图形化界面方法(如Add/Remove... 和Synaptic Package Manageer)比较简单,所以这里主要总结在终端通过命令行 ...

  8. SSM思路大总结(部门信息的显示和增删改查)

    #ssm整合(部门管理) ##1.新建工程 1.新建maven工程 2.添加web.xml 3.添加tomcat运行环境 4.添加依赖jar包 spring-webmvc mysql commonse ...

  9. Torch或Numpy

    1.什么是NumpyNumpy系统是Python的一种开源的数值计算扩展,用python实现的科学计算包.这种工具可用来存储和处理大型矩阵,包括强大的N维数组对象Array,比较成熟的函数库等.num ...

  10. ora-24550 signo=6 signo=11解决

    我们有台测试服务器pro*c/oci应用总是发生各种比较奇葩的现象,就这一台机器会发生,其他几十台都不会发生. sig 11的原因,内存地址访问越界.各signo的si_code含义可参考http:/ ...