09、SpringCloud之Gateway网关组件学习笔记

文章目录

  • 前言
  • 一、背景
    • 1.1、网关的背景
    • 1.2、是否有网关的对比
    • 1.3、网关的技术实现
  • 二、认识Springcloud Gateway
    • 2.1、简介
    • 2.2、gateway的三大核心概念
    • 2.3、gateway的工作流程
    • 2.4、实际应用的服务架构
    • 2.5、Nginx与Gateway的区别
  • 三、实战1:搭建SpringCloud Gateway服务
    • 3.1、搭建基础Gateway服务,实现路由转发(暂无注册中心)
    • 3.2、Gateway的两种路由配置方式(暂无注册中心)
      • 方式一:代码路由方式
      • 方式二:yaml配置方式
    • 3.3、实现动态路由(搭配注册中心)
      • 配置过程
      • 测试动态路由
    • 3.4、非动态路由的配置实现动态路由效果
  • 四、实战2:搭建Gateway集群
    • 4.1、搭建详细过程
    • 4.2、测试集群
  • 五、Predicate断言工厂
    • 5.1、认识断言
    • 5.2、断言详细配置
    • 5.3、实战3:配置一个After断言
    • 5.4、自定义断言器
  • 六、Filter过滤器工厂
    • 6.1、介绍
    • 6.2、自定义全局过滤器
    • 6.3:实战4:实现一个ip拦截的过滤器
    • 6.4、实战5:在网关中实现token认证校验
      • login-service(增加登录接口)
      • user-service模块(新增,添加一个对外界接口)
      • gateway-server模块(添加token认证过滤器)
      • 测试
  • 七、实战系列
    • 7.1、实战6:实现请求限流
      • 7.1.1、认识限流
      • 7.1.2、限流模型
      • 7.1.3、Gateway 结合 redis 实现请求量限流(Gateway内置限流令牌桶实现)
    • 7.2、实战7:Gateway集成跨域配置
  • 参考文章

前言

本节配套案例代码:Gitee仓库、Github仓库

所有博客文件目录索引:博客目录索引(持续更新)

学习视频:动力节点最新SpringCloud视频教程|最适合自学的springcloud+springcloudAlibaba

PS:本章节中部分图片是直接引用学习课程课件,如有侵权,请联系删除。

一、背景

1.1、网关的背景

在分布式微服务架构中,某个服务可以会有多个实例来去注册到注册中心,那么如何去调用如此多个的服务也成为了一个比较大的问题。

此时客户端去调用服务就会出现以下问题:①客户端访问地址配置问题。②多个服务的认证授权问题,造成鉴权认证功能重复冗余情况。③服务访问量大造成的重构问题。

如何解决上面的问题呢?微服务引入了 网关 的概念,网关为微服务架构的系统提供简单、有效且统一的API路由管理,作为系统的统一入口,提供内部服务的路由中转,给客户端提供统一的服务,可以实现一些和业务没有耦合的公用逻辑,主要功能包含认证、鉴权、路由转发、安全策略、防刷、流量控制、监控日志等。

原本的对应服务客户端去直接调服务实例接口转变为统一走gateway网关来进行服务转发:

09、SpringCloud之Gateway网关组件学习笔记_第1张图片

1.2、是否有网关的对比

没有网关:客户端直接访问我们的微服务,会需要在客户端配置很多的 ip:port,如果 user-service 并发比较大,则无法完成负载均衡。

  • 试想:若是某个服务实例采用集群,那么我们在进行负载均衡配置时难道也要一一配置真实的服务地址嘛,那这就会出现很大的人力问题。

有网关:客户端访问网关,网关来访问微服务,(网关可以和注册中心整合,通过服务名称找到目标的 ip:prot)这样只需要使用服务名称即可访问微服务,可以实现负载均衡,可 以实现 token 拦截,权限验证,限流等操作 。

  • 好处:使用网关呢,我们就不需要在nginx负载均衡中配置大量的服务实例地址,而是只需要配置gateway网关的集群地址,某个请求通过nginx走到网关,接着在网关进行服务发现+负载均衡去访问某个服务的实例。

1.3、网关的技术实现

本章节介绍其中的Gateway:是 Spring Cloud 官方提供的用来取代 zuul(netflix)的新一代网关组件

Zuul 1.0 : Netflix开源的网关,使用Java开发,基于Servlet架构构建,本质就是 web 组件 web 三大组件(监听器 过滤器 servlet)便于二次开发。因为基于Servlet内部延迟严重,并发场景不友好,一个线程只能处理一次连接请求。

  • 性能:使用的是 BIO(Blocking IO) tomcat7.0 以前都是 BIO 性能一般。

Zuul 2.0 : 采用Netty实现异步非阻塞编程模型,一个CPU一个线程,能够处理所有的请求和响应,请求响应的生命周期通过事件和回调进行处理,减少线程数量,开销较小。

  • 性能:性能好采用的是NIO,AIO 异步非阻塞,基于 spring5.x,springboot2.x 和 ProjectReactor 等技术。

