SpringCloud —— Gateway 网关

前文

SpringCloud 简介

SpringCloud 版本选型

SpringCloud 工程构建

SpringCloud —— Eureka 注册中心

SpringCloud —— Eureka 集群

SpringCloud —— 服务注册进 Eureka 集群

SpringCloud —— Eureka 自我保护

SpringCloud —— SpringCloud Consul 实现服务注册中心

SpringCloud —— 三个注册中心的异同点

SpringCloud —— Ribbon

SpringCloud —— Ribbon 负载均衡算法

SpringCloud —— OpenFeign

SpringCloud —— Hystrix 简介

SpringCloud —— Hystrix 断路器

SpringCloud —— HystrixDashboard 服务监控

概述简介

    Gateway 是在 Spring 生态系统上构建的 API 网关服务,基于 Spring 5,SpringBoot 2 和 Project Reactor 等技术。Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等

上一代 Zuul Zuul 官网,自行去看即可
在这里插入图片描述

Gateway 官网 https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.2.RELEASE/reference/html/

在这里插入图片描述

Gateway 是什么?

    SpringCloud 全家桶中有个很重要的组件就是网关,在 1.x 版本中都是采用的 Zuul 网关;但在 2.x 版本中,Zuul 的升级一直跳票,SpringCloud 最后自己研发了一个网关替代 Zuul,那就是 SpringCloud Gateway,Gateway 是原 Zuul 1.x 版的替代
在这里插入图片描述
    SpringCloud Gateway 是 SpringCloud 的一个全新项目,基于 Spring 5.0 + SpringBoot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式

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

    SpringCloud Gateway 的目标是提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全、监控 / 指标、和限流

Gateway 能干什么?

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

微服务中的网关在哪里?

在这里插入图片描述

为什么使用 Gateway 不使用 Zuul?

    一方面因为 Zuul 1.0 已经进入了维护阶段,而且 Gateway 是 SpringCloud 团队研发的,是子产品,值得信赖。而且很多功能 Zuul 都没有用起来也非常的简单便捷

    Gateway 是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然 Netflix 早就发布了最新的 Zuul 2.x,但 SpringCloud 貌似没有整合计划。而且 Netflix 相关组件都宣布进入维护期

多方面综合考虑 Gateway 是很理想的网关选择

SpringCloud Gateway 的特性

SpringCloud Gateway 具有如下特性:

  • 基于 Spring Framework 5,Project Reactor 和 SpringBoot 2.0 进行构建
  • 动态路由:能够匹配任何请求属性
  • 可以对路由指定 Predicate(断言)和 Filter(过滤器)
  • 集成 Hystrix 的断路器功能
  • 集成 SpringCloud 服务发现功能
  • 易于编写的 Predicate(断言)和 Filter(过滤器)
  • 请求限流功能
  • 支持路径重写

SpringCloud 与 Zuul 的区别

在 SpringCloud Finchley 正式版之前,SpringCloud 推荐的网关是 Netflix 提供的 Zuul:

  • Zuul 1.x 是一个基于阻塞 I/O 的 API Gateway
  • Zuul 1.x 基于 Servlet 2.5 使用阻塞架构,它不支持任何长连接(如 WebSocket)Zuul 的设计模式和 Nginx 较相似,每次 I/O 操作都是从工作流程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是 Nginx 是使用 C++ 实现的,而 Zuul 是使用 Java 实现的,而 JVM 本身会有第一次加载较慢的情况,使得 Zuul 的性能相对较差
  • Zuul 2.x 理念更先进,想基于 Netty 非阻塞和支持长连接,但 SpringCloud 目前还没有整合。Zuul 2.x 的性能较 Zuul 1.x 有较大的提升。在性能方面,根据官方提供的基准测试,SpringCloud Gateway 的 RPS(每秒请求数)是 Zuul 的 1.6 倍
  • SpringCloud Gateway 建立在 Spring Framework 5,Project Reactor 和 SpringBoot 2.x 之上,使用非阻塞 API
  • SpringCloud Gateway 还支持 WebSocket,并且 Spring 紧密集成拥有更好的开发体验

什么是 WebFlux?

    传统的 Web 框架,比如说 Struts 2,SpringMVC 等都是基于 Servlet API 与 Servlet 容器基础上运行的。

    但是 在 Servlet 3.1 之后有了异步非阻塞的支持。而 WebFlux 是一个典型非阻塞异步的框架,它的核心是基于 Reactor 的相关 API 实现的。相对于传统的 Web 框架来说,它可以运行在诸如 Netty,Undertow 以及支持 Servlet 3.1 的容器上。非阻塞式 + 函数式编程(Spring 5 必须让你使用 Java 8)

    Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 SpringMVC,它不需要依赖 Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应流规范

三大核心概念

Route(路由)

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

Predicate(断言)

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

Filter(过滤)

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

总体

在这里插入图片描述
Web 请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。Predicate 就是我们的匹配条件

而 Filter,就可以理解为一个无所不能的拦截器,有了这两个元素,再加目标 URI,就可以实现一个具体的路由了

Gateway 工作流程

官网总结

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

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

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

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

核心逻辑

路由转发 + 执行过滤器链

入门配置

新建 Moudle

在这里插入图片描述

添加依赖

官网说明使用 Gateway 不需要添加 web 和 actuator 依赖,不然会报错

<dependencies>
   <!--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>
    <!--一般基础配置类-->
    <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>

编写 application.yml 文件

