一、新一代网关 Gateway

1. 网关

网关是所有请求的入口,是所有响应的出口,起到请求转发和安全监控等作用,是 Cloud 全家桶中很重要的组件

spring gateway netty 线程数_Cloud

2. Gateway

SpringCloud Gateway是SpringCloud的一个全新项目,基于Spring5.0+、SpringBoot2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式,且基于Filter链提供了网关基本的功能(例如:安全、监控/指标、和限流。)以及提供一些强大的过滤器功能。(例如:熔断、限流、重试等)

为了提升网关的性能,SpringCloud Gatway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通讯框架Netty。

总结起来就是:
Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架

3. 主要用途

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控
  • 。。。。。。

二、三大核心概念

1. Route(路由)

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

2. Predicate(断言)

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

3. Filter(过滤)

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

4. 总体

前端界面发送的请求进入路由,经过断言判断是否符合路由,符合就进入过滤器,过滤后进入微服务模块中。

也就是:Web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。

spring gateway netty 线程数_端口号_02

Predicate就是我们的匹配条件: 而Filter,就是可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了。

三、Gateway 工作流程

spring gateway netty 线程数_spring cloud_03


客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。

Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑

Filter 在 “pre” 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;在 “post” 类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等,有着非常重要的作用。

核心逻辑就是:路由转发+执行过滤器链

四、当前项目结构介绍

当前项目中包含八个模块:

spring gateway netty 线程数_gateway_04

  • 公有API:cloud-api-commons
  • 服务提供者: cloud-provider-payment8001/8002 (端口号8001 和 8002 )
  • 服务消费者(restTemplate): cloud-consumer-order80 (端口号80 )
  • 服务消费者(OpenFeign): cloud-consumer-feign-order80(端口号80 )
  • 注册中心 Eureka: cloud-eureka-server7001(端口号7001 )
  • 使用断路器实现的服务提供者 (Hystrix):cloud-provider-hystrix-payment8001(端口号8001)
  • 使用断路器实现的服务消费者(Hystrix):cloud-consumer-feign-hystrix-order80(端口号80)
  • 服务监控(hystrixDashboard):cloud-consumer-hystrix-dashboard9001(端口号9001)

前文链接:
项目源码:https://gitee.com/zhangchouchou/spring-cloud-demo

五、项目中添加 Gateway

1. 新建 Module

同样以 Maven 方式创建模块,命名为 cloud-gateway-gateway9527

2. POM 中添加引用

<dependencies>

        <!--引入 Eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</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>

        <!--自定义API-->
        <dependency>
            <groupId>org.zjh.springclouddemo</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

    </dependencies>

3. 添加 YML 配置

server:
  port: 9527
spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
      register-with-eureka: true
      fetch-registry: true
      service-url:
          defaultZone: http://localhost:7001/eureka

4. 添加主启动类

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

5. 配置路由映射

YML新增网关配置

在 spring.application 同级下添加:

cloud:
    gateway:
      routes:
       - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
         uri: http://localhost:8001   #匹配后提供服务的路由地址
         predicates:
           - Path=/user/get/**   #断言,路径相匹配的进行路由
 
       - id: payment_routh2
         uri: http://localhost:8001
         predicates:
           - Path=/user/lb/**   #断言,路径相匹配的进行路由

spring gateway netty 线程数_Cloud_05

routes 下的 - id 表示数组,可以配置多个
predicates 下的 - Path 同样表示数组,请求路径中包含 path 后的内容都将匹配到 8001 端口(例如:请求端口号 9527 的路径中包含 /payment/get,最终都会匹配至 8001)

6. 测试

启动 7001 注册中心 :cloud-eureka-server7001

启动 8001 :cloud-provider-payment8001

启动 9527 网关:cloud-gateway-gateway9527

spring gateway netty 线程数_端口号_06

添加网关前: http://localhost:8001/user/get/2

spring gateway netty 线程数_spring cloud_07

添加网关后: http://localhost:9527/user/get/2

spring gateway netty 线程数_spring cloud_08


测试结果证明 Gateway 网关添加成功。

六、动态路由

首先思考一个问题:访问端口号在配置文件中写死了,在实际项目中为了减轻服务器压力必然会将同一模块部署到不同的服务器上,那么负载均衡如何实现呢?我们能否通过网关事项负载均衡呢?

答案是肯定的,默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。

1. 启动服务

启动 Eureka 注册中心 和 服务提供者 8001/8002

访问注册中心进行观察 http://localhost:7001/

spring gateway netty 线程数_spring_09

2. 修改 YML

修改网关配置

discovery:
	locator:
          enabled: true  #开启从注册中心动态创建路由的功能,利用微服务名进行路由

	uri: lb://CLOUD-PAYMENT-SERVICE

spring gateway netty 线程数_端口号_10

需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri

3. 重启 9527 测试

清空 8001 和 8002 端口的控制台,再次访问 http://localhost:9527/user/get/2 ,访问后打开控制台查看测试结果

spring gateway netty 线程数_Cloud_11


spring gateway netty 线程数_Cloud_12

测试成功。

七、断言 Predicate

启动gatewat9527,查看启动日志,可以看到断言工厂标识

spring gateway netty 线程数_spring cloud_13


Spring Cloud Gateway 包括许多内置的 Route Predicate 工厂。所有这些 Predicate 都与 HTTP 请求的不同属性匹配。多个 Route Predicate 工厂可以进行组合。

Spring Cloud Gateway 创建 Route 对象时,使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。Spring Cloud Gateway 包含许
多内置的Route Predicate Factories

说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理

1. 常用的 Route Predicate

predicates:
	- Path=/payment/lb/**   #断言,路径相匹配的进行路由
	#- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
	#- Cookie=username,zhangshuai #并且Cookie是username=zhangshuai才能访问
	#- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
	#- Host=**.atguigu.com
	#- Method=GET
	#- Query=username, \d+ #要有参数名称并且是正整数才能路由

1)After Route Predicate

在某个时间之后才能访问。比如秒杀活动中可以应用。

格式:- After=2021-09-27T13:06:20.047449800+08:00[Asia/Shanghai]

spring gateway netty 线程数_端口号_14


这里我将日期设置为 9月27日 之后可访问,而当前是 9月26日,重启9527之后再次访问之前的路径,报404错误。

spring gateway netty 线程数_端口号_15


配置中的时间格式执行以下代码即可得到:

ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);

spring gateway netty 线程数_spring_16

2)Before Route Predicate

在某个时间之前能访问

- Before=2021-09-26T13:06:20.047449800+08:00[Asia/Shanghai]

3)Between Route Predicate

在某个时间段内能访问

- Between=2021-09-26T13:06:20.047449800+08:00[Asia/Shanghai] , 2021-09-27T13:06:20.047449800+08:00[Asia/Shanghai]

4)Cookie Route Predicate

请求中带有 cookie 才能访问,并且 cookie 的键值对必须是 {username:zjh}

- Cookie=username,zjh

spring gateway netty 线程数_端口号_17

使用 Curl 进行验证:下载及使用教程

spring gateway netty 线程数_Cloud_18


若修改 zjh 为 zjh123 进行验证,结果为:404

spring gateway netty 线程数_端口号_19

5)Header Route Predicate

请求头中要有X-Request-Id属性并且值为整数的正则表达式:- Header=X-Request-Id, \d+

spring gateway netty 线程数_gateway_20

spring gateway netty 线程数_spring_21

6)Host Route Predicate

主机域名
- Host=**.zjh.org

7) Method Route Predicate

请求方式是 get/post

- Method=GET

8)Query Route Predicate

要有参数名称并且是正整数才能路由:- Query=username, \d+