GateWay : 是Spring Cloud的一个全新的API网关项目,替换Zuul开发的网关服务,基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能高于Zuul

Nginx+lua : 性能要比上面的强很多,使用Nginx的反向代码和负载均衡实现对API服务器的负载均衡以及高可用,lua作为一款脚本语言,可以编写一些简单的逻辑,但是无法嵌入到微服务架构中。

Kong : 基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,性能高效且稳定,支持多个可用插件(限流、鉴权)等,开箱即可用,只支持HTTP协议,且二次开发扩展难,缺乏更易用的管理和配置方式


二、认识Springcloud Gateway

2.1、简介

Spring Cloud Gateway 是Spring Cloud的一个全新的API网关项目,目的是为了替换掉Zuul1。

技术选型:基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发。

性能方面:性能⾼于Zuul,官⽅测试,Spring Cloud GateWay是Zuul的1.6倍 ,旨在为微服务架构提供⼀种简单有效的统⼀的API路由管理⽅式。

特点:Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。

  • 比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。
  • 比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。

2.2、gateway的三大核心概念

三个核心:路由、断言、过滤器

路由(Route)能够与注册中心来结合作动态路由。是GateWay中最基本的组件之一,表示一个具体的路由信息载体,主要有一个ID、一个目标URI、一组断言和一组过滤器来定义,具体如下:

  1. id:路由唯一标识,区别于其他的route。
  2. url: 路由指向的目的地URL,客户端请求最终被转发到的微服务。
  3. order: 用于多个Route之间的排序,数值越小越靠前,匹配优先级越高。
  4. predicate:断言的作用是进行条件判断,只有断言为true,才执行路由。
  5. filter: 过滤器用于修改请求和响应信息。

断言(Predicate)返回一个bool类型,用于表示在不同状态情况下,该请求是否符合要求。输入的类型是一个ServerWebExchange,可以使用其来匹配HTTP请求的任何内容,例如headers、cookie等等。

过滤器(filter):在gateway中分为两种类型filter:①Gateway filter。②Global filter。

  • 效果:能够对请求和响应进行修改处理。
  • 两种路由各自用途:
    • ①:一个是针对某一个路由(路径)的 filter,例如对某一个接口做限流。
    • ②:一个是针对全局的 filter token ip 黑名单。

2.3、gateway的工作流程

09、SpringCloud之Gateway网关组件学习笔记_第2张图片

执行流程如下

1、Gateway ClientSpring Cloud Gateway 发送请求,请求首先会被 HttpWebHandlerAdapter 进行提取组装成网关上下文。

3、此时网关的上下文会传递到 DispatcherHandler ,它负责将请求分发给 RoutePredicateHandlerMapping

4、RoutePredicateHandlerMapping 负责路由查找,并根据路由断言判断路由是否可用。

5、如果过断言成功,由 FilteringWebHandler 创建过滤器链并调用。

6、通过特定于请求的 Fliter 链运行请求,Filter 被虚线分隔的原因是Filter可以在发送代理请求之前(pre)和之后(post)运行逻辑。

7、执行所有pre过滤器逻辑。然后进行代理请求。发出代理请求后,将运行“post”过滤器逻辑。

8、处理完毕之后将 Response 返回到 Gateway 客户端。

针对于代理请求pre与post的分别应用场景

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

2.4、实际应用的服务架构

若是想要我们的网关达到高可用,那么就需要进行部署集群,并在gateway网关上层配置一个负载均衡层:

09、SpringCloud之Gateway网关组件学习笔记_第3张图片

2.5、Nginx与Gateway的区别

Nginx:在做路由,负载均衡,限流之前,都有修改 nginx.conf 的配置文件,把需要负载均衡,路由,限流的规则加在里面。

  • 需要手动配置。

gateway :gateway 自动的负载均衡和路由,gateway 和 eureka 高度集成,实现自动的路由,和 Ribbon 结合,实现了负载均衡(

lb),gateway 也能轻易的实现限流和权限验证。

区别

1、Nginx需要去自行配置路由、负载均衡等规则;gateway有动态路由,可与注册中心结合使用。

2、Nginx(c语言)比gateway(gateway)的性能高一点。

3、Nginx是服务器级别的;Gateway是项目级别的。

比较合适的搭配效果如下

09、SpringCloud之Gateway网关组件学习笔记_第4张图片


三、实战1:搭建SpringCloud Gateway服务

项目版本:SpringBoot 2.3.12.RELEASESpringCloud Hoxton.SR12

3.1、搭建基础Gateway服务,实现路由转发(暂无注册中心)

本小节只需要使用一个gateway与一个服务实例即可。

09、SpringCloud之Gateway网关组件学习笔记_第5张图片

实现目标:网关实现路由转发的效果。(之后小结会进行动态路由的实现)

login-service:登录模块服务

09、SpringCloud之Gateway网关组件学习笔记_第6张图片

当前该服务仅仅只要引入web模块即可,当前还没有涉及到注册中心。

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

