• 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

  • 相关阅读:
    解析XML
    事务
    js小工具
    plsql用过的流程语句
    查询语句
    存储过程
    用过的CRT命令
    mysql常用命令
    Spirng MVC demo 完整示例01 环境搭建
    jmeter多个http请求串联
  • 原文地址:https://www.cnblogs.com/wood-life/p/10341400.html
Copyright © 2020-2023  润新知