当前位置: 首页 > news >正文

SpringCloud之微服务网关的入门与进阶(请求过滤、网关限流)

(一)什么是微服务网关

后端写完所有的微服务之后,最终是要交给前端去调用。我们都知道每个微服务都有各自的端口号,如果前端直接通过IP加端口的方式去调用微服务会很麻烦。如果想对请求增加限制也会变得十分困难。这个时候微服务网关就出现了。

微服务网关又成为API网关,它是系统对外的唯一入口。APl网关封装了系统内部架构,为每个客户端提供一个定制的APl。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。

听起来有些抽象,下面就做一张图给大家展示一下:前端的请求统一由微服务网关进行管理,微服务网关可以调用各个微服务,同时自身还有多种功能。

API网关的职责有身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理,最重要的功能是和外界联系。

常见的API网关实现方式有Zuul,traefix,Spring Cloud Gateway等等。目前主流的微服务网关是Spring Cloud Gateway。

(二)Spring Cloud Gateway网关

Spring Cloud Gateway是Spring 官方基于Spring 5.0,Spring Boot 2.0和Project Reactor 等技术开发的网关,旨在为微服务架构提供一种简单而有效的统一的API路由管理方式,统一访问接口。Spring Cloud Gateway作为Spring Cloud 生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。它是基于Nttey的响应式开发模式。

2.1 核心概念:

路由(route):路由是网关最基础的部分,路由信息由一个ID,一个目的URL,一组断言工厂和一组Filter组成。

断言(predicates):java8中的断言函数,Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自Http Request中的任何信息。当断言为真时,则匹配路由。

过滤器(filter):对请求和响应进行过滤

下面通过实践来操作微服务网关。

2.2 搭建环境:

所使用的项目依旧基于之前博客所写的项目,源码放在文末。为了方便理解,所用的模块只有三个:

创建工程导入依赖

创建api_gateway_server工程,在工程中的pom.xml中导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka,之后会用到-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置启动类

创建GatewayApplication类

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

编写配置文件

