SpringCloud - GateWay服务网关

文章目录

  • 一. Gateway概述
    • 1.Gateway是什么
    • 2. Gateway作用
    • 3. 微服务架构中网关的位置
    • 4. SpringCloud Gateway概念
    • 5. SpringCloud Gateway IO 模型
  • 二.Gateway的三大核心概念
    • 1. Route 路由
    • 2. Predicate 断言
    • 3. Filter 过滤
    • 4. 总结
  • 三.Spring Cloud Gateway工作流程
  • 四.服务搭建
    • 1. 创建cloud-gateway-gateway-9527 模块
    • 2. 写pom
    • 3. 改yml
    • 4. 主启动
    • 5. 网关路由映射
    • 6. 测试
  • 五.路由配置的两种方式
    • 1. YML 配置
    • 2. 代码配置
  • 六.Gateway 动态路由
  • 七.Predicate 断言的使用
    • 1. 常用的断言
    • 2.小总结(pom)
  • 八.Filter 过滤器
    • 1. 什么是Filter
    • 2. 自定义过滤器
      • 2.1 实现两个接口
      • 2.2 作用
      • 2.3 演示代码

一. Gateway概述

1.Gateway是什么

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

在微服务架构中,一个系统往往由多个微服务组成,而这些服务可能部署在不同机房、不同地区、不同域名下。这种情况下,客户端(例如浏览器、手机、软件工具等)想要直接请求这些服务,就需要知道它们具体的地址信息,例如 IP 地址、端口号等。

这种客户端直接请求服务的方式存在以下问题:

  • 当服务数量众多时,客户端需要维护大量的服务地址,这对于客户端来说,是非常繁琐复杂的。
  • 在某些场景下可能会存在跨域请求的问题。
  • 身份认证的难度大,每个微服务需要独立认证。

2. Gateway作用

API 网关是一个搭建在客户端和微服务之间的服务,我们可以在 API 网关中处理一些非业务功能的逻辑,例如权限验证、监控、缓存、请求路由等。

API 网关就像整个微服务系统的门面一样,是系统对外的唯一入口。有了它,客户端会先将请求发送到 API 网关,然后由 API 网关根据请求的标识信息将请求转发到微服务实例。

对于服务数量众多、复杂度较高、规模比较大的系统来说,使用 API 网关具有以下好处:

  • 客户端通过 API 网关与微服务交互时,客户端只需要知道 API 网关地址即可,而不需要维护大量的服务地址,简化了客户端的开发。
  • 客户端直接与 API 网关通信,能够减少客户端与各个服务的交互次数。
  • 客户端与后端的服务耦合度降低。
  • 节省流量,提高性能,提升用户体验。
  • API 网关还提供了安全、流控、过滤、缓存、计费以及监控等 API 管理功能。

常见的 API 网关实现方案主要有以下 5 种:

  • Spring Cloud Gateway
  • Spring Cloud Netflix Zuul
  • Kong
  • Nginx+Lua
  • Traefik

3. 微服务架构中网关的位置

SpringCloud - GateWay服务网关_第1张图片

4. SpringCloud Gateway概念

SpringCloud Gateway 是在建立在 Spring 生态系统之上的 API 网关服务,基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,以及提供一些强大的过滤器功能,例如:安全,监控/指标、熔断、限流、重试等。

Spring Cloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。

5. SpringCloud Gateway IO 模型

Zuul 1.x 的 IO 模型:

SpringCloud 中所集成的 Zuul 1.x 版本,采用的是 Tomcat 容器,使用的是传统的 Servlet IO 处理模型。servlet 由 servlet container 进行生命周期管理:

  1. container 启动时构造 servlet 对象并调用 servlet.init() 进行初始化;
  2. container 关闭时调用 servlet.destory() 销毁 servlet;
  3. container 运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用 service()。

SpringCloud - GateWay服务网关_第2张图片

上述模式的缺点:servlet 是一个简单的网络 IO 模型,当请求进入 servlet container 时,servlet container 就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发,线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单的业务场景下,不希望为每个请求分配一个线程,只需要一个或几个线程就能应对极大并发的请求,这种业务场景下 servlet 模型没有优势。

所以 Zuul 1.x 是基于 servlet 阻塞 IO 模型的 API 网关,即 Spring 实现了处理所有 request 请求的一个 servlet(DispatcherServlet),并由该 servlet 阻塞式处理处理。每次 I/О 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成。所以 Zuul 1.x 无法摆脱 servlet 模型的弊端。

虽然 Zuul 2.0 开始,使用了 Netty 非阻塞和支持长连接,并且已经有了大规模 Zuul 2.0 集群部署的成熟案例,但是,SpringCloud 官方已经没有集成该版本的计划了。

GateWay 非阻塞异步模型

传统的Web框架,比如说:Struts2,SpringMVC 等都是基于 Servlet APl 与 Servlet 容器基础之上运行的。

但是在 Servlet3.1 之后有了异步非阻塞的支持。而 WebFlux 是一个典型非阻塞异步的框架,它的核心是基于 Reactor 的相关 API 实现的。Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖 Servlet APl,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。

SpringCloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。

二.Gateway的三大核心概念

1. Route 路由

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

2. Predicate 断言

路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务

3. Filter 过滤

过滤器,我们可以使用它对请求进行拦截和修改,还可以使用它对上文的响应进行再处理。

4. 总结

  • web 请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化控制
  • predicate 就是我们的匹配条件
  • filter:就可以理解为一个无所不能的拦截器,有了这两个元素,再加上目标的uri,就可以实现一个具体的路由了。

