spring cloud gateway配置请求限流

spring cloud gateway配置请求限流

一、原理介绍

  1. spring cloud gateway内置的限流器使用的是令牌桶算法(Token Bucket),它是目前应用最广泛的一种限流算法,它的基本思想由两部分组成:生成令牌 和 消费令牌。
  • 生成令牌:假设有一个装令牌的桶,最多能装 M 个,然后按某个固定的速度(每秒 r 个)往桶中放入令牌,桶满时不再放入;
  • 消费令牌:我们的每次请求都需要从桶中拿一个令牌才能放行,当桶中没有令牌时即触发限流,这时可以将请求放入一个缓冲队列中排队等待,或者直接拒绝;
  • 令牌桶算法的图示如下:(图片来源:https://blog.csdn.net/weixin_38405253/article/details/108891772)在这里插入图片描述

二、项目实践

  1. 打开我们的spring cloud gateway项目,进入pom.xml配置必要的依赖
 		<!-- Redis-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
		</dependency>
 		<!-- 网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
 		<!-- 服务发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
  1. 在路由中配置限流规则
#配置路由规则一定要注意缩进级别,否则就算是所有的代码都对了,一个缩进级别不对都会导致配置不生效
spring:
#  redis:我的redis已经通过全局配置配置完成了,所以这里配置redis的代码被注释掉了
#    host: 127.0.0.1
#    port: 6379
  cloud:
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true
      #配置路由
      routes:
        - id: #修改为自己的全局唯一id
          uri: lb://#修改为自己的注册中心的服务名
          predicates:#断言,只要是匹配到下面path规则的请求都会被纳入过滤器
            - Path=/api-uaa/oauth/token
          filters:
            - StripPrefix=1
            - PreserveHostHeader
            #从这里开始,下面的5行就是配置spring cloud gateway的限流规则
            - name: RequestRateLimiter#这第一行,表示使用spring cloud gateway
            #内置的一个三方过滤器,这东西就是配置过滤规则的,支持自定义实现
              args:
                redis-rate-limiter.replenishRate: 10#这第三行是设置每秒钟生成的令
#牌个数,前面我们说过,spring cloud gateway的底层原理是令牌桶算法,这行
#代码可以看作是一个生产者,它每秒钟生产10个令牌放到桶里面,如果桶满了
#就生产者就休息了(wait)。
                redis-rate-limiter.burstCapacity: 20#这第四行,设置桶的容量,桶里面
#最多能放多个令牌它说了算。
                key-resolver: "#{@hostAddrKeyResolver}"#这第五行,使用了spel表
#达式匹配一个bean,用来配置限流方式,这需要我们创建一个Bean,名字就叫做
#【hostAddrKeyResolver】,创建bean的代码在后面。

        #这里我为什么要再写一遍开头的路由配置呢,我并不是闲的蛋疼哈,首先声明,
#这个路由配置和开头的路由配置除了【- Path=/api-uaa/**】这一行不一样外,其他完
#全一致;然后我们在说说为啥再写一遍,开头的匹配规则是
#“- Path=/api-uaa/oauth/token”,他匹配了唯四(get/post/put/delete)个接口,
#当然在实际开发中应该是一个哈哈哈,而本路由配置【- Path=/api-uaa/**】匹配了一
#堆接口,如果将限流规则的设置写在这个路由下,并且key-resolver中设置了将远程服
#务器ip或者用户id等类型的数据设置为限流分组的id,同一个客户端如果有多条
#api-uaa/开头的接口同时访问服务器,则过滤器会将这一堆接口看作一个整体来进行限
#流,可能会造成意想不到的惊吓,所以嘛,个人感觉限流这东西应该是配置在具体的
#接口上的,还没完哈,我自己在实践时发现,如果将下面的的路由放到上面去,就是将
#这两条路由调换位置,那我所配置的限流规则就不起作用了,因为我们想要限流的接口
#【/api-uaa/oauth/token】他先匹配了【- Path=/api-uaa/**】,而这个东西没有限
#流规则,所有请求都被放行,限流也就成了无稽之谈了。
#个例子,我们有两个接口【api/limit]、【api/pass],我们想要限流api/limit,
#如果我们先配置了- Path=api/limit限流,然后再配置- Path=api/**放行,那么当
#api/limit请求进来时,匹配到- Path=api/limit,被限流,api/pass请求进来时,
#没匹配到- Path=api/limit,那它就想了,我不能单着呀,继续匹配下一个路由,然后
#发现- Path=api/**是个中央空调,当即就跑到人家怀里取暖去了,这种情况表示我们
#限流配置成功;第二种情况就是,上面的配置我要“反之”一下,当api/limit请求进来
#时,发现了中央空调- Path=api/**,立马深陷其中无法自拔,根本不能
#到达- Path=api/limit路由配置的限流过滤器,所以此种情况表示限流失败,非常失败!
        - id: #修改为自己的全局唯一id
          uri: lb://#修改为自己的注册中心的服务名
          predicates:#断言,只要是匹配到下面path规则的请求都会被纳入过滤器
            - Path=/api-uaa/**
          filters:
            - StripPrefix=1
            - PreserveHostHeader
  1. 配置限流规则中的分组id,这个类不需要深究,就是个通用的配置
package com.central.gateway.filter;

import org.springframework.boot.SpringBootConfiguration;
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 lxf
 */
@SpringBootConfiguration
public class RequestRateLimiterConfig {
    /**
     * IP 限流
     * 把用户的 IP 作为限流的 Key
     *
     * @return
     */
    @Bean
    @Primary
    public KeyResolver hostAddrKeyResolver() {
        return (exchange) -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
    /**
     * 用户 id 限流
     * 把用户 ID 作为限流的 key
     *
     * @return
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
    }
    /**
     * 请求接口限流
     * 把请求的路径作为限流 key
     *
     * @return
     */
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
}
文章的内容都是个人看法,如果不对之处,烦请指出,先行谢过。

文章借鉴了https://www.jb51.net/article/267798.htm、https://blog.csdn.net/weixin_38405253/article/details/108891772,这两篇文章写的很好,非常感谢他们。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值