这里对配置文件的编写使用.yml格式的配置文件,在resource文件夹下创建applicaiion.yml。这里需要注意的是gateway下routes的配置,首先用id自定义网关接着定义uri,意思是当访问的是9090端口(即访问的是网关时),就自动把ip和端口号变成该uri;最后写断言predicates,这里的Path=/user/** 指得是自动匹配/user/**的路径。当请求地址为http://localhost:9090/user/1时,微服务网关会去访问http://127.0.0.1:9001/user/1,这就是微服务网关调用微服务的功能。 

#设置端口为9090
server:
  port: 9090
#eureka配置
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true
    
spring:
  application:
    name: api-gateway-server
  #配置SpringCloudGateway的路由
  cloud:
    gateway:
      routes:
        - id: user-service  #id是自定义的标识
          uri: http://127.0.0.1:9001  #当访问9090端口时,自动把ip和端口替换为此uri
          predicates:
            - Path = /user/**  #自动匹配/user/**的路径

一个小错误:

springcloudgateway的内部是通过netty+webflux实现,webflux实现和springmvc存在冲突,因此修改整个项目的父类依赖

将springmvc的依赖放到需要的module上,将依赖从父依赖中删除

2.3 路由配置详解

  路由断言中有许多匹配规则,之前只介绍了Path,其实还有许多匹配规则

predicates:
    - After=XXX #路由断言之后匹配
    - Before=XXX #路由断言之前匹配
    - Between=XXX,XXX #路由断言之间
    - Cookie=XXX  #路由断言Cookie匹配,匹配给定名称(如master)和正则表达式(如ma.r)
    - Header=XXX  #根据请求头信息匹配
    - Host=**.somehost.org   #根据host地址匹配
    - Method=GET  #根据请求方法匹配
    - Path=***   #路由断言匹配 
   - Query=baz  #路由断言Query匹配
   - RemoteAddr=192.168.1.1/24   #根据远程路径匹配  

定义请求路径时,我们还可以据微服务名称拉取请求路径,下面是两种不同方法定义uri,实现同样的功能。

#uri: http://127.0.0.1:9001  #当访问9090端口时,自动把ip和端口替换为此uri
uri: lb://userservice #根据微服务名称拉取请求路径

在请求转发时,我们还可以使用http://localhost:9090/微服务名称/user/1的方式进行请求转发,只需要在配置文件中增加相关配置:

discovery:
  locator:
    enabled: true #开启根据微服务名称自动转发
    lower-case-service-id: true  #w微服务名称小写呈现

总体的配置如图

2.4 Spring Cloud Gateway过滤器

Spring Cloud Gateway除了具有请求路由功能之外,也支持对请求的过滤。

生命周期:

过滤器有两种生命周期,PRE和POST:

PRE这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

POST这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

过滤器类型

GatewayFilter应用到单个路由或者一个分组的路由上

GlobalFilter应用到所有的路由上

局部过滤器可以使用Spring Cloud Gateway内置的方法,在这里不做过多解释。这里主要介绍如何自定义全局过滤器

过滤器实践:

创建一个Filter包,在包下新建GatewayFilter类,自定义全局过滤器需要继承GlobalFilter, Ordered接口,并重写接口的两个方法

@Component
public class GatewayFilter implements GlobalFilter, Ordered {
    //在里面写过滤器的逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("进入了过滤器");
        //表示继续向下执行
        return chain.filter(exchange);
    }
    //过滤器的优先级,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

通过访问可以看到控制台上出现了打印的信息。

模拟用户认证过程:

全局过滤器可以实现对全局的用户验证。修改gatewayFilter,编写失败的逻辑,如果获取到的request请求中第一个属性不是access-token或为空,则返回HttpStatus.UNAUTHORIZED的报错

@Component
public class GatewayFilter implements GlobalFilter, Ordered {
    //在里面写过滤器的逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("进入了过滤器");
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
        if (token==null){
            //如果不存在,认证失败
            System.out.println("没有登陆");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
    //过滤器的优先级,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

在浏览器中输入:http://localhost:9090/user/1,出现报错,因为没有access-token

在浏览器中输入:http://localhost:9090/user/1?access-token=1,获取正确结果。

(三) 网关限流

在实际环境中会遇到这样的问题,同一时间会有许多请求去调用微服务,大量请求的进入很有可能导致微服务被破环,因此限流就变得很重要。

3.1 常见的一些限流算法:

计数器算法:设定一个单位时间内的计数值,如果单位时间内访问的请求大于这个设定的值,则之后的请求都拒绝。直到单位时间内的值低于这个值才继续接收。

漏桶算法:我们可以把漏桶算法想象成一个漏斗。请求进来时先到漏斗中,然后按设定好的输出速率分配到微服务上。当一下子有很多请求进来时,只能在漏斗上等着。为了控制流量,需要设置两个变量:一个是桶的大小,另一个是漏斗流出的速率。

令牌桶算法:令牌桶算法是对漏桶算法的一种改进,相比于漏桶算法,令牌桶算法能允许一定程度的突发调用。令牌桶算法的原理:在令牌桶算法中,存在一个桶,用来存放固定数量的令牌,令牌会以一定速率放到桶中,桶中的令牌满了之后就不会再放令牌。当一个请求来临时,要先在令牌桶中拿到一个令牌,才能去调用微服务,当令牌桶中没有令牌的时候,后来到的请求就需要等待。令牌桶算法除了可以限流,还允许一定程度的突发调用:比如一个令牌桶的容量是100,在没有请求的时候,令牌桶中的令牌数量是满的。此时如果突然来了100个请求,那么这100个请求都能被立刻执行。

3.2 基于Filter的限流实践

SpringCloudGateway官方就提供了基于令牌桶的限流支持,基于其内置的过滤器工厂

RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方

式进行流量控制。所以我们可以通过配置redis,在redis中看到运行的情况。

环境搭建:

首先还是引入相关依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

修改配置文件:

整段配置文件如下:

server:
  port: 9090
spring:
  application:
    name: api-gateway-server
  #配置redis的参数
  redis:
    host: localhost
    port: 6379
  #配置SpringCloudGateway的路由
  cloud:
    gateway:
      routes:
        - id: user-service  #id是自定义的标识
          #uri: http://127.0.0.1:9001  #当访问9090端口时,自动把ip和端口替换为此uri
          uri: lb://userservice #根据微服务名称拉取请求路径
          predicates:
            - Path=/user/**  #自动匹配/user/**的路径
          filters:
            - name: RequestRateLimiter
              args:
                #使用SpEL从Spring容器中获取对象
                key-resolver: '#{@pathKeyResolver}'
                #令牌桶每秒填充的平均速率
                redis-rate-limiter.replenishRate: 1
                #令牌桶的容量
                redis-rate-limiter.burstCapacity: 3
      discovery:
        locator:
          enabled: true #开启根据微服务名称自动转发
          lower-case-service-id: true  #w微服务名称小写呈现

eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true

这里需要注意的是第19行开始的过滤器配置,首先要从Spring容器中获取对象,这个对象我们接下来会去创建。其次就是设置令牌桶的填充速率和容量。为了方便观察我设置的比较小。

配置KeyResolver

为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。新建KeyResolverConfiguration类:这里仅仅实现基于路径的限流,即同一路径请求超过令牌桶的限制就会报错。

@Component
public class KeyResolverConfiguration{
    /*
    *基于请求路径的限流
    */
    @Bean
    public KeyResolver pathKeyResolver(){
        //lambda表达式
        return exchange -> Mono.just(
                exchange.getRequest().getPath().toString()
        );
//      普通的java写法
//        return new KeyResolver() {
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
//                return Mono.just(exchange.getRequest().getPath().toString());
//            }
//        };

    }
}
}

