[SpringCloud-07] 服务网关:SpringCloud Gateway

1、前言

1.1 SpringCloud Gateway

  GateWay 在这里指的是 Spring 的 API 网关服务 SpringCloud Gateway,不是广义上的网关的意思。它是基于 Spring5、SpringBoot2 和 Project Reactor 等技术的。旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。

  SpringCloud GateWay 作为 SpringCloud 生态系统中的网关,目标是替代 Zuul,在 SpringCloud 2.0 以上的版本,没有对应的 Zuul 2.0 以上最新高性能版本进行集成,仍然还是使用的 Zuul 1.x 非 Reactor 模式的老版本。为了提升网关的性能,SpringCloud 是基于 WebFlux 框架实现的,而 WebFlux 框架的底层使用了高性能的 Reactor 模式通信框架 Netty。

  GateWay 之所以性能好,就是因为底层使用了 WebFlux,而 WebFlux 底层使用了 Netty 通信(NIO)。

  网关在整个请求响应中,处于下图这个位置:
在这里插入图片描述
  SpringCloud GateWay 具有以下特性:
  1、动态路由:能够匹配任何请求属性;
  2、可以对路由指定 Predicate(断言)和 Filter (过滤器);
  3、集成 Hystrix 的断路器功能;
  4、集成 SpringCloud 服务发现功能;
  5、易于编写的 Predicate 和 Filter;
  6、请求限流功能;
  7、支持路径重写。

1.2 Zuul

  Gateway 和 Zuul的区别:
  1、Zuul 1.x 是基于阻塞 I/O 的 API Gateway;
  2、Zuul 1.x 是基于 Servlet 2.5 的使用阻塞架构,它不支持任何长链接(如:WebSocket),Zuul 的设计模式和 Nginx 比较像,每次 I/O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是 Nginx 用 C++ 实现,Zuul 用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得 Zuul 的性能相对较差。
  3、Zuul 2.x 理念更先进,基于 Netty 非阻塞和支持长连接,但是 SpringCloud 目前没有整合,Zuul 2.x 性能比 1.x 有较大的提升。在性能方面,Zuul 2.x 是SpringCloud Gateway 的 1.6 倍。
  4、SpringCloud Gateway 建立在 SpringFramework5、Project Reactor 和 SpringBoot 2 之上,使用非阻塞 API。
  5、SpringCloud Gateway 还支持 WebSocket,并且与 Spring 紧密集成。

  Zuul 1.x 的模型如下图所示
在这里插入图片描述
  Servlet 是一个简单的网络 I/O 模型,当请求进入 Servlet 容器时,Servlet 容器就会给这个请求绑定一个线程,在并发量不高的场景下,这种模型是使用的。但是在高并发的场景下,线程数量就会上涨,而线程资源代价是昂贵的(上下文线程切换、内存消耗大),严重影响请求的处理时间。

  在一些简单业务场景下,不希望为每一个 request 分配一个线程,只要 1 个或者几个线程就能应付极大并发的请求,这种业务场景下 servlet 模型没有优势。

  Zuul 1.x 基于 servlet 这种阻塞式处理模型,即 spring 实现了处理所有 request 请求的 servlet (DispatcherServlet),并由该 servlet 阻塞式处理,所以 SpringCloud Zuul 无法摆脱 servlet 模型的弊端。

1.3 WebFlux

  WebFlux 是一个非阻塞式 Web 框架,类似 SpringMVC 这种。

  传统的 Web 框架,比如:structs2、SpringMVC 等都是基于 servlet 和 servlet 容器之上运行的。Servlet 3.1 之后有了异步非阻塞的支持。

  WebFlux 是一个典型的非阻塞异步框架,核心是基于 Reactor 的相关 API 实现的。相对于传统的 Web 框架来说,它可以运行在诸如 Netty、Undertow 及支持 Servlet 3.1 的容器上。

2、Gateway的相关概念

  路由:构建网关的基本模块,由 id、url、断言、过滤器等组成,如果断言为 true 则匹配该路由。简单来说,就是根据某些规则,将请求发送到指定服务上。

  断言:参考的是 Java8 的 java.util.function.Predicate 。开发人员可以匹配 HTTP 请求中的所有内容(例如请求头、请求参数等),如果请求和断言相匹配,则进行路由。简单来说,就是一种判断条件。

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

3、Gateway的工作原理

