Skip to content

Gateway组件

1.1 Gateway简介

1.1.1 官网

  • https://docs.spring.io/spring-cloud-gateway/docs/4.0.x/reference/html/

image-20230721090940955

1.1.2 概述

  • Gateway是在spring生态系统之上构建的API网关服务,基于Spring5,SpringBoot2和Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等
  • SpringCloud Gateway是SpringCloud的一个全新项目,基于Spring5.X+SpringBoot2.X和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。
  • 为了提升网关的性能,SpringCloud Gatway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通讯框架Netty。
  • SpringCloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全、监控/指标、和限流。

1.1.3 架构图

image-20230624161556300

1.2 三大核心概念

1.2.1 Route(路由)

路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由

image-20230721091801547

1.2.2 Predicate(断言)

参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

image-20230721091835236

1.2.3 Filter(过滤)

指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

image-20230721091858141

1.3 工作流程

image-20230721092104682

image-20230721092129655

  • 客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求匹配的路由,将其发送到Gateway Web Handler.
  • Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回。
  • 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")或之后("post")执行业务逻辑。
  • Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量控制等有着非常重要的作用

1.4 Gateway入门

下面,我们就演示下网关的基本路由功能。基本步骤如下:

  • 1、在spzx-cloud-parent下创建子模块spzx-cloud-gateway

image-20230721101439700

  • 2、引入如下依赖:
xml
<!--网关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!--nacos服务发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- 负载均衡组件 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>

image-20230721092532222

  • 3、编写启动类
java
// com.at.spzx.cloud.gateway
@SpringBootApplication
public class GatewayApplication {

  public static void main(String[] args) {
    SpringApplication.run(GatewayApplication.class, args);
  }
}
  • 4、在application.yml配置文件中编写基础配置和路由规则
yaml
server:
  port: 8222