controller/LoginController.java

package com.changlu.loginservice.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * @Description:
 * @Author: changlu
 * @Date: 8:20 PM
 */
@RestController
public class LoginController {

    @GetMapping("/doLogin")
    public String doLogin() {
        return UUID.randomUUID().toString();
    }

}

gateway-service:网关服务

依赖配置:对应springboot、springcloud版本号在三章节下有说明

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-gatewayartifactId>
dependency>

09、SpringCloud之Gateway网关组件学习笔记_第7张图片

配置文件:application.yaml

server:
  port: ${SERVER_PORT:81}   # 默认是81端口,可以通过命令行读取参数 -DSERVER_PORT=82

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true   # 默认开启,只要加了网关依赖
      routes:
        - id: login-service-route  # 路由id,保持唯一
          uri: http://localhost:8081  # uri
          predicates:
            - Path=/doLogin  # 匹配路径规则 只要你Path匹配上了/doLogin 就往 uri 转发 并且将路径带上
  • 实现:①开启网关(其实是默认开启的)。②编写路由。(若是要进行转发我们只需要配置好id、uri以及断言匹配路径)
  • id:表示的该组路由的id,需要是唯一的。
  • uri:表示匹配到的路由进行转发的地址。
  • predicates:当前是进行一个路径匹配的uri接口。

目前的话我们无需编写任何代码,就可以使用一个路由转发的效果,接下来我们来进行测试一下!

测试:访问路径http://localhost:81/doLogin,即可在gateway中进行转发到我们的login-service服务实例上。

09、SpringCloud之Gateway网关组件学习笔记_第8张图片

注意:当前仅仅是路由转发指定的地址,并没有去注册中心拉取服务实例进行访问!


3.2、Gateway的两种路由配置方式(暂无注册中心)

方式一:代码路由方式

可参考官方案例:https://spring.io/projects/spring-cloud-gateway#overview

实现目标:路由转发到百度一下

09、SpringCloud之Gateway网关组件学习笔记_第9张图片

可以看到百度的路由地址是:https://www.baidu.com/s?wd=123

思路:将/s即之后的内容转发到指定的百度网址,实际上与3.1中配置的大体参数类似

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

/**
 * @Description:
 * @Author: changlu
 * @Date: 1:15 PM
 */
@Configuration
public class RouteConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                //路由
                .route("baidu_route", r -> r.path("/s").uri("https://www.baidu.com/"))
                .build();
    }

}

ok,此时我们来测试一下:http://localhost:81/s?wd=123

09、SpringCloud之Gateway网关组件学习笔记_第10张图片


方式二:yaml配置方式

