API网关Zuul(六)

更多内容参见个人技术博客,无广告欢迎关注

1.1         API网关

1.1.1      概念

Amazon Andriod 应用的商品详情页显示内容

购物车中的商品数量

历史订单

客户评论

低库存预警

送货选项

各种推荐,包括经常与该商品一起购买的其它商品、购买该商品的客户购买的其它商品、购买该商品的客户看过的其它商品

其它的购物选择

使用单体应用程序架构时,移动客户端通过向应用程序发起一次REST请求,来获取这些数据,负载均衡器将请求路由给N个相同的应用程序实例中的其中之一,然后应用程序会查询各种数据库表,并将响应返回给客户端。

相反,若是采用微服务架构,显示在产品页上的数据会分布在不同的微服务上,下面列举了可能与产品详情页数据有关的一些微服务:

购物车服务~购物车中的件数

订单服务~历史订单

目录服务~商品基本信息、如名称、图片和价格

评论服务~客户的评论

库存服务~低库存预警

送货服务~送货选项、期限和费用,这些信息单独从送货方API获取

推荐服务~推荐商品

1.1.2      客户端与微服务直接通信的问题

1.    客户端需求和每个微服务暴露的细粒度 API 不匹配。在这个例子中,客户端需要发送 7 个独立请求。在更复杂的应用程序中,可能要发送更多的请求;按照 Amazon 的说法,他们在显示他们的产品页面时就调用了数百个服务。然而,客户端通过 LAN 发送许多请求,这在公网上可能会很低效,在移动网络上就根本不可行。这种方法还使得客户端代码非常复杂。

2.    客户端直接调用微服务的另一个问题是,部分服务使用的协议对 web 并不友好。一个服务可能使用 Thrift 二进制 RPC,而另一个服务可能使用 AMQP 消息传递协议。不管哪种协议对于浏览器或防火墙都不够友好,最好是内部使用。在防火墙之外,应用程序应该使用诸如 HTTP WebSocket 之类的协议。

3.    另一个缺点是,它会使得微服务难以重构。随着时间推移,我们可能想要更改系统拆分服务的方式。例如,我们可能合并两个服务,或者将一个服务拆分成两个或更多服务。然而,如果客户端与微服务直接通信,那么执行这类重构就非常困难了。

1.1.3      使用 API 网关构建微服务

通常来说,使用 API 网关是更好的解决方式。API 网关是一个服务器,也可以说是进入系统的唯一节点。API 网关封装内部系统的架构,并且提供 API 给各个客户端。它还可能还具备授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等功能。下图展示了一个适应当前架构的 API 网关。

 

API 网关负责服务请求路由、组合及协议转换。客户端的所有请求都首先经过 API 网关,然后由它将请求路由到合适的微服务。API 网关经常会通过调用多个微服务并合并结果来处理一个请求。它可以在 web 协议(如 HTTP WebSocket)与内部使用的非 web 友好协议之间转换。

API 网关还能为每个客户端提供一个定制的 API。通常,它会向移动客户端暴露一个粗粒度的 API以产品详情的场景为例,API 网关可以提供一个端点(/productdetails?productid=xxx),使移动客户端可以通过一个请求获取所有的产品详情。API 网关通过调用各个服务(产品信息、推荐、评论等等)并合并结果来处理请求。

Netflix API 网关是一个很好的 API 网关实例。Netflix 流媒体服务提供给成百上千种类型的设备使用,包括电视、机顶盒、智能手机、游戏系统、平板电脑等等。

最初,Netflix 试图为他们的流媒体服务提供一个通用的 API。然而他们发现,由于各种各样的设备都有自己独特的需求,这种方式并不能很好地工作。如今,他们使用一个 API 网关,通过运行与针对特定设备的适配器代码,来为每种设备提供定制的 API通常,一个适配器通过调用平均 6 7 个后端服务来处理每个请求。Netflix API 网关每天处理数十亿请求。

1.1.4      API 网关的优点和缺点

如你所料,使用 API 网关有优点也有不足。使用 API 网关的最大优点是,它封装了应用程序的内部结构。客户端只需要同网关交互,而不必调用特定的服务。API 网关为每一类客户端提供了特定的 API这减少了客户端与应用程序间的交互次数,还简化了客户端代码。

API 网关也有一些不足。它增加了一个我们必须开发、部署和维护的高可用组件。还有一个风险是,API 网关变成了开发瓶颈。为了暴露每个微服务的端点,开发人员必须更新 API 网关。API网关的更新过程要尽可能地简单,这很重要;否则,为了更新网关,开发人员将不得不排队等待。不过,虽然有这些不足,但对于大多数现实世界的应用程序而言,使用 API 网关是合理的。

1.1.5      Zuul

 

Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行。

Zuul可以通过加载动态过滤机制,从而实现以下各项功能:

验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。

审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。

动态路由: 以动态方式根据需要将请求路由至不同后端集群处。

压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。

负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。

静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。

多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。

除此之外,Netflix公司还利用Zuul的功能通过金丝雀版本实现精确路由与压力测试。

1.1.6      拓展:金丝雀测试(灰度测试)

 

现今我们的服务集群数量都非常大,系统操作的人非常多,此时更新代码也不能停机来更新,必须逐步更新。例如:金丝雀版本发布方式。

