0
点赞
收藏
分享

微信扫一扫

五,微服务网关(待改)

拾杨梅记 2022-02-26 阅读 62

写在前面的话:本文大部分(说全部也没问题)都是抄写的,课堂的资料给的特别好。感觉耻于提笔,但不写上就影响整个博客内容了。以后会改/写出我的笔记的。
感谢你的路过,希望学生的笔记能给你一点微不足道的参考(2/100)
Java基础思维导图,完整Java体系的链接
微服务部分内容链接。感谢开课吧。
大概十几个文章吧

5.1 微服务网关简介

第三章我们介绍了通过Spring Cloud LoadBalancer实现了微服务之间的调用和负载均衡,以及使用Spring Cloud OpenFeign声明式调用,那我们的各种微服务又要如何提供给外部应用调用呢?
   当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问题的。但出于种种原因,这并不是一个好的选择。让客户端直接与各个微服务通讯,会存在以下几个问题。
      ● 客户端会多次请求不同的微服务,增加了客户端的复杂性。
      ● 存在跨域请求,在一定场景下处理会变得相对比较复杂。
      ● 实现认证复杂,每个微服务都需要独立认证。
      ● 难以重构,项目迭代可能导致微服务重新划分。如果客户端直接与微服务通讯,那么重构将会很难实施。
      ● 如果某些微服务使用了防火墙、浏览器不友好的协议,直接访问会有一定困难。
面对类似上面的问题,我们要如何解决呢?答案就是:服务网关!在微服务系统中微服务资源一般不直接暴露给我外部客户端访问,这样做的好处是将内部服务隐藏起来,从而解决上述问题。
   网关有很多重要的意义,具体体现在下面几个方面。
      ● 网关可以做一些身份认证、权限管理、防止非法请求操作服务等,对服务起一定保护作用。
      ● 网关将所有微服务统一管理,对外统一暴露,外界系统不需要知道微服务架构个服务相互调用的复杂性,同时也避免了内部服务一些敏感信息泄露问题。
      ● 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
&   nbsp;  ● 客户端只跟服务网关打交道,减少了客户端与各个微服务之间的交互次数。
      ● 多渠道支持,可以根据不同客户端(WEB端、移动端、桌面端…)提供不同的API服务网关。
      ● 网关可以用来做流量监控。在高并发下,对服务限流、降级。
      ● 网关把服务从内部分离出来,方便测试。
微服务网关能够实现,路由、负载均衡等多种功能。类似Nginx,反向代理的功能。在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的URL,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,在API网关中进行权限控制,同时API网关将请求以负载均衡的方式发送给后端服务。微服务网关架构,如图5-1所示。
在这里插入图片描述

5.2 Spring Cloud Gateway简介

5.2.1 简介

SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
   SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
   注意:Spring Cloud Gateway 底层使用了高性能的通信框架Netty。

5.2.2 特征

SpringCloud官方,对SpringCloud Gateway 特征介绍如下:
   ● 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
   ● 集成 Spring Cloud DiscoveryClient
   ● Predicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters
   ● 具备一些网关的高级功能:动态路由、限流、路径重写
   ● 集成Spring Cloud DiscoveryClient
   ● 集成熔断器CircuitBreaker
从以上的特征来说,和Zuul的特征差别不大。SpringCloud Gateway和Zuul主要的区别,还是在底层的通信框架上。
简单说明一下上文中的三个术语:
(1)Filter(过滤器):
   和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对下游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。
(2)Route(路由):
   网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
(3)Predicate(断言):
   这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。

5.2.3 入门案例

创建微服务网关工程05_cloud_gateway。
1.添依赖
添加依赖Eureka Discovery Client,Spring Cloud Gateway,如图5-2所示。
在这里插入图片描述

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 https://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.4.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lxs.demo</groupId>
    <artifactId>05_cloud_gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>05_cloud_gateway</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.3</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2.启动器

package com.lxs.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

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

}

3.配置文件

application.yml代码如下。
server:
  port: 9005
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: url-proxy-1
          uri: https://blog.csdn.net
          predicates:
            - Path=/csdn      
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:9004/eureka