实际上就是3.1中进行配置的内容:

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true   # 默认开启,只要加了网关依赖
      routes:
        - id: login-service-route  # 路由id,保持唯一
          uri: http://localhost:8081  # uri
          predicates:
            - Path=/doLogin  # 匹配路径规则 只要你Path匹配上了/doLogin 就往 uri 转发 并且将路径带上
        - id: user-service-route  # 路由id,保持唯一
          uri: http://localhost:8082  # uri
          predicates:
            - Path=/info/**  # 匹配路径规则 只要你Path匹配上了/doLogin 就往 uri 转发 并且将路径带上

对于下面的/info/,就是会匹配所有前缀为/info/*的内容

例如访问http://localhost:81/info/doLogin,实际上就会进行转发http://localhost:8081/info/doLogin


3.3、实现动态路由(搭配注册中心)

配置过程

之前3.1中案例并没有搭配注册中心,我们就以3.1中的案例来进行集成实现!

同样是对前两个进行改造:

09、SpringCloud之Gateway网关组件学习笔记_第11张图片

两个服务都添加如下依赖及配置:

①引入依赖:

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>

②配置application.yaml添加eureka连接信息:

# 配置eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

③开启服务发现:在启动器类上添加

@EnableEurekaClient  //开启服务注册

最后在``gateway-server`服务的yaml配置里进行开启动态路由:

09、SpringCloud之Gateway网关组件学习笔记_第12张图片

# 服务发现相关配置
discovery:
    locator:
        enabled: true   # 开启动态路由  开启通用应用名称 找到服务的功能
        lower-case-service-id: true  # 开启服务名称小写,因为在eureka中默认服务名是大写的

当前我们已经实现了动态路由了,完全就只需要进行配置即可!

测试动态路由

我们来启动网关、生产者服务以及eureka注册中心:

09、SpringCloud之Gateway网关组件学习笔记_第13张图片

  • 注册中心的代码案例使用之前博文中的eureka案例:09、SpringCloud之Gateway网关组件学习笔记_第14张图片

09、SpringCloud之Gateway网关组件学习笔记_第15张图片

在对login-servicegateway-server都开启了服务注册之后,以及开启了gateway-server的动态路由,我们就可以来实现根据服务名调用指定注册中心中的服务实例了。

再此之前我们进行路由代码配置访问的是:http://localhost:81/doLogin

针对于动态路由,我们在/doLogin前添加一个服务名,例如在eureka中心中login-service注册的服务名为login-service:http://localhost:81/login-service/doLogin

测试一下:ok能够进行测试访问

09、SpringCloud之Gateway网关组件学习笔记_第16张图片

3.4、非动态路由的配置实现动态路由效果

之前3.1、3.2节是我们进行静态绑定的,那么我们如何来实现静态绑定的方式来达到动态路由的效果呢?

那么我们只需要对对应uri来进行操作即可:

09、SpringCloud之Gateway网关组件学习笔记_第17张图片

将原本指明服务ip地址以及port端口的更改为负载均衡协议lb://服务名

#          uri: http://localhost:8081  # uri
           uri: lb://login-service   # 实现负载均衡

测试一下

09、SpringCloud之Gateway网关组件学习笔记_第18张图片

没得问题!

四、实战2:搭建Gateway集群

4.1、搭建详细过程

目标效果:访问nginx,通过nginx来进行负载均衡转发请求到集群gateway中,此时gateway里同样去搭配注册中心进行服务发现来进行负载均衡访问服务实例!下面就开始吧。

准备:nginx服务器+gateway两个不同端口(实际上是不同ip地址)服务+生产者服务实例。

生产者服务实例:使用的是login-service,也就是3.1节中的对外提供了一个接口。

gateway服务实例准备

直接使用的是3.1章节中的gateway,如何创建不同端口的多实例呢?

我们针对yaml配置文件来编写可接收命令传参:

server:
  port: ${SERVER_PORT:81}   # 默认是81端口,可以通过命令行读取参数 -DSERVER_PORT=82

image-20220729114057073

09、SpringCloud之Gateway网关组件学习笔记_第19张图片

-DSERVER_PORT=82

接着我们来进行启动服务实例:

09、SpringCloud之Gateway网关组件学习笔记_第20张图片

nginx配置

下载地址:http://nginx.org/en/download.html

09、SpringCloud之Gateway网关组件学习笔记_第21张图片

我们打开nginx文件夹中的nginx.conf来进行编辑:

09、SpringCloud之Gateway网关组件学习笔记_第22张图片

  • 第一个框+第二个框:主要目的是为了能够查看到访问请求进行负载均衡的一个日志情况。
  • 第三个框+第四个框时进行负载均衡配置。

配置如下:

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"'
              '$connection $upstream_addr '
              'upstream_response_time $upstream_response_time request_time $request_time ';

access_log  logs/access.log  main;

upstream www.gateway.com {
    server localhost:81;
    server localhost:82;
}

server {
    location / {
        # root   html;
        # index  index.html index.htm;
        # 代理服务地址
        proxy_pass http://www.gateway.com;
    }
}

nginx相关的启动、关闭命令:

start nginx # 启动,下载好之后到指定目录下执行启动命令即可
nginx -s quit  # 关闭nginx
nginx -s reload  # 优雅重启

# 若是想要停止服务还可以使用这个命令或者使用任务管理器找到nginx.exe来关闭
taskkill /IM nginx.exe /F   # 关闭所有正在启动的nginx服务

4.2、测试集群

接下来我们来访问nginx的80端口:http://localhost/doLogin

09、SpringCloud之Gateway网关组件学习笔记_第23张图片

09、SpringCloud之Gateway网关组件学习笔记_第24张图片

那么我们如何来看负载均衡访问服务器的地址呢?

查看nginx的日志:``access.log`即可

可以看到默认的nginx负载均衡是轮训:

09、SpringCloud之Gateway网关组件学习笔记_第25张图片

注意:默认的日志打印内容是没有访问服务器的地址的,在前面nginx中我是有进行自主配置加上打印的服务器地址这里才会显示的。

五、Predicate断言工厂

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

5.1、认识断言

在 gateway 启动时会去加载一些路由断言工厂**(判断一句话是否正确 一个 boolean 表达式** ) ,例如我们3.1中搭建的案例在启动时就会出现如下的一些断言信息:

09、SpringCloud之Gateway网关组件学习笔记_第26张图片

本质:满足条件的返回true放行,不满足的false进行拦截。

介绍:Spring Cloud Gateway 将路由作为 Spring WebFlux HandlerMapping 基础架构的一部分进行匹配。Spring Cloud Gateway 包括许多内置的路由断言工厂。所有这些断言都与 HTTP 请求的不同属性匹配。您可以将多个路由断言可以组合使用。

源码:Spring Cloud Gateway 创建对象时,使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。

09、SpringCloud之Gateway网关组件学习笔记_第27张图片

09、SpringCloud之Gateway网关组件学习笔记_第28张图片

5.2、断言详细配置

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true   # 默认开启,只要加了网关依赖
      routes:
        - id: login-service-route  # 路由id,保持唯一
#          uri: http://localhost:8081  # uri
          uri: lb://login-service   # 实现负载均衡
          predicates:
            - Path=/doLogin  # 匹配路径规则 只要你Path匹配上了/doLogin 就往 uri 转发 并且将路径带上
            - After=2020-01-20T17:42:47.789-07:00[Asia/Shanghai] #此断言匹配发生在指定 日期时间之后的请求,ZonedDateTime dateTime=ZonedDateTime.now()获得
            - Before=2020-06-18T21:26:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定 日期时间之前的请求
            - Between=2020-06-18T21:26:26.711+08:00[Asia/Shanghai],2020-06-18T21:32:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之间的请求
            - Cookie=name,xiaobai #Cookie 路由断言工厂接受两个参数,Cookie 名称和 regexp(一 个 Java 正则表达式)。此断言匹配具有给定名称且其值与正则表达式匹配的 cookie
            - Header=token,123456 #头路由断言工厂接受两个参数,头名称和 regexp(一个 Java 正 则表达式)。此断言与具有给定名称的头匹配,该头的值与正则表达式匹配。
            - Host=**.bai*.com:* #主机路由断言工厂接受一个参数:主机名模式列表。该模式是一 个 ant 样式的模式。作为分隔符。此断言匹配与模式匹配的主机头
            - Method=GET,POST #方法路由断言工厂接受一个方法参数,该参数是一个或多个参数: 要匹配的 HTTP 方法
            - Query=username,cxs #查询路由断言工厂接受两个参数:一个必需的 param 和一个 可选的 regexp(一个 Java 正则表达式)。
            - RemoteAddr=192.168.1.1/24 #RemoteAddr 路由断言工厂接受一个源列表(最小大小 1), 这些源是 cidr 符号(IPv4 或 IPv6)字符串,比如 192.168.1.1/24(其中 192.168.1.1 是 IP 地址,24 是子网掩码)。

其他额外的包含有权重属性:下面

  • 80%的请求,由 https://weighthigh.org 这个 url 去处理
  • 20%的请求由 https://weightlow.org 去处理
spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true  
      routes:
        - id: weight_high
          uri: https://weighthigh.org
          predicates:
            - Weight=group1, 2  # 权重
        - id: weight_low
          uri: https://weightlow.org
          predicates:
            - Weight=group1, 8 # 权重

5.3、实战3:配置一个After断言

效果:指定某个接口只能在指定时间后才能够进行访问,否则无法访问,报出404异常。

配置内容如下:- After=2022-07-29T17:23:21.719+08:00[Asia/Shanghai]

09、SpringCloud之Gateway网关组件学习笔记_第29张图片

image-20220729163444743

可以看到当前时间是四点多,配置After是五点多,也就是说这个接口在五点多才能够访问,再此之前不能够访问!

测试效果

09、SpringCloud之Gateway网关组件学习笔记_第30张图片

5.4、自定义断言器

自定义步骤

09、SpringCloud之Gateway网关组件学习笔记_第31张图片

1、编写一个断言工厂类:注意工厂类的名字尽量为xxxRoutePredicateFactory,因为之后配置文件要进行配置

package com.changlu.config;


import lombok.Data;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.function.Predicate;

/**
 * @Description:
 * @Author: changlu
 * @Date: 4:38 PM
 */
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {

    public CheckAuthRoutePredicateFactory() {
        super(Config.class);
    }

    //此时Config也就是自定义配置的一些参数
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            System.out.println("当前进入到CheckAuthRoutePredicateFactory:" + config.getName());
            return config.getName().equals("changlu");
        };
    }

    @Data
    static class Config {
        private String name;
    }
}