在这里插入图片描述
  客户端向 SpringCloud Gateway 发出请求,然后在 Gateway Handlet Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。

  Handler 再通过指定的过滤器来将请求发送到我们实际的服务执行业务逻辑,然后返回。

  过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前或之后执行业务逻辑。

  Filter 在 pre 类型的过滤器可以做参数校验、权限校验、流量控制、日志输出、协议转换等。在 post 类型的过滤器中可以做相应内容、响应头的修改、日志输出、流量监控等。

4、Gateway的使用

4.1 路由功能

  比如现在已经拥有名字叫做 CLOUD-PAYMENT-SERVICE 的服务,这个服务对应着 8001 端口和 8002 端口的两个模块。
在这里插入图片描述
  现在我不想直接访问 CLOUD-PAYMENT-SERVICE ,而是访问 Gateway,然后 Gateway 来访问 CLOUD-PAYMENT-SERVICE ,并把响应结果返回给客户端。
在这里插入图片描述
  创建一个模块作为网关,导入 Gateway 依赖和 Eureka 依赖:

<!--gateway-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

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

  编写配置文件 application.yaml

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启从注册中心动态创建路由的功能,利用微服务的名称进行路由
      routes:
        - id: payment_routh
          # uri: http://localhost:8001
          uri: lb://CLOUD-PAYMENT-SERVICE # 注意这里的前缀是 lb ,不是 http ,表示负载均衡
          predicates:
            - Path=/payment/get/**  # Path 断言,表示路径相匹配的才进行路由

        - id: payment_routh2
          # uri: http://localhost:8001
          uri: lb://CLOUD-PAYMENT-SERVICE
          predicates:
            - Path=/payment/lb

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka  # 服务注册中心的地址
    register-with-eureka: true  # 自己注册到服务中心
    fetch-registry: true

  主启动类激活 Eureka:

package pers.klb.springcloud;

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

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

  启动 Eureka Server、Gateway、8001、8002后,通过浏览器访问:http://localhost:9527/payment/get/12 即可通过网关来访问服务模块。

  我们的路由就是配置文件中的这部分:
在这里插入图片描述

4.2 断言功能

  其实在上面的配置中已经使用了断言功能,即这部分:
在这里插入图片描述
  断言其实就是一种判断,判断这个请求能不能取到某一个服务。

  我们可以看到 Gateway 模块的启动日志里有这个:
在这里插入图片描述
  这个是路由断言工厂,包含多个断言功能。比如说其中的 Path 就是判断请求的路径,是否有对应的服务访问路径和请求的路径对应上,如果对应得上,那么 Path 断言就成功。

  这里就不一个个展示所有断言功能,每一个的使用方式是一样的。修改配置文件为:
在这里插入图片描述
  当有一个请求来到 Gateway,那么就判断这个请求的三个方面,首先是 Path,Gateway 的所有 routes 中,有没有一个服务是满足这个路径的;如果有,再看第二个断言 After ,这个断言的意思是在这个时间之后才能访问;如果又满足,接着判断第三个 Cookie 断言,这个断言的意思就是你这个请求中的 Cookie 有没有包含 “username=klb” 的键值对,有则通过,没有则不通过。

  可以看出,断言其实就是网关决定这个请求可以去哪一个微服务的判断工作,每一个断言加起来变成逻辑 and,只要其中一个断言判断不通过,就没得访问。

4.3 过滤器功能

  通过了断言后,请求会进入过滤器,过滤器可用于修改进入的 Http 请求和返回的 Http 相应,路由过滤器只能指定路由进行使用。

  SpringCloud Gateway 内置了多种过滤器,由 GatewayFilter 的工厂类来生成。生命周期为:请求进入路由之前,和处理请求完成,再次到达路由之前。

  基本用法如下:
在这里插入图片描述
  一般来说,我们很少用已拥有的过滤器,都是使用自定义的过滤器。

  自定义过滤器必须实现两个接口:GlobalFilter 和 Ordered:

package pers.klb.springcloud.gatewayfilters;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("已进入过滤器:" + new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname==null){
            log.info("你的请求没有包含uname,你是非法用户o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();    // 直接返回,不用进入下一个过滤器
        }
        return chain.filter(exchange);  // 进入下一个过滤器
    }

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

  只需要这个过滤器能够被扫描到 IOC 容器,它会自动生效,无需其他配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值