4.启动并测试
启动Eureka Server和网关微服务访问http://localhost:9005/csdn,发现路由到了https://blog.csdn.net

5.2.4 处理流程

   客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。如图5-3所示。
在这里插入图片描述

5.3 路由配置方式

路由是网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。

5.3.1 基础路由配置方式

如果请求的目标地址,是单个的URI资源路径,配置文件实例如下。

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: service1
          uri: https://blog.csdn.net
          predicates:
            - Path=/csdn

各字段含义如下。
   ● id:我们自定义的路由 ID,保持唯一
   ● uri:目标服务地址
   ● predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
上面这段配置的意思是,配置了一个 id 为 url-proxy-1的URI代理规则,路由的规则为,当访问地址http://localhost:8080/csdn/1.jsp时,会路由到上游地址https://blog.csdn.net/1.jsp。

5.3.2 基于代码的路由配置方式

转发功能同样可以通过代码来实现,我们可以在启动类 GateWayApplication 中添加方法 customRouteLocator() 来定制转发规则。

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

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

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("path_route", r -> r.path("/csdn")
                        .uri("https://blog.csdn.net"))
                .build();
    }
}

5.3.3 和注册中心相结合的路由配置方式

在uri的schema协议部分为自定义的lb:类型,表示从微服务注册中心(如Eureka)订阅服务,并且通过负载均衡进行服务的路由。代码如下。