2、yaml来进行配置

09、SpringCloud之Gateway网关组件学习笔记_第32张图片

  • -name表示的是工厂名称。
  • args.name:其中的name就是工厂参数。
- name: CheckAuth #自定义路由断言工厂的名称xxxRoutePredicateFactory,这个xxx就是在这里指明
    args:
    name: changlu1  #传入到自定义路由断言工厂的参数

测试

果然由于配置文件的name与在断言方法类中的值不一致,此时该接口就访问不到了

09、SpringCloud之Gateway网关组件学习笔记_第33张图片

将name修改为changlu,再次尝试一下:

09、SpringCloud之Gateway网关组件学习笔记_第34张图片

六、Filter过滤器工厂

6.1、介绍

介绍:gateway 里面的过滤器和 Servlet 里面的过滤器,功能差不多,路由过滤器可以用于修改进入Http 请求和返回Http响应

分类

按照生命周期:pre(在业务逻辑前)、post(在业务逻辑后)。

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

按照种类区别:路由过滤器(某个路由单独使用)、全局过滤器(所有路由)。

  • 路由过滤器(GatewayFilter):需要配置某个路由,才能过滤。如果需要使用全局路由,需要配置 Default。
  • 全局过滤器(GlobalFilter):不需要配置路由,系统初始化作用到所有路由上 。

官网

官网-gatewayfilter-factories:包含31中单一路由过滤器。

官网-global-filters:包含9种全局路由过滤器。


6.2、自定义全局过滤器

自定义过程