启动需要用到的微服务后(EurekaApplication、UserApplication、GatewayApplication),在浏览器中输入http://localhost:9090/user/1,看到出现正常的调用。快速刷新页面,当请求无法拿到令牌后,就出现报错:

也可以启动redis,在redis中输入monitor,观察监测数据:

除了基于路径的限流外,还有其他的限流方式,修改KeyResolverConfiguration

@Component
public class KeyResolverConfiguration{
    /*
    *基于请求路径的限流
    */
    @Bean
    public KeyResolver pathKeyResolver(){
        //lambda表达式
        return exchange -> Mono.just(
                exchange.getRequest().getPath().toString()
        );
    }
    
    /**
     * 基于请求ip的限流
     */
    @Bean
    public KeyResolver ipkeyResolver(){
        return exchange -> Mono.just(
                exchange.getRequest().getHeaders().getFirst("X-Forwarded-For")
        );
    }
    
    /**
     * 基于用户的限流
     */
    @Bean
    public KeyResolver userkeyResolver(){
        return exchange -> Mono.just(
                exchange.getRequest().getQueryParams().getFirst("user")
        );
    }
}

项目代码放在github下的version6.0分支上:直接点击即可github源码

相关文章:

  • redis入门到精通系列(一):入门redis看这一篇就够了
  • 常用的第三方库
  • redis入门到精通系列(二):redis操作的两个实践案例
  • 给所有的td单元格绑定一个click事件
  • redis入门到精通系列(三):key的通用操作和redis内部db的通用操作
  • 正则表达式与grep详解
  • redis入门到精通系列(四):Jedis--使用java操作redis详解
  • C语言 原码--反码--补码
  • redis入门到精通系列(五):redis的持久化操作(RDB、AOF)
  • 我的学习进度条
  • redis入门到精通系列(六):redis的事务详解
  • Gradle和Android Studio入门
  • redis入门到精通系列(七):redis高级数据类型详解(BitMaps,HyperLogLog,GEO)
  • iTween基础之Shake(摆动)
  • redis入门到精通系列(八):redis的高可用--主从复制详解
  • 03Go 类型总结
  • 0基础学习移动端适配
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • Angular 响应式表单 基础例子
  • codis proxy处理流程
  • es6(二):字符串的扩展
  • ES6系统学习----从Apollo Client看解构赋值
  • es的写入过程
  • java多线程
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • Phpstorm怎样批量删除空行?
  • python 学习笔记 - Queue Pipes,进程间通讯
  • spring + angular 实现导出excel
  • Spring Boot快速入门(一):Hello Spring Boot
  • Spring-boot 启动时碰到的错误
  • SpringBoot 实战 (三) | 配置文件详解
  • Terraform入门 - 1. 安装Terraform
  • Unix命令
  • v-if和v-for连用出现的问题
  • 马上搞懂 GeoJSON
  • 面试遇到的一些题
  • 使用parted解决大于2T的磁盘分区
  • 项目实战-Api的解决方案
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 智能网联汽车信息安全
  • nb
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令...
  • (超详细)语音信号处理之特征提取
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (十)T检验-第一部分
  • (四)Linux Shell编程——输入输出重定向
  • (详细版)Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models
  • (转)详解PHP处理密码的几种方式
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .NET : 在VS2008中计算代码度量值
  • .NET Core跨平台微服务学习资源