1 缘起
最近补充微服务网关相关知识,学习了网关相关概念,
 了解网关在微服务中存在的意义及其使命,如统一用户认证、接口权限控制、接口限流、接口熔断、黑白名单机制等,
 打算通过实践的方式逐步学习网关的相关功能,同时分享网关应用系列文章。
 本文分享通过网关实现接口限流,构建相关服务,帮助读者轻松应对知识考核与交流。
2 架构
本文的服务架构如下图所示,
 由图可知,应用架构共有三个部分:网关、注册中心和服务,
 通过网关进行接口限流,当然是在网关中做文章,实现接口的流量控制。
 本套架构基于Spring原生的组件,其中,网关:SpringCloudGateway,注册中心:Eureka,服务:SpringBoot。
 
3 Gateway配置
使用Gateway进行接口限流,注解当然是Gateway服务,
 如下配置均是对Gateway服务进行的,其他的服务各自按照需要准备好即可,
 需要注意的是:其他服务与网关应该在同一个注册中心。
3.1 依赖
学技术,讲版本。
 SpringBoot与SpringCloud的版本对应关系如下表:
 官网查询地址:https://start.spring.io/actuator/info
| 序号 | SpringCloud版本 | SpringBoot版本 | 
|---|---|---|
| 1 | Edgware | 1.5.x | 
| 2 | Finchley | 2.0.x | 
| 3 | Greenwich | 2.1.x | 
| 4 | Hoxton.SR12 | [2.2.0.RELEASE, 2.4.0.M1) | 
| 5 | 2020.0.6 | [2.4.0.M1, 2.6.0-M1) | 
| 6 | 2021.0.0-M1 | [2.6.0-M1, 2.6.0-M3) | 
| 7 | 2021.0.0-M3 | [2.6.0-M3, 2.6.0-RC1) | 
| 8 | 2021.0.0-RC1 | [2.6.0-RC1, 2.6.1) | 
| 9 | 2021.0.5 | [2.6.1, 3.0.0-M1) | 
| 10 | 2022.0.0-M1 | [3.0.0-M1, 3.0.0-M2) | 
| 11 | 2022.0.0-M2 | [3.0.0-M2, 3.0.0-M3) | 
| 12 | 2022.0.0-M3 | [3.0.0-M3, 3.0.0-M4) | 
| 13 | 2022.0.0-M4 | [3.0.0-M4, 3.0.0-M5) | 
| 14 | 2022.0.0-M5 | [3.0.0-M5, 3.0.0-RC1) | 
| 15 | 2022.0.0-RC1 | [3.0.0-RC1, 3.1.0-M1) | 
3.1.1 SpringBoot
SpringBoot版本:2.2.8.RELEASE。
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.8.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
3.1.2 SpringCloud
SpringCloud提供微服务相关组件。
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
3.1.3 注册中心&网关&Redis
注册中心:网关为了发现注册中心的服务,配置uri进行限流;
 网关:网关配置的基础组件;
 Redis:存储限流使用的令牌(Token)。
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <version>2.2.3.RELEASE</version>
</dependency>
<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>
3.2 创建拦截类型Bean
为什么创建拦截类型Bean?
 SpringCloud Gateway中可以自定义配置拦截方式,
 通过Spring正则表达式(SpEL)匹配对应的拦截Bean,
 所以,要创建接口拦截方式的Bean。
 其中,拦截有三种方式:路径拦截、IP拦截和参数拦截,
 配置样例如下:
package com.monkey.gateway_template.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;
/**
 * 请求限流配置.
 *
 * @author xindaqi
 * @since 2022-11-16 18:19
 */
@Configuration
public class RequestRateLimiter {
    @Primary
    @Bean
    KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
    @Bean
    KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
    @Bean
    KeyResolver paramKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("param-name"));
    }
}
3.3 限流配置
SpringCloud Gateway的限流算法采用令牌桶限流,
 结合Redis,将令牌存储与Redis,提高读写性能。
 网关限流配置的最核心部分如下,
 其中,
 (1)通过动态路由方式获取注册中心的服务:uri: lb://service-name;
 (2)断言路径predicates Path,为了使限流生效,路径不可与服务名称相同;
 (3)为使限流生效,需要去除配置的断言路径:StripPrefix=1;
 (4)过滤器名称为RequestRateLimiter,Redis约定的名称,不可更改。
 限流参数:
 (1)key-resolver: “#{@ipKeyResolver}”: 限流方式:Bean名称
 (2)redis-rate-limiter.replenishRate: 1生成令牌速率:个/秒
 (3)redis-rate-limiter.burstCapacity: 令牌桶容量,突发流量通过网关进入该接口时可处理的最大请求数
 (4)redis-rate-limiter.requestedTokens: 1 # 每次消费的Token数量