我们基于之前的3.1案例的gateway-server来进行自定义。

09、SpringCloud之Gateway网关组件学习笔记_第35张图片

自定义全局filter实现了一个GlobalFilter(过滤方法)、Ordered(执行顺序):

package com.changlu.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashMap;

/**
 * @Description: 自定义全局过滤器
 * @Author: changlu
 * @Date: 9:17 PM
 */
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求体以及请求对象
        ServerHttpRequest request = exchange.getRequest();
        // HttpServletRequest  这个是web里面的
        // ServerHttpRequest  webFlux里面 响应式里面的
        ServerHttpResponse response = exchange.getResponse();
        //通过请求对象可以拿到请求的一系列内容
        String path = request.getURI().getPath();//uri路径
        System.out.println("path:" + path);
        HttpHeaders headers = request.getHeaders();//请求头
        System.out.println("headers:" + headers);
        String name = request.getMethod().name();//请求方法名,也就是对应ip:port/xxx,这个/xxx
        System.out.println("method name:"+ name);
        String ip = request.getHeaders().getHost().getHostString();//获取到ip主机名
        System.out.println("ip:" + ip);

        //来进行测试响应数据
        // 用了微服务 肯定是前后端分离的 前后端分离 一般前后通过 json
        // {"code":200,"msg":"ok"}
        //1、设置响应头
        response.getHeaders().set("content-type", "application/json;charset=utf-8");
        //2、响应结果集封装
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", HttpStatus.UNAUTHORIZED.value());
        result.put("msg", "暂未授权");
        ObjectMapper objectMapper = new ObjectMapper();//jackson工具类
        byte[] data = new byte[0];
        try {
            data = objectMapper.writeValueAsBytes(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //通过使用buffer工厂类来将其转为一个数据包(底层是基于netty,该对象底层是nio的bytebuffer)
        DataBuffer wrap = response.bufferFactory().wrap(data);
//        return response.writeWith(Mono.just(wrap));
        //放行过滤器
        return chain.filter(exchange);
    }

    //越小优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

其中放行是执行chain的方法:

return chain.filter(exchange);

若是直接拦截结束,则是对response进行写数据:

//这个wrap包装成netty中的DataBuffer
return response.writeWith(Mono.just(wrap));

测试

直接gateway中进行拦截响应

09、SpringCloud之Gateway网关组件学习笔记_第36张图片

09、SpringCloud之Gateway网关组件学习笔记_第37张图片

放行效果

09、SpringCloud之Gateway网关组件学习笔记_第38张图片

此时可以直接访问对应login-service接口:

09、SpringCloud之Gateway网关组件学习笔记_第39张图片

该过滤器打印的一些request请求对象的信息:

image-20220730104606411


6.3:实战4:实现一个ip拦截的过滤器

思路:同样也是在Gateway网关中添加一个全局过滤器组件。

09、SpringCloud之Gateway网关组件学习笔记_第40张图片

package com.changlu.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description: IP检查过滤器
 * @Author: changlu
 * @Date: 8:41 AM
 */
@Component
public class IPCheckFilter implements GlobalFilter, Ordered {

    /**
     * 网关的并发比较高 不要再网关里面直接操作mysql
     * 后台系统可以查询数据库 用户量 并发量不大
     * 如果并发量大 可以查redis 或者 在内存中写好
     */
    private static final List<String> BLACK_LIST = Arrays.asList("127.0.0.1", "192.168.1.1");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取到请求对象化
        ServerHttpRequest request = exchange.getRequest();
        String ip = request.getHeaders().getHost().getHostString();
        //若是在集合中出现该ip,那么此时就拦截响应(一般黑名单可以存储在数据库中也可以存储的redis里)
        if (!BLACK_LIST.contains(ip)) {
            chain.filter(exchange);
        }
        //若是存在就进行拦截,并响应
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("content-type", "application/json;charset=utf-8");
        Map<String, Object> result = new HashMap<>();
        result.put("code", 438);
        result.put("msg", "你已被拉黑,无法访问");
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] data = new byte[0];
        try {
            data = objectMapper.writeValueAsBytes(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer wrap = response.bufferFactory().wrap(data);
        return response.writeWith(Mono.just(wrap));
    }

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

测试一下

可以看到localhost是在拦截范围内的,所以gateway会进行拦截响应:

09、SpringCloud之Gateway网关组件学习笔记_第41张图片

09、SpringCloud之Gateway网关组件学习笔记_第42张图片


6.4、实战5:在网关中实现token认证校验

在实战5中,我们完成的就是下图的第7步骤,也就是token进行认证校验是否合法来进行放行或直接响应!

09、SpringCloud之Gateway网关组件学习笔记_第43张图片

说明:本章节的话会在login-service中完善doLogin接口,接着在gateway服务里添加一个认证token过滤器,并新建一个user-service并在其中添加一个接口对外使用。

注意:本章节的重点是在gateway中实现token认证来达到放行or错误响应,并不是在登录接口存储用户信息这些细节上,对于token生成、校验以及用户认证都仅仅只是做了简单的实现。

login-service(增加登录接口)

09、SpringCloud之Gateway网关组件学习笔记_第44张图片

domain/user.java

package com.changlu.loginservice.domain;

import lombok.Data;

import java.io.Serializable;

/**
 * @Description: 用户实体类
 * @Author: changlu
 * @Date: 9:20 AM
 */
@Data
public class User implements Serializable {
    private String username;
    private String password;
}

1、硬编码指定一个token。

private static final String token = "700d7a8d-262a-447a-8254-9dd9ead6a0e2";

2、添加一个doLogin接口,来用于获取token。

@PostMapping("/doLogin")
public String doLogin(@RequestBody User user) {
    System.out.println("dologin进行登录:" + user);
    //数据库进行认证,这里的话直接返回一个token
    return token;
}

user-service模块(新增,添加一个对外界接口)

说明:该模块主要是用于测试之后携带token的接口是否能够通过gateway认证并进行转发。

09、SpringCloud之Gateway网关组件学习笔记_第45张图片

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.3.12.RELEASEversion>
    <relativePath/> 
parent>

<properties>
    <java.version>1.8java.version>
    <spring-cloud.version>Hoxton.SR12spring-cloud.version>
properties>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>

配置文件:application.yaml

server:
  port: 8082
spring:
  application:
    name: user-service

# 注册目标
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

提供一个用户接口:仅仅是进行简单的用户返回。

package com.changlu.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description:
 * @Author: changlu
 * @Date: 9:16 AM
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping
    public Map<String, Object> getUser() {
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "成功获取到用户信息");
        return result;
    }

}