金丝雀发布:一般先发 1 台,或者一个小比例,例如 2% 的服务器,主要做流量验证用,也称为金丝雀 (Canary) 测试(国内常称灰度测试)。以前旷工开矿下矿洞前,先会放一只金丝雀进去探是否有有毒气体,看金丝雀能否活下来,金丝雀发布由此得名。简单的金丝雀测试一般通过手工测试验证,复杂的金丝雀测试需要比较完善的监控基础设施配合,通过监控指标反馈,观察金丝雀的健康状况,作为后续发布或回退的依据。

1.1.7      烂接口的特征

没有接口文档

出入参数风格不一致

异常提示不友好

模型接口混乱,粗暴升级

稳定性差,还找不到人

1.1.8      Zuul生命周期

 

1.2         Zuul实现网关

1.2.1      创建Maven项目

 

1.2.2      pom.xml

父工程pom.xml 

<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wood</groupId>
<artifactId>spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud</name>
<description>Demo project for Spring Boot</description>
<!--Maven项目可以继承,三个子工程-->
<modules>
<module>eureka-server</module>
<module>provider-a</module>
<module>provider-b</module>
<module>consumer-client</module>
<module>consumer-client-ribbon</module>
<module>feign-client</module>
<module>consumer-hystrix</module>
<module>geteway-zuul</module>
</modules>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>

<dependencies>
<!--公共依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>

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

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>

</project> 

注意zuul也要注册到注册中心Eureka 

 

<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.wood</groupId>
<artifactId>spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wood</groupId>
<artifactId>geteway-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>geteway-zuul</name>
<description>Demo project for Spring Boot</description>

<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-zuul</artifactId>
<version>LATEST</version>
</dependency>

</dependencies>

</project>

 

1.2.3      application.properties

zuul的配置:app-a路由名称随意,path映射路径,访问路径就无需敲入serviceId那么长,可以利用path映射路径简写。

旧的方式:http://localhost:8095/provider-user/hello/wood

新的方式:http://localhost:8095/user/hello/wood

server.port=8095
spring.application.name=getway-zuul
eureka.client.serviceUrl.defaultZone=http://localhost:6001/eureka
# 定义根目录下日志级别
logging.level.root=DEBUG

# zuul配置:app-a路由名称随意,path映射路径,访问路径就无需敲入serviceId那么长,可以利用path映射路径简写
# 默认会把所有注册在eureka上的微服务都反向代理
##############################################################################
zuul.routes.app-a.path=/user/**
zuul.routes.app-a.service-id=provider-user
##############################################################################




# 这样配置后,只有provider-user的被映射到user,其它的服务访问形式不变,如果有多个之间用逗号隔开
##############################################################################
#zuul.routes.app-a.path=/user/**
#zuul.routes.app-a.service-id=provider-user
zuul.ignored-services="*"
##############################################################################
 

ignoredServices: '*'

这样配置后,只有provider-user的被映射到user,其它的服务访问形式不变,如果有多个之间用逗号隔开

直接访问:               http://localhost:8095/hello/wood

未映射访问(无法访问): http://localhost:8095/provider-user/hello/wood

ZUUL访问:               http://localhost:8095/user/hello/wood

1.2.4      GetewayZuulApplication.java

package com.wood.getewayzuul;

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

@SpringBootApplication
@EnableEurekaClient // zuul服务要注册到Eureka上
@EnableZuulProxy // 开启Zuul
public class GetewayZuulApplication {

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

}

 

1.2.5      测试

启动EurekaServerApplication

启动ProviderAApplication

启动GetewayZuulApplication

  

 

直接访问:       http://localhost:8081/hello/wood

未映射访问: http://localhost:8095/provider-user/hello/wood

ZUUL访问:      http://localhost:8095/user/hello/wood 

1.2.6      日志

 

1.3         拓展:Zuul中实现断路器

1.3.1      HelloFallback.java

package cn.wood.fallback;

 

import java.io.ByteArrayInputStream;

import java.io.IOException;

import java.io.InputStream;

 

import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;

import org.springframework.http.HttpHeaders;

import org.springframework.http.HttpStatus;

import org.springframework.http.MediaType;

import org.springframework.http.client.ClientHttpResponse;

import org.springframework.stereotype.Component;

 

@Component   //Zuul实现熔断机制

public class HelloFallback implements ZuulFallbackProvider{

 

    @Override

    public String getRoute() {

        return "provider-user";

    }

 

    @Override

    public ClientHttpResponse fallbackResponse() {

        return new ClientHttpResponse() {

            

             @Override

            public HttpHeaders getHeaders() {

                 HttpHeaders headers = new HttpHeaders();

                 headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

                 return headers;

             }

            

             @Override

             public InputStream getBody() throws IOException {

                 return new ByteArrayInputStream(("fallback "+HelloFallback.this.getRoute()).getBytes());

             }

            

             @Override

             public String getStatusText() throws IOException {

                 return HttpStatus.BAD_REQUEST.getReasonPhrase();

             }

            

             @Override

             public HttpStatus getStatusCode() throws IOException {

                 return HttpStatus.BAD_REQUEST;

             }

            

             @Override

             public int getRawStatusCode() throws IOException {

                 return HttpStatus.BAD_REQUEST.value();

             }

            

             @Override

             public void close() {

                

             }

        };

    }

 

}

 

执行业务正常,停掉provider-user服务,断路器生效 

但这样实现的断路器刷新时间比较长,一旦服务正常,刷新正常。

 

 

 demo下载 spring-cloud-zuul.zip

 

posted @ 2019-01-31 12:02  一只特立独行的程序猿  阅读(682)  评论(0编辑  收藏  举报