server:
  port: 9001
eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://localhost:8001/eureka/eureka
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启以应用名代理服务,即通过应用名称访问接口
          lowerCaseServiceId: true # 微服务名称小写
      routes:
        - id: producer # 代理的服务ID,唯一
          uri: lb://producer # 代理的服务实例名称,开启微服务名称小写后,可使用小写的名称
          predicates:
            - Path=/producer-server/** # 断言路径,即通过该路径匹配代理的服务,为了使限流生效,路径不可与服务名称相同
          filters:
            - StripPrefix=1 # 去除producer-server,对后面的URI进行限流,保证限流生效
            - name: RequestRateLimiter # 使用Redis限流器
              args:
                key-resolver: "#{@ipKeyResolver}" # 限流方式:Bean名称
                redis-rate-limiter.replenishRate: 1 # 生成令牌速率:个/秒
                redis-rate-limiter.burstCapacity: 3 # 令牌桶容量
                redis-rate-limiter.requestedTokens: 1 # 每次消费的Token数量
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 1 # 连接池:最大连接数,-1不限制
        max-idle: 8 # 连接池:最大空闲连接数量
        max-wait: 2000 # 连接池:最大阻塞等待时间,-1不限制,单位:毫秒
        min-idle: 0 # 连接池;最小空闲连接数量
      timeout: 1000 # 连接Redis服务器超时时间,单位:毫秒
4 测试
4.1 Postman接口测试
通过接入网关接入接口,Postman测试结果如下图所示。
4.1.1 正常请求
成功响应的结果如下图所示,由图可知,响应头中包含配置的限流参数。
| 序号 | 参数 | 描述 | 
|---|---|---|
| 1 | X-RateLimit-Remaining | 剩余可请求次数 | 
| 2 | X-RateLimit-Requested-Tokens | 每次请求消耗的Token数量 | 
| 3 | X-RateLimit-Burst-Capacity | 令牌桶容量,接口可承载的最大峰值请求数量 | 
| 4 | X-RateLimit-Replenish-Rate | 令牌生成速率,个/秒 | 

4.1.2 被丢弃的请求
请求次数超过配置后,响应结果如下图所示,
 由图可知,响应的状态码为429,描述信息为:Too Many Requests。
 
 SpringGateway生成的数据存储在Redis,样式如下图所示,
 由图可知,键名前缀为:request_rate_limiter.
 键名后缀为:tokens和timestamp。
 
4.2 JMeter测试
为了更好地体验接口限流功能,使用JMeter配置多线程请求接口,
 测试结果如下图所示,由结果树可看到请求过程中,有正常的请求,有被拒绝的请求。
4.2.1 正常请求
正常的请求响应内容如下图所示。
 
4.2.2 被丢弃的请求
被丢弃的请求响应内容如下图所示。
 
5 小结
网关通过动态路由(lb:service-name)方式代理服务时,使限流生效需满足两个条件:
 (1)断言中配置的路径前缀不能与服务名称相同,如代理的服务为producer,断言(predicates)路径中的前缀禁止配置为producer;
 (2)过滤器(filters)必须配置StripPrefix=1,移除前缀,保证代理的接口可以进行限流;
 (3)过滤器名称为RequestRateLimiter,Redis约定的名称,不可更改。
 常用的限流方式有3钟,IP限流、路径限流和参数限流:
 (1)IP限流:exchange.getRequest().getRemoteAddress().getHostName()
 (2)路径限流:exchange.getRequest().getPath().value()
 (3)参数限流:exchange.getRequest().getQueryParams().getFirst(“param-name”)。












![[附源码]Python计算机毕业设计GuiTar网站设计](https://img-blog.csdnimg.cn/04514b1dae9842f79d2730aaf5f9d354.png)