gateway-server模块(添加token认证过滤器)

09、SpringCloud之Gateway网关组件学习笔记_第46张图片

1、添加一个token过滤器

package com.changlu.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * @Description: token检查过滤器
 * @Author: changlu
 * @Date: 9:25 AM
 */
@Component
public class TokenCheckFilter implements GlobalFilter, Ordered {

    private static final String token = "700d7a8d-262a-447a-8254-9dd9ead6a0e2";

    private static final List<String> WHITE_PATH = Arrays.asList("/doLogin");

    /**
     * 流程:1、路径检测(是否放行)。2、请求头token获取。3、校验:放行or直接响应
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        //放行一些公开接口
        String path = request.getURI().getPath();
        if (WHITE_PATH.contains(path)) {
            return chain.filter(exchange);
        }
        //从请求头中获取到Authorization
        List<String> authorization = request.getHeaders().get("Authorization");
        if (!ObjectUtils.isEmpty(authorization)) {
            String token = authorization.get(0);
            //去掉前缀"bearer "
            token = token.replaceFirst("Bearer ", "");
            //token校验,成功放行(实际上会进行token解析取到uuid来从redis中获取,这里简单来表示一下)
            if (TokenCheckFilter.token.equals(token)) {
                return chain.filter(exchange);
            }
        }
        //失败进行错误响应
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("content-type", "application/json;charset=utf-8");
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", HttpStatus.UNAUTHORIZED.value());
        result.put("msg", "暂未授权");
        ObjectMapper objectMapper = new ObjectMapper();//jackson工具类
        byte[] data = new byte[0];
        try {
            data = objectMapper.writeValueAsBytes(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer wrap = response.bufferFactory().wrap(data);
        return response.writeWith(Mono.just(wrap));
    }

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

2、编写配置文件,新增一个路由

09、SpringCloud之Gateway网关组件学习笔记_第47张图片

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true   # 默认开启,只要加了网关依赖
      routes:
          # 用户服务路由
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/user

测试

09、SpringCloud之Gateway网关组件学习笔记_第48张图片

我们启动这四个模块,分别是:注册中心、网关、登录服务、用户服务。

来启动服务,以及查看一下eureka的注册中心服务注册情况:

09、SpringCloud之Gateway网关组件学习笔记_第49张图片

09、SpringCloud之Gateway网关组件学习笔记_第50张图片

接下来就可以开始进行测试了:我准备好两个接口

09、SpringCloud之Gateway网关组件学习笔记_第51张图片

①测试doLogin接口是否能够放行并返回token

09、SpringCloud之Gateway网关组件学习笔记_第52张图片

②测试用户服务接口

首先添加一下token,接着来发送请求

09、SpringCloud之Gateway网关组件学习笔记_第53张图片

09、SpringCloud之Gateway网关组件学习笔记_第54张图片

那我们来故意写错token来发送一下:

09、SpringCloud之Gateway网关组件学习笔记_第55张图片

09、SpringCloud之Gateway网关组件学习笔记_第56张图片


七、实战系列

7.1、实战6:实现请求限流

7.1.1、认识限流

通俗的说,限流就是限制一段时间内,用户访问资源的次数,减轻服务器压力,限流大致分为两种:

  1. IP 限流(5s 内同一个 ip 访问超过 3 次,则限制不让访问,过一段时间才可继续访问)
  2. 请求量限流(只要在一段时间内(窗口期),请求次数达到阀值,就直接拒绝后面来的访问了,过一段时间才可以继续访问)(粒度可以细化到一个 api(url),一个服务)

7.1.2、限流模型

介绍限流模型

限流模型:漏斗算法,令牌桶算法,窗口滑动算法,计数器算法。

常用的模型分类有两种:

  • 时间模型
    • 固定窗口模型:timeline 按照固定间隔分窗口,每个窗口有一个独立计数器,每个计数器统计窗口内的 qps,如果达到阈值则拒绝服务。最简单的限流模型,但是缺点比较明显,当在临界点出现大流量冲击,就无法满足流量控制。
    • 滑动窗口模型:滑动时间模型会将每个窗口切分成 N 个子窗口,每个子窗口独立计数。这样用w1+w2计数之和来做限流阈值校验,就可以解决此问题。
  • 桶模型
    • 令牌桶:系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
      • 解决了在实际上的互联网应用中,流量经常是突发性的问题。
    • 漏桶:水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

本章实战说明

本章节的话就使用Gateway内置的一个限流过滤器RequestRateLimiterGatewayFilterFactory

也就是令牌桶限流模型入不敷出

1)、所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;

2)、根据限流大小,设置按照一定的速率往桶里添加令牌;

3)、桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;

4)、请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完

业务逻辑之后,将令牌直接删除;

5)、令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令

牌,以此保证足够的限流;

09、SpringCloud之Gateway网关组件学习笔记_第57张图片


7.1.3、Gateway 结合 redis 实现请求量限流(Gateway内置限流令牌桶实现)

集成过程

注意:Spring Cloud Gateway 已经内置了一个 RequestRateLimiterGatewayFilterFactory,该过滤器是针对于某个路由的,并不是全局过滤器。

09、SpringCloud之Gateway网关组件学习笔记_第58张图片

1、添加redis依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redis-reactiveartifactId>
dependency>

2、指定限流的内容:ip或接口

config/RequestLimitConfig.java

package com.changlu.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;


/**
 * @Description: 请求限流配置类
 * @Author: changlu
 * @Date: 10:34 AM
 */
@Configuration
public class RequestLimitConfig {

