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

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. Beta冲刺阶段3.0

    1. 提供当天站立式会议照片一张 2. 每个人的工作 (有work item 的ID) 成员 昨天已完成的工作 今天计划完成的工作 工作中遇到的困难 具体贡献 郑晓丽 完成"我的活动&quo ...

  2. poj2987 求最大权闭合回路

    建图差不多和以前做的差不多,就是最后询问这个闭合子图有多少个的时候,只要输出这个图的S集合,就是进行dfs能遍历到的点一定在S集合中,不能遍历到的点在T集合中 #include <iostrea ...

  3. laravel中使用的PDF扩展包——laravel-dompdf和laravel-snappy

    这两天项目中需要将HTML页面转换为PDF文件方便打印,我在网上搜了很多资料.先后尝试了laravel-dompdf和laravel-snappy两种扩展包,个人感觉laravel-snappy比较好 ...

  4. 孤立森林(Isolation Forest)

    前言随着机器学习近年来的流行,尤其是深度学习的火热.机器学习算法在很多领域的应用越来越普遍.最近,我在一家广告公司做广告点击反作弊算法研究工作.想到了异常检测算法,并且上网调研发现有一个算法非常火爆, ...

  5. idea下导入Tomcat源码

    对于web开发者来说,如果明白了tomcat那对于开发还是后面的学习都是有很大益处的,但在网上看了很多的文章,总是没弄好,经历了很久才弄好了,写个文章记录下,希望也能帮助到其他人.下载Tomcat源码 ...

  6. python使用SAX解析xml

    python 标准库包含SAX解析器,SAX用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件 在python中使用sax方式处理xml要先引入xml.s ...

  7. SQL SERVER镜像配置(包含见证服务器)

    镜像简介   重要说明:保持数据库镜像运行.如果您关闭数据库镜像,则必须执行完全备份并还原数据库以重建数据库镜像.   一. 简介 SQL SERVER 2005镜像基于日志同步,可良好实现故障转移. ...

  8. MaxiSYS Elite

    The Maxisys Elite is Autel UK’s top of the range diagnostic and analysis scanner with advanced J2534 ...

  9. jquery easyui datagrid 空白条处理 自适应宽高 格式化函数formmater 初始化时会报错 cannot read property 'width'||'length' of null|undefined

    1---表格定义好之后右侧可能会有一个空白条 这个空白条是留给滚动条的,当表格中的一页的数据在页面中不能全显示时会自动出现滚动条,网上有很多事要改源码才可以修改这个,但是当项目中多处用到时,有的需要滚 ...

  10. socket聊天的业务逻辑

        一.主要思想:     1.如果用户A想要发消息给用户B,A需要将消息发送到一个服务器上,服务器接收到A发送的消息之后,再把消息发送给B,B接收到消息     2.当用户B断开连接时服务器不会 ...