spring:
  application:
    name: spzx-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: spzx-cloud-user  # 路由id,可以自定义,只要唯一即可
          uri: lb://spzx-cloud-user  # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates:
            - Path=/*/user/** # 路径匹配
        - id: spzx-cloud-order
          uri: lb://spzx-cloud-order
          predicates:
            - Path=/*/order/** # 路径匹配
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  • 5、重启测试

重启网关,访问http://localhost:8222/api/user/findUserByUserId/1时,符合/api/user/**规则,

请求转发到uri:http://spzx-cloud-user/api/user/findUserByUserId/1,得到了结果:

image-20230624163155679

1.5 Predicate的使用

  • 启动网关服务后,在控制台可以看到如下信息:

image-20230721101903404

  • 思考问题:我们在配置文件中只是配置了一个访问路径的规则,怎么就可以实现路由呢?

底层原理:框架底层会自动读取配置文件中的内容,然后通过制定的路由工厂将其转换成对应的判断条件,然后进行判断。在Gateway中提供了很多的路由工厂如下所示:https://docs.spring.io/spring-cloud-gateway/docs/4.0.6/reference/html/#gateway-request-predicates-factories

image-20230624163811030

大致有12个,每一种路由工厂的使用Spring Cloud的官网都给出了具体的示例代码,我们可以参考示例代码进行使用。以After Route Predicate

Factory路由工厂举例,如下所示:

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: spzx-cloud-user
          uri: lb://spzx-cloud-user
          predicates:
            - Path=/api/user/**
            - After=2023-07-21T10:23:06.978038800+08:00[Asia/Shanghai]  # 系统时间在2023-07-21之后才可以进行访问
java
//获取当前时区时间代码
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
  • 总结

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapper基础框架的一部分。 Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合 Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给 Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and 。

1.6 过滤器

1.6.1 过滤器简介

在gateway中要实现其他的功能:权限控制、流量监控、统一日志处理等。就需要使用到gateway中所提供的过滤器了。过滤器,可以对进入网关的请求和微服务返回的响应做处理:

image-20230624164230054

1.6.2 内置过滤器

spring gateway提供了31种不同的过滤器。

官网地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

例如:

名称说明
AddRequestHeader给当前请求添加一个请求头
RemoveRequestHeader移除请求中的一个请求头
AddResponseHeader给响应结果中添加一个响应头
RemoveResponseHeader从响应结果中移除有一个响应头
RequestRateLimiter限制请求的流量

在Gateway中提供了三种级别的类型的过滤器:

1、路由过滤器:只针对当前路由有效

2、默认过滤器:针对所有的路由都有效

3、全局过滤器:针对所有的路由都有效,需要进行自定义

1.6.3 路由过滤器

需求:给所有进入spzx-cloud-user的请求添加一个请求头:Truth=at

实现:

1、修改gateway服务的application.yml文件,添加路由过滤

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: spzx-cloud-user
          uri: lb://spzx-cloud-user
          predicates:
            - Path=/api/user/**
          filters:
            - AddRequestHeader=Truth, at		# 配置路由基本的过滤器,给访问user微服务的所有接口添加Truth请求头

当前过滤器写在spzx-cloud-user路由下,因此仅仅对访问spzx-cloud-user的请求有效。

2、在spzx-cloud-user的接口方法中读取请求头数据,进行测试

java
@GetMapping(value = "/findUserByUserId/{userId}")
public User findUserByUserId(@PathVariable(value = "userId") Long userId , @RequestHeader(name = "Truth")String header) {
    log.info("UserController...findUserByUserId方法执行了... ,header: {} " , header);
    return userService.findUserByUserId(userId) ;
}

1.6.4 默认过滤器

如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

yml
spring:
  cloud:
    gateway:
      routes:
        - id: spzx-cloud-user
          uri: lb://spzx-cloud-user
          predicates:
            - Path=/api/user/**
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]
      default-filters:
        - AddRequestHeader=Truth, at is good

1.6.5 全局过滤器

  • 概述

上述的过滤器是gateway中提供的默认的过滤器,每一个过滤器的功能都是固定的。但是如果我们希望拦截请求,做自己的业务逻辑,默认的过滤器就没办法实现。此时就需求使用全局过滤器,全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。

  • 需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:

请求参数中是否有username,如果同时满足则放行,否则拦截

  • 步骤分析:

1、定义一个类实现GlobalFilter接口

2、重写filter方法

3、将该类纳入到spring容器中

4、实现Ordered接口定义该过滤器的顺序

  • 实现代码:
java
@Component
public class AuthorizationFilter implements GlobalFilter, Ordered {

    //实现过滤器逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String username = exchange.getRequest().getQueryParams().getFirst("username");
        if(!StringUtils.hasText(username)){
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    //定义该过滤器的顺序
    @Override
    public int getOrder() {
        return 0;
    }
}

1.6.6 过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:

image-20230624170925571

排序的规则是什么呢?

1、按照order的值进行排序,order的值越小,优先级越高,执行顺序越靠前。

2、路由过滤器和默认过滤器会按照order的值进行排序,这个值由spring进行指定,默认是按照声明顺序从1递增

3、当过滤器的order值一样时,会按照 globalFilter > defaultFilter > 路由过滤器的顺序执行

核心源码分析:org.springframework.cloud.gateway.handler.FilteringWebHandler#handle方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链

java
public Mono<Void> handle(ServerWebExchange exchange) {
    Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
    
    // 获取路由级别的过滤器和默认过滤器的集合
    List<GatewayFilter> gatewayFilters = route.getFilters();
    
    // 获取全局过滤器的集合
    List<GatewayFilter> combined = new ArrayList(this.globalFilters);
    
    // 将取路由级别的过滤器和默认过滤器的集合中的元素添加到全局过滤器的集合中
    combined.addAll(gatewayFilters);
    
    // 进行排序
    AnnotationAwareOrderComparator.sort(combined);
    if (logger.isDebugEnabled()) {
        logger.debug("Sorted gatewayFilterFactories: " + combined);
    }

    // 调用过滤器链中的filter方法
    return (new DefaultGatewayFilterChain(combined)).filter(exchange);
}

2 Nacos配置中心

Nacos除了可以做注册中心,同样可以做配置管理来使用。

2.1 统一配置管理

当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就显得十分的不方便,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。

image-20230624171403235

nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。

2.2 Nacos入门

2.2.1 Nacos中添加配置

在Nacos服务端创建一个配置,如下所示:

image-20230624171530387

然后在弹出的表单中,填写配置信息:

image-20210714164856664

2.2.2 微服务集成配置中心

微服务需要进行改造,从Nacos配置中心中获取配置信息进行使用。

步骤:

1、在spzx-cloud-user微服务中,引入spring-cloud-starter-alibaba-nacos-config依赖

xml
<!-- nacos作为配置中心时所对应的依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、在spzx-cloud-user项目的 /src/main/resources/application.yml 配置文件中配置 Nacos Config 地址并引入服务配置

yml
# 配置数据库的连接信息
spring:
  cloud:
    nacos:
      config:
        server-addr: 192.168.136.142:8848
  config:
    import:
      - nacos:spzx-cloud-user-dev.yml

2.2.3 读取自定义配置

@Value

通过@Value注解读取自定义配置,如下所示:

java
@RestController
@RequestMapping(value = "/api/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService ;

    @Value("${pattern.dateformat}")
    private String pattern ;

    @GetMapping(value = "/findUserByUserId/{userId}")
    public User findUserByUserId(@PathVariable(value = "userId") Long userId , @RequestHeader(name = "Truth")String header) {
        log.info("UserController...findUserByUserId方法执行了... ,header: {} , dateformat: {} " , header , pattern);
        return userService.findUserByUserId(userId) ;
    }

}

@ConfigurationProperties

也可以通过实体类,配合@ConfigurationProperties注解读取自定义配置,代码如下所示:

1、定义一个实体类,代码如下所示:

java
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {

    private String dateformat ;

}

2、在启动类上添加@EnableConfigurationProperties注解,如下所示:

java
@SpringBootApplication
@EnableConfigurationProperties(value = { PatternProperties.class })
public class UserApplication {

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

}

3、使用该实体类,代码如下所示:

java
@RestController
@RequestMapping(value = "/api/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService ;

    @Value("${pattern.dateformat}")
    private String pattern ;

    @Autowired   // 注入实体类
    private PatternProperties patternProperties ; 

    @GetMapping(value = "/findUserByUserId/{userId}")
    public User findUserByUserId(@PathVariable(value = "userId") Long userId , @RequestHeader(name = "Truth")String header) {
        log.info("UserController...findUserByUserId方法执行了... ,header: {} , dateformat: {} " , header , patternProperties.getDateformat());
        return userService.findUserByUserId(userId) ;
    }

}

2.3 配置热更新

我们最终的目的,是修改Nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。实现配置的热更新有两种方式:

方式一:在@Value注入的变量所在类上添加注解**@RefreshScope**

image-20230624200928589

方式二:通过实体类,配合@ConfigurationProperties注解读取配置信息,自动支持热更新

2.4 配置优先级

思考问题:如果在application.yml文件中和Nacos配置中心中都定义了相同的配置内容,那么哪一个配置的优先级较高呢?

优先级顺序:Nacos配置中心的配置(后导入的配置 > 先导入的配置) > application.yml