    //针对某一个ip地址来进行限流(例如:localhost)
    @Bean(name = "ipKeyResolver")
    @Primary
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());
    }

    //针对某一个接口uri来进行限流(例如:/doLogin)
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
}

3、配置文件为指定的路由配置filter:

配置文件:application.yaml

09、SpringCloud之Gateway网关组件学习笔记_第59张图片

# redis参数配置
redis:
    host: localhost
    port: 6379
    database: 0
    password: 123456
# 配置路由
filters:
  - name: RequestRateLimiter
    args:
        key-resolver: '#{@ipKeyResolver}'
        redis-rate-limiter.replenishRate: 1 #令牌每秒填充速度
        redis-rate-limiter.burstCapacity: 1 #桶大小
        redis-rate-limiter.requestedTokens: 1 #默认是1,每次请求消耗的令牌数

测试

09、SpringCloud之Gateway网关组件学习笔记_第60张图片

使用jmeter来进行测试:

09、SpringCloud之Gateway网关组件学习笔记_第61张图片

若是请求失败,默认就会返回响应码为429。

09、SpringCloud之Gateway网关组件学习笔记_第62张图片

看一下redis中存储的参数:

09、SpringCloud之Gateway网关组件学习笔记_第63张图片

我们也可以换之前配置指定的另一个参数也就是接口名,此时redis中存储的如下:

09、SpringCloud之Gateway网关组件学习笔记_第64张图片

image-20220730120029882

7.2、实战7:Gateway集成跨域配置

对于ajax 同源策略,例如前端的访问端口与后端访问的端口不一致时,也就会产生跨域问题。

方式一:参数配置

spring:
  cloud:
    gateway:
        globalcors:
          cors-configurations:
            '[/**]':
              allowedOrigins: "*"
              allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION

方式二:通过java配置过滤器

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
 
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
 
        return new CorsWebFilter(source);
    }
}

测试

准备一个ajax的跨域问题:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>
<body>
    <input type="button" value="触发按钮" onclick="getData()">
<script src="http://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js">script>
<script>
    function getData() {
        //ajax请求
        $.get('http://localhost:81/doLogin',function(data){
            alert(data);
        });
    }
script>
body>
html>

09、SpringCloud之Gateway网关组件学习笔记_第65张图片

配置完跨域后再来进行测试:

09、SpringCloud之Gateway网关组件学习笔记_第66张图片

参考文章

[1]. 如何设置nginx日志格式来查看负载分担结果

[2]. Nginx负载均衡配置+记录请求分发日志

[3]. Spring Cloud Gateway-自定义断言及过滤器

[4]. spring-cloud-gateway 11 限流 RequestRateLimiterGatewayFilterFactory

你可能感兴趣的:(#,SpringCloud,spring,cloud,gateway,学习)