SpringCloud - GateWay服务网关_第3张图片

三.Spring Cloud Gateway工作流程

SpringCloud - GateWay服务网关_第4张图片

Spring Cloud Gateway 工作流程说明如下:

  1. 客户端将请求发送到 Spring Cloud Gateway 上。
  2. Spring Cloud Gateway 通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler
  3. Gateway Web Handler 通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑,返回响应结果返回给客户端。
  4. 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
  5. 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等。
  6. 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。

总而言之,客户端发送到 Spring Cloud Gateway 的请求需要通过一定的匹配条件,才能定位到真正的服务节点。在将请求转发到服务进行处理的过程前后(pre 和 post),我们还可以对请求和响应进行一些精细化控制。

Predicate 就是路由的匹配条件,而 Filter 就是对请求和响应进行精细化控制的工具。有了这两个元素,再加上目标 URI,就可以实现一个具体的路由了。

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

四.服务搭建

1. 创建cloud-gateway-gateway-9527 模块

2. 写pom

做网关不需要添加 web starter


<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2022artifactId>
        <groupId>com.jm.springcloudgroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>cloud-gateway-gateway9527artifactId>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
        
        <dependency>
            <groupId>com.jm.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>${project.version}version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>


project>

3. 改yml

server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

4. 主启动

package com.jm.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);
     }

}

5. 网关路由映射

我们目前不想暴露8001端口,希望在8001外面套一层9527

YML新增网关配置

server:
  port: 9527

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

        - id: payment_routh2
          uri:  http://localhost:8001
          predicates:
            - Path=/payment/lb/**
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

6. 测试

启动网关前访问:http://localhost:8001/get/payment/31
启动网关后访问:http://localhost:9527/get/payment/31

五.路由配置的两种方式

1. YML 配置

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:  #网关路由配置
      routes:
        - id: payment_routh    #路由的ID,没有固定规则但要求唯一,建议与服务名对应
          uri: http://localhost:8001  #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/getPaymentById/**    #断言,路径相匹配的进行路由
            - Method=GET 	#只能时GET请求时,才能访问
        - id: payment_routh2   #路由的ID,没有固定规则但要求唯一,建议与服务名对应
          uri: http://localhost:8001  #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由

测试地址:http://localhost:9527/payment/get/31

2. 代码配置

当请求路径为 /guonei时,转发到 https://news.baidu.com/guonei

@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_route",
                r -> r.path("/guonei")
                        .uri("https://news.baidu.com/guonei"));
        return routes.build();
    }
}

测试:http://localhost:9527/guonei

六.Gateway 动态路由

相当于给网关配置一个负载均衡,因为看上面的配置把8001写死了

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

开启动态路由:spring.cloud.gateway.discovery.locator.enabled:true;

在添加uri的时候,开头是 lb://微服务名,lb: 负载均衡协议

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

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

测试:

因为开启了8001和8002两个端口,所以网关负载均衡的效果是 8001/8002切换

七.Predicate 断言的使用

Spring Cloud Gateway 通过 Predicate 断言来实现 Route 路由的匹配规则。简单点说,Predicate 是路由转发的判断条件,请求只有满足了 Predicate 的条件,才会被转发到指定的服务上进行处理。

使用 Predicate 断言需要注意以下 3 点:

  • Route 路由与 Predicate 断言的对应关系为“一对多”,一个路由可以包含多个不同断言。
  • 一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言。
  • 当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。

参考gateway官网的断言的例子:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-after-route-predicate-factory

1. 常用的断言

常用的Route Predicate

After Route Predicate

  • - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
  • 匹配该断言时间之后的 uri请求

Before Route Predicate

  • - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
    - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]

Between Route Predicate

  • - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]

Cookie Route Predicate

不带cookies访问

带上cookies访问 - Cookie=username,atguigu #并且Cookie是username=atguigu 才能访问

Cookie Route Predicate 需要两个参数,一个时Cookie name,一个是正则表达式。

路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上就不执行

Header Route Predicate
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行;

Host Route Predicate

  • - Host=**.atguigu.com

Method Route Predicate

  • - Method=GET

Path Route Predicate

== Query Route Predicate==

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

2.小总结(pom)

server:
  port: 9527

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

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service     #lb:负载均衡协议
          #filters:
          #  - AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
          predicates:
            - Path=/payment/lb/**             # 断言,路径相匹配的进行路由
            #- After=2022-11-26T19:14:10.785+08:00[Asia/Shanghai]
            #- Before=2022-11-26T19:22:29.119+08:00[Asia/Shanghai]
            #- Between=2022-11-26T19:14:10.785+08:00[Asia/Shanghai], 2022-11-26T19:22:29.119+08:00[Asia/Shanghai]
            #- Cookie=username,zzyy
            #- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式
            #- Host=**.atguigu.com
            #- Method=GET
            #- Query=username, \d+  # 要有参数名username并且值还要是整数才能路由

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

八.Filter 过滤器

1. 什么是Filter

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

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

官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories

声明周期 :

  • pre 在业务逻辑之前
  • post 在业务逻辑之后

种类:

  • 单一的:GatewayFilter
  • 全局的:GlobalFilter

2. 自定义过滤器

2.1 实现两个接口

implements GlobalFilterOrdered

2.2 作用

全局日志记录
统一网关鉴权

2.3 演示代码

package com.jm.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;

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

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

你可能感兴趣的:(Spring全家桶,spring,cloud,微服务,gateway)