server:
  port: 9527

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

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

eureka:
  instance:
    hostname: demo-provider-payment
  client: # 服务提供者 provider注册进 eureka 服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

编写启动类

package com.java.springcloud;

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

/**
 * @author Woo_home
 * @create 2020/3/29 13:16
 */

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

测试访问

启动 EurekaServer 7001,启动服务提供者 8001,启动 Gateway 网关 9527

访问 http://eureka7001.com:7001/ 可以发现两个服务都已注册
在这里插入图片描述

访问 http://localhost:8001/payment/get/1,正常访问
在这里插入图片描述
访问 http://localhost:9527/payment/get/1 也可以正常访问 8001
在这里插入图片描述
为什么访问 http://localhost:9527/payment/get/1 也可以正常访问 8001 呢?因为我们在 9527 的配置文件中使用了 Predicate (断言),在 8001 的外层套了一层 9527
在这里插入图片描述

配置路由的两种方式

一种是上面的使用 application.yml 的方式

一种是使用 JavaConfig 的方式

package com.java.springcloud.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Woo_home
 * @create 2020/3/29 14:02
 */

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        RouteLocatorBuilder.Builder routes = builder.routes();
        // 定义 ID,可随意定义,不重复即可
        routes.route("path_route_demo",
        		// 定义外层访问规则
                r -> r.path("/Woo_home")
                		// 外层访问的真实地址
                        .uri("https://blog.csdn.net/Woo_home")).build();
        return routes.build();
    }
}

在这里插入图片描述

通过微服务名实现动态路由

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

上面的 application.yml 文件中,已经把 URI 的路径固定了,这种方式肯定是不可用的,所以需要修改一下,修改内容如下:

server:
  port: 9527

spring:
  application:
    name: demo-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true                     # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
      - id: payment_routh #payment_route    # 路由的ID,没有固定规则但要求唯一,建议配合服务名
        # uri: http://localhost:8001          # 匹配后提供服务的路由地址
        uri: lb://demo-provider-payment     # 匹配后提供服务的路由地址
        predicates:
        - Path=/payment/get/**              # 断言,路径相匹配的进行路由

      - id: payment_routh2 #payment_route   # 路由的ID,没有固定规则但要求唯一,建议配合服务名
        # uri: http://localhost:8001         # 匹配后提供服务的路由地址
        uri: lb://demo-provider-payment     # 匹配后提供服务的路由地址
        predicates:
        - Path=/payment/lb/**               # 断言,路径相匹配的进行路由
        #- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
        #- Cookie=username,zzyy
        #- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式

eureka:
  instance:
    hostname: demo-provider-payment
  client: # 服务提供者 provider 注册进 eureka 服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

启动 7001、8001、8002、9527,访问 http://localhost:9527/payment/lb,可以发现已经实现负载均衡
在这里插入图片描述
在这里插入图片描述

Predicate 的使用


根据上面可以知道每个路由包含 ID、URI、Predicate

还有当我们启动 Gateway 服务的时候,控制台会输出以下这样的内容:
在这里插入图片描述

RoutePredicateFactory

那么这个 RoutePredicateFactory 是什么呢?

SpringCloud Gateway 将路由匹配作为 Spring WebFlux HandlerMapping 基础架构的一部分

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

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

所有这些谓词都匹配 HTTP 请求的不同属性,多种谓词工厂可以组合,并通过逻辑 and

常用的 RoutePredicate

看下 RoutePredicateFactory 的常用实现类
在这里插入图片描述

AfterRoutePredicate

After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]

在这里插入图片描述

CookieRoutePredicate

Cookie=username,admin

CookieRoutePredicate 需要两个参数,一个是 Cookie name,一个是正则表达式

路由规则会通过获取对应的 Cookie name 的值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
在这里插入图片描述
在 application.yml 中配置 Cookie 后,访问时需要带上 Cookie,如:http://localhost:9527/payment/lb --cookie “username=admin” 才可以正常访问

HeaderRoutePredicate

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

两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配执行
在这里插入图片描述
访问时需要带上 header 属性,如:http://localhost:9527/payment/lb --H “X-Request-Id:-123”

Filter 的使用

Filter 是什么?

路由过滤器可用于修改进入的 HTTP 请求和返回 HTTP 响应,路由过滤器只能指定路由进行使用

SpringCloud Gateway 内置了多种路由过滤器,它们都由 GatewayFilter 的工厂类来产生

自定义过滤器

自定义一个全局过滤器

主要实现 GlobalFilter、Ordered 接口

package com.java.springcloud.filter;

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;

/**
 * @author Woo_home
 * @create 2020/3/29 17:14
 */

@Component
@Slf4j
public class GlobalFilterConfig implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("******** come in GlobalFilterConfig :" + new Date());
        // 请求的用户必须带上 username
        String uname = exchange.getRequest().getQueryParams().getFirst("username");
        if (uname == null) {
            log.info("****** 用户名为 null,非法用户");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

测试访问

http://localhost:9527/payment/lb?username=admin
在这里插入图片描述
在这里插入图片描述
如果 URL 后面不带上 username 访问,则会直接访问不了
在这里插入图片描述
控制台输出
在这里插入图片描述
或者是 username 写错了也访问不了
在这里插入图片描述


完整代码已上传至码云,感兴趣的朋友可以下载测试下 代码地址

发布了227 篇原创文章 · 获赞 1032 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/Woo_home/article/details/105174342