server:
  port: 9005
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: service1
          uri: https://blog.csdn.net
          predicates:
            - Path=/csdn
        - id: service2
        # uri: http://127.0.0.1:9001
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/**
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:9004/eureka

   注册中心相结合的路由配置方式,与单个URI的路由配置,区别其实很小,仅仅在于URI的schema协议不同。单个URI的地址的schema协议,一般为http或者https协议。启动多个支付微服务,会发现端口9000,9001轮流出现。

5.4 路由匹配规则

   Spring Cloud Gateway的主要功能之一是转发请求,转发规则的定义主要包含三个部分,如表5-1所示。
在这里插入图片描述
   Spring Cloud Gateway 的功能很强大,我们仅仅通过 Predicates 的设计就可以看出来,前面我们只是使用了 predicates 进行了简单的条件匹配,其实 Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。
   Spring Cloud Gateway 是通过 Spring WebFlux 的 HandlerMapping 做为底层支持来匹配到转发路由,Spring Cloud Gateway 内置了很多 Predicates 工厂,这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。如图5-4所示。
在这里插入图片描述

5.4.1 Predicate 断言条件

   Predicate 来源于 Java 8,是 Java 8 中引入的一个函数,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。网上有一张图总结了 Spring Cloud 内置的几种 Predicate 的实现。如图5-5所示。
在这里插入图片描述
   说白了 Predicate 就是为了实现一组匹配规则,方便让请求过来找到对应的 Route 进行处理,接下来我们接下 Spring Cloud GateWay 内置几种 Predicate 的使用。转发规则(predicates),假设 转发uri都设定为http://localhost:9001,常见Predicate,如表5-2所示
在这里插入图片描述
1.通过请求参数匹配
Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。

spring:
  cloud:
    gateway:
      routes:
        - id: service3
          uri: https://www.baidu.com
          order: 0
          predicates:
            - Query=smile

   这样配置,只要请求中包含 smile 属性的参数即可匹配路由。使用 curl 测试,命令行输入:curl localhost:9005?smile=x&id=2,经过测试发现只要请求汇总带有 smile 参数即会匹配路由,不带 smile 参数则不会匹配。
   还可以将 Query 的值以键值对的方式进行配置,这样在请求过来时会对属性值和正则进行匹配,匹配上才会走路由。

spring:
  cloud:
    gateway:
      routes:
        - id: service3
          uri: https://www.baidu.com
          order: 0
          predicates:
            - Query=keep, pu.

   这样只要当请求中包含 keep 属性并且参数值是以 pu 开头的长度为三位的字符串才会进行匹配和路由。使用 curl 测试,命令行输入:curl localhost:8080?keep=pub,测试可以返回页面代码,将 keep 的属性值改为 pubx 再次访问就会报 404,证明路由需要匹配正则表达式才会进行路由。
2. 通过Header匹配
   Header Route Predicate 和 Query Route Predicate 一样,也是接收 2 个参数,一个 header 中属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。

spring:
  cloud:
    gateway:
      routes:        
        - id: service4
          uri: https://www.baidu.com
          order: 0
          predicates:
            - Header=X-Request-Id, \d+

   使用 curl 测试,命令行输入:curl http://localhost:9005 -H “X-Request-Id:88”,则返回页面代码证明匹配成功。将参数-H "X-Request-Id:88"改为-H “X-Request-Id:spring”,再次执行时返回404证明没有匹配。
3. 通过Cookie匹配
   Cookie Route Predicate 可以接收两个参数,一个是 Cookie name ,一个是正则表达式,路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。

spring:
  cloud:
    gateway:
      routes:        
        - id: service5
          uri: https://www.baidu.com
          predicates:
            - Cookie=sessionId, test

   使用 curl 测试,命令行输入,curl http://localhost:9005 --cookie “sessionId=test”,则会返回页面代码,如果去掉–cookie “sessionId=test”,后台汇报 404 错误。
4.通过Host匹配
   Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。它通过参数中的主机地址作为匹配规则

spring:
  cloud:
    gateway:
      routes:    
        - id: service6
          uri: https://www.baidu.com
          predicates:
            - Host=**.baidu.com      

   使用 curl 测试,命令行输入,curl http://localhost:9005 -H "Host: www.baidu.com"或者curl http://localhost:8080 -H “Host: md.baidu.com”,经测试以上两种 host 均可匹配到 host_route 路由,去掉 host 参数则会报 404 错误。
5.通过请求方式匹配
   可以通过是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由。

spring:
  cloud:
    gateway:
      routes:    
        - id: service7
          uri: https://www.baidu.com
          predicates:
            - Method=PUT

   使用 curl 测试,命令行输入,curl -X PUT http://localhost:9005,测试返回页面代码,证明匹配到路由,以其他方式,返回 404 没有找到,证明没有匹配上路由
6.通过请求路径匹配
   Path RoutePredicate 接收一个匹配路径的参数来判断是否路由。

spring:
  cloud:
    gateway:
      routes:    
        - id: service8
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/payment/{segment}

   如果请求路径符合要求,则此路由将匹配, curl 测试,命令行输入,curl http://localhost:9005/payment/1,
可以正常获取到页面返回值,curl http://localhost:9005/payment2/1,报404,证明路由是通过指定路由来匹配。
7.组合匹配

spring:
  cloud:
    gateway:
      routes:    
        - id: service9
          uri: https://www.baidu.com
          order: 0
          predicates:
            - Host=**.foo.org
            - Path=/headers
            - Method=GET
            - Header=X-Request-Id, \d+
            - Query=foo, ba.
            - Query=baz
            - Cookie=chocolate, ch.p

   各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。
一个请求满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发

5.4.2 过滤器规则

列举几个过滤器,如表5-3所示。
在这里插入图片描述
1.PrefixPath
   对所有的请求路径添加前缀

spring:
  cloud:
    gateway:
      routes:    
        - id: service10
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/{segment}
          filters:
            - PrefixPath=/payment

   访问/123请求被发送到http://127.0.0.1:9001/payment/123。
2.2. StripPrefix
   跳过指定的路径

spring:
  cloud:
    gateway:
      routes:    
        - id: service11
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/api/{segment}
          filters:
            - StripPrefix=1
            - PrefixPath=/payment

   此时访问http://localhost:9005/api/123,首先StripPrefix过滤器去掉一个/api,然后PrefixPath过滤器加上一个/payment,能够正确访问到支付微服务。
3. RewritePath

spring:
  cloud:
    gateway:
      routes:        
        - id: service12
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/api/payment/**
          filters:
            - RewritePath=/api/(?<segment>.*), /$\{segment}

   请求http://localhost:9005/api/payment/123路径,RewritePath过滤器将路径重写为http://localhost:9005/payment/123,能够正确访问支付微服务。
4. SetPath
   SetPath和Rewrite类似,代码如下。

spring:
  cloud:
    gateway:
      routes:           
        - id: service13
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/api/payment/{segment}
          filters:
            - SetPath=/payment/{segment}

   请求http://localhost:9005/api/payment/123路径,SetPath过滤器将路径设置为http://localhost:9005/payment/123,能够正确访问支付微服务。
5. RemoveRequestHeader
   去掉某个请求头信息。

spring:
  cloud:
    gateway:
      routes:
        - id: removerequestheader_route
          uri: https://example.org
          filters:
          - RemoveRequestHeader=X-Request-Foo

   去掉请求头X-Request-Foo
6.RemoveResponseHeader
   去掉某个回执头信息

spring:
  cloud:
    gateway:
      routes:
        - id: removerequestheader_route
          uri: https://example.org
          filters:
          - RemoveResponseHeader=X-Request-Foo

7.SetRequestHeader
   设置请求头信息

spring:
  cloud:
    gateway:
      routes:
        - id: setrequestheader_route
          uri: https://example.org
          filters:
          - SetRequestHeader=X-Request-Red, Blue

8.default-filters
   对所有的请求添加过滤器。

spring:
  cloud:
    gateway:
      routes:        
        - id: service14
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/9001/{segment}
        - id: service15
          uri: http://127.0.0.1:9000
          predicates:
            - Path=/9000/{segment}
      default-filters:
      	- StripPrefix=1
        - PrefixPath=/payment

5.5 自定义过滤器

5.5.1 过滤器执行次序

   Spring-Cloud-Gateway 基于过滤器实现,同 zuul 类似,有pre和post两种方式的 filter,分别处理前置逻辑和后置逻辑。客户端的请求先经过pre类型的 filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过post类型的 filter 处理,最后返回响应到客户端。过滤器执行流程如下,order 越大,优先级越低,如图5-6所示。
在这里插入图片描述
过滤器分为全局过滤器和局部过滤器。
   ● 全局过滤器:对所有路由生效。
   ● 局部过滤器:对指定的路由生效。

5.5.2 全局过滤器

   实现 GlobalFilter 和 Ordered,重写相关方法,加入到spring容器管理即可,无需配置,全局过滤器对所有的路由都有效。代码如下。

//@Configuration
public class FilterConfig
{

    @Bean
    public GlobalFilter a()
    {
        return new AFilter();
    }

    @Bean
    public GlobalFilter b()
    {
        return new BFilter();
    }

    @Bean
    public GlobalFilter c()
    {
        return new CFilter();
    }

    @Bean
    public GlobalFilter myAuthFilter()
    {
        return new MyAuthFilter();
    }


    @Slf4j
    static class AFilter implements GlobalFilter, Ordered
    {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
        {
            log.info("AFilter前置逻辑");
            return chain.filter(exchange).then(Mono.fromRunnable(() ->
            {
                log.info("AFilter后置逻辑");
            }));
        }

        //   值越小,优先级越高
        @Override
        public int getOrder()
        {
            return HIGHEST_PRECEDENCE + 100;
        }
    }

    @Slf4j
    static class BFilter implements GlobalFilter, Ordered
    {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
        {
            log.info("BFilter前置逻辑");
            return chain.filter(exchange).then(Mono.fromRunnable(() ->
            {
                log.info("BFilter后置逻辑");
            }));
        }

        @Override
        public int getOrder()
        {
            return HIGHEST_PRECEDENCE + 200;
        }
    }

    @Slf4j
    static class CFilter implements GlobalFilter, Ordered
    {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
        {
            log.info("CFilter前置逻辑");
            return chain.filter(exchange).then(Mono.fromRunnable(() ->
            {
                log.info("CFilter后置逻辑");
            }));
        }

        @Override
        public int getOrder()
        {
            return HIGHEST_PRECEDENCE + 300;
        }
    }


    @Slf4j
    static class MyAuthFilter implements GlobalFilter, Ordered {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("MyAuthFilter权限过滤器");
            String token = exchange.getRequest().getHeaders().getFirst("token");
            if (StringUtils.isBlank(token)) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }

            return chain.filter(exchange);
        }

        @Override
        public int getOrder() {
            return HIGHEST_PRECEDENCE + 400;
        }
    }

}

   定义了4个全局过滤器,顺序为A>B>C>MyAuthFilter,其中全局过滤器MyAuthFilter中判断令牌是否存在,如果令牌不存在,则返回401状态码,表示没有权限访问,使用Postman执行请求,如图5-7所示。
在这里插入图片描述

5.5.3 局部过滤器

定义局部过滤器步骤如下。
(1)需要实现GatewayFilter, Ordered,实现相关的方法
(2)包装GatewayFilter,产生GatewayFilterFactory
(3)GatewayFilterFactory加入到过滤器工厂,并且注册到spring容器中。
(4)在配置文件中进行配置,如果不配置则不启用此过滤器规则。
接下来定义局部过滤器,对于请求头user-id校验,如果不存在user-id请求头,直接返回状态码406。代码如下。

@Component
public class UserIdCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
{
    @Override
    public GatewayFilter apply(Object config)
    {
        return new UserIdCheckGateWayFilter();
    }

    @Slf4j
    static class UserIdCheckGateWayFilter implements GatewayFilter, Ordered
    {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
        {
            String url = exchange.getRequest().getPath().pathWithinApplication().value();
            log.info("请求URL:" + url);
            log.info("method:" + exchange.getRequest().getMethod());
            //获取header
            String userId = exchange.getRequest().getHeaders().getFirst("user-id");
            log.info("userId:" + userId);

            if (StringUtils.isBlank(userId))
            {
                log.info("*****头部验证不通过,请在头部输入  user-id");
                //终止请求,直接回应
                exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }

        //   值越小,优先级越高
        @Override
        public int getOrder()
        {
            return HIGHEST_PRECEDENCE;
        }
    }

}

配置文件application.yml代码如下。

spring:
  cloud:
    gateway:
      routes:        
        - id: service14
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/{segment}
      default-filters:
        - PrefixPath=/payment
        - UserIdCheck

5.6 高级特性

5.6.1 熔断降级

   在分布式系统中,网关作为流量的入口,因此会有大量的请求进入网关,向其他服务发起调用,其他服务不可避免的会出现调用失败(超时、异常),失败时不能让请求堆积在网关上,需要快速失败并返回给客户端,想要实现这个要求,就必须在网关上做熔断、降级操作。
   为什么在网关上请求失败需要快速返回给客户端?因为当一个客户端请求发生故障的时候,这个请求会一直堆积在网关上,当然只有一个这种请求,网关肯定没有问题(如果一个请求就能造成整个系统瘫痪,那这个系统可以下架了),但是网关上堆积多了就会给网关乃至整个服务都造成巨大的压力,甚至整个服务宕掉。因此要对一些服务和页面进行有策略的降级,以此缓解服务器资源的的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应,所以需要网关上请求失败需要快速返回给客户端。
CircuitBreaker过滤器使用Spring Cloud CircuitBreaker API 将网关路由包装在断路器中。
   Spring Cloud CircuitBreaker 支持多个可与 Spring Cloud Gateway 一起使用熔断器库。比如,Spring Cloud 支持开箱即用的 Resilience4J。要启用Spring Cloud CircuitBreaker过滤器,步骤如下。
1.添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>

2.配置文件

server:
  port: 9005
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: service14
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/payment/{segment}
          filters:
            - name: CircuitBreaker
              args:
                name: backendA
                fallbackUri: forward:/fallbackA

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:9004/eureka

resilience4j:
  circuitbreaker:
    configs:
      default:
        failureRateThreshold: 30 #失败请求百分比,超过这个比例,CircuitBreaker变为OPEN状态
        slidingWindowSize: 10 #滑动窗口的大小,配置COUNT_BASED,表示10个请求,配置TIME_BASED表示10秒
        minimumNumberOfCalls: 5 #最小请求个数,只有在滑动窗口内,请求个数达到这个个数,才会触发CircuitBreader对于断路器的判断
        slidingWindowType: TIME_BASED #滑动窗口的类型
        permittedNumberOfCallsInHalfOpenState: 3 #当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求个数
        automaticTransitionFromOpenToHalfOpenEnabled: true #设置true,表示自动从OPEN变成HALF_OPEN,即使没有请求过来
        waitDurationInOpenState: 2s #从OPEN到HALF_OPEN状态需要等待的时间
        recordExceptions: #异常名单
          - java.lang.Exception
    instances:
      backendA:
        baseConfig: default
      backendB:
        failureRateThreshold: 50
        slowCallDurationThreshold: 2s #慢调用时间阈值,高于这个阈值的呼叫视为慢调用,并增加慢调用比例。
        slowCallRateThreshold: 30 #慢调用百分比阈值,断路器把调用时间大于slowCallDurationThreshold,视为慢调用,当慢调用比例大于阈值,断路器打开,并进行服务降级
        slidingWindowSize: 10
        slidingWindowType: TIME_BASED
        minimumNumberOfCalls: 2
        permittedNumberOfCallsInHalfOpenState: 2
        waitDurationInOpenState: 120s #从OPEN到HALF_OPEN状态需要等待的时间

3.全局过滤器
创建一个全局过滤器,打印熔断器状态,代码如下

@Component
@Slf4j
public class CircuitBreakerLogFilter implements GlobalFilter, Ordered {

    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String url = exchange.getRequest().getPath().pathWithinApplication().value();
        log.info("url : {} status : {}", url, circuitBreakerRegistry.circuitBreaker("backendA").getState().toString());
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

4.降级方法

@RestController
@Slf4j
public class FallbackController {

    @GetMapping("/fallbackA")
    public ResponseEntity fallbackA() {
        return ResponseEntity.ok("服务不可用,降级");
    }
}

使用JMeter工具同时1秒内并发发送20次请求,触发断路器backendA熔断条件,断路器打开,之后2秒后熔断器自动进入进入半开状态(automaticTransitionFromOpenToHalfOpenEnabled: true,waitDurationInOpenState: 2s),如图5-8所示。
在这里插入图片描述
接下来2秒后再次请求熔断器处于半开状态,接下来启动支付微服务,服务能够正常访问熔断器关闭。

5.6.2 统一跨域请求

1.跨域简介
   跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指的是这样的一个概念:我们认为若协议 + 域名 + 端口号均相同,那么就是同域。
   举个例子:假如一个域名为aaa.cn的网站,它发起一个资源路径为aaa.cn/books/getBookInfo的 Ajax 请求,那么这个请求是同域的,因为资源路径的协议、域名以及端口号与当前域一致(例子中协议名默认为http,端口号默认为80)。但是,如果发起一个资源路径为bbb.com/pay/purchase的 Ajax 请求,那么这个请求就是跨域请求,因为域不一致,与此同时由于安全问题,这种请求会受到同源策略限制。
   演示一下,创建index.html,发送ajax测试跨域,代码如下所示。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="./js/jquery/jquery-1.10.2.min.js"></script>
    <script>
        function sendAjax() {
            $.ajax({
                method: 'GET',
                url: "http://127.0.0.1:9001/payment/123",
                contentType: 'application/json; charset=UTF-8',
                success: function(o) {
                    alert(o.id);
                    alert(o.message);
                }
            });
        }
    </script>
</head>
<body>
    <button onclick="sendAjax();" >send ajax</button>
</body>
</html>

   通过上述index.html,发送请求,因为浏览器同源策略,就会出现跨域访问问题。
   虽然在安全层面上同源限制是必要的,但有时同源策略会对我们的合理用途造成影响,为了避免开发的应用受到限制,有多种方式可以绕开同源策略,常用的做法JSONP, CORS。可以使用@CrossOrigin,代码如下。

@RestController
@RequestMapping("/payment")
@CrossOrigin
public class PaymentController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/{id}")
    public ResponseEntity<Payment> payment(@PathVariable("id") Integer id) {
        Payment payment = new Payment(id, "支付成功,服务端口=" + serverPort);
        return ResponseEntity.ok(payment);
    }

}

2.跨域配置
   现在请求经过gatway网关是,可以通过网关统一配置跨域访问,代码如下。

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origin-patterns: "*" # spring boot2.4配置
#            allowed-origins: "*"
            allowed-headers: "*"
            allow-credentials: true
            allowed-methods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
举报

相关推荐

0 条评论