限流是什么?怎么限流?

1.为什么需要限流?

限流(Rate Limiting)是一种控制数据或服务访问速率的技术,通常用于防止滥用、保护系统免受过载和攻击,以及确保服务质量。通过限制访问速率,限流可以确保系统的稳定性和可靠性,并防止单个用户或来源过度消耗资源。

限流的基本原理是在一定时间段内限制对特定资源或服务的请求速率。这可以通过设置阈值来实现,例如每秒允许的最大请求数。当请求速率超过阈值时,系统可以采取不同的策略来处理额外的请求,例如拒绝请求、延迟处理或限制请求的频率。

下面是一个简单的例子来帮助你理解限流的概念:

就像去⾃助餐厅吃饭,如果有⼈⼀股脑地把所有美⻝都拿光了,其他⼈就⽆法享用。所以,我们需要对此做出一些限制,以便其他用户正常享用。

2.限流算法

🍊固定窗口限流

固定窗口限流算法是一种最简单的限流算法,其原理是在固定时间窗口(单位时间)内限制请求的数量。该算法将时间分成固定的窗口,并在每个窗口内限制请求的数量。具体来说,算法将请求按照时间顺序放入时间窗口中,并计算该时间窗口内的请求数量,如果请求数量超出了限制,则拒绝该请求。假设单位时间是1秒,限流阀值为3。在单位时间1秒内,每来一个请求,计数器就加1,如果计数器累加的次数超过限流阀值3,后续的请求全部拒绝。等到1秒结束后,计数器清0,重新开始计数。

缺点:可出现流量突刺

限定:1 ⼩时只允许 10 个⽤户操作

⽐如:前 59 分钟没有 1 个操作,第 59 分钟来了 10 个操作;第 1 ⼩时 01 分钟⼜来 了 10 个操作。相当于 2 分钟内执⾏了 20 个操作,服务器仍然有⾼峰危险。

    /**
     * 固定窗口时间算法
     * @return
     */
    boolean fixedWindowsTryAcquire() {
        long currentTime = System.currentTimeMillis();  //获取系统当前时间
        if (currentTime - lastRequestTime > windowUnit) {  //检查是否在时间窗口内
            counter = 0;  // 计数器清0
            lastRequestTime = currentTime;  //开启新的时间窗口
        }
        if (counter < threshold) {  // 小于阀值
            counter++;  //计数器加1
            return true;
        }

        return false;
    }

🍒滑动窗口限流算法

滑动窗口限流算法是一种更为复杂的限流算法,它解决了固定窗口限流算法存在的问题。在滑动窗口限流算法中,时间窗口不再是固定的,而是根据请求的到达时间动态滑动。算法将时间划分为多个小窗口,每个窗口有一定的计数器,用于记录该窗口内的请求数量。当请求到达时,根据请求的时间戳确定其所属的窗口,并将计数器加1。如果某个窗口内的请求数量超过了设定的阈值,则后续的请求将被拒绝或限制。

优点:能够解决上述流量突刺的问题,因为第 59 分钟时,限流窗口是 59 分 ~1 小时 59 分,这个时间段内只能接受 10 次请求,只要还在这个窗⼝内,更多的操作就会被拒绝。

缺点:实现相对固定窗⼝来说⽐较复杂,限流效果和你的滑动单位有关,过大的时间窗口可能导致无法及时处理突发流量,而过小的时间窗口可能导致频繁地滑动窗口和重置计数器,增加系统的开销。因此,在实际应用中需要进行充分的测试和调优来确定合适的时间窗口大小和滑动步长。

 /**
     * 单位时间划分的小周期(单位时间是1分钟,10s一个小格子窗口,一共6个格子)
     */
    private int SUB_CYCLE = 10;

    /**
     * 每分钟限流请求数
     */
    private int thresholdPerMin = 100;

    /**
     * 计数器, k-为当前窗口的开始时间值秒,value为当前窗口的计数
     */
    private final TreeMap<Long, Integer> counters = new TreeMap<>();

   /**
     * 滑动窗口时间算法实现
     */
    boolean slidingWindowsTryAcquire() {
        long currentWindowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / SUB_CYCLE * SUB_CYCLE; //获取当前时间在哪个小周期窗口
        int currentWindowNum = countCurrentWindow(currentWindowTime); //当前窗口总请求数

        //超过阀值限流
        if (currentWindowNum >= thresholdPerMin) {
            return false;
        }

        //计数器+1
        counters.get(currentWindowTime)++;
        return true;
    }

   /**
    * 统计当前窗口的请求数
    */
    private int countCurrentWindow(long currentWindowTime) {
        //计算窗口开始位置
        long startTime = currentWindowTime - SUB_CYCLE* (60s/SUB_CYCLE-1);
        int count = 0;

        //遍历存储的计数器
        Iterator<Map.Entry<Long, Integer>> iterator = counters.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, Integer> entry = iterator.next();
            // 删除无效过期的子窗口计数器
            if (entry.getKey() < startTime) {
                iterator.remove();
            } else {
                //累加当前窗口的所有计数器之和
                count =count + entry.getValue();
            }
        }
        return count;
    }

🥝漏桶算法

特点:以固定的速率处理请求(漏水),当请求桶满了后,拒绝请求。

举例:每秒处理 10 个请求,桶的容量是 10,每 0.1 秒固定处理⼀次请求,如果 1 秒 内来了 10 个请求,这 10 此请求都可以处理完。

但如果 1 秒内来了 11 个请求,最后那个请求就会溢出桶,被拒绝请求。

优点:能够⼀定程度上应对流量突刺,能够固定速率处理请求,保证服务器的安全。

缺点:没有固定速率处理⼀批请求,只能⼀个⼀个按顺序来处理(固定速率的缺点)


 /**
     * 每秒处理数(出水率)
     */
    private long rate;

    /**
     *  当前剩余水量
     */
    private long currentWater;

    /**
     * 最后刷新时间
     */
    private long refreshTime;

    /**
     * 桶容量
     */
    private long capacity;

    /**
     * 漏桶算法
     * @return
     */
    boolean leakybucketLimitTryAcquire() {
        long currentTime = System.currentTimeMillis();  //获取系统当前时间
        long outWater = (currentTime - refreshTime) / 1000 * rate; //流出的水量 =(当前时间-上次刷新时间)* 出水率
        long currentWater = Math.max(0, currentWater - outWater); // 当前水量 = 之前的桶内水量-流出的水量
        refreshTime = currentTime; // 刷新时间

        // 当前剩余水量还是小于桶的容量,则请求放行
        if (currentWater < capacity) {
            currentWater++;
            return true;
        }
        
        // 当前剩余水量大于等于桶的容量,限流
        return false;
    }

🍗令牌桶限流

令牌桶算法(Token Bucket Algorithm) 令牌桶算法可以看作⼀个令牌桶,其中令牌以恒定的速率产⽣。当⼀个请求到达时,如果令牌桶中仍然有令牌,则该请求得到处理并从令牌桶中减去⼀个令牌。如果令牌桶中没有令牌,则请求将被拒绝。 在此算法中,令牌代表请求能够被处理的数量,⽽桶则代表着请求被处理的容器。

举例:

管理员先⽣成⼀批令牌,每秒⽣成 10 个令牌;当⽤户要操作前,先去拿到⼀个令牌,有令牌的⼈就有资格执⾏操作、同时执⾏操作;拿不到令牌的就等着。

优点:能够并发处理同时的请求,并发性能会更高

令牌桶算法可以缓解漏桶算法的缺点,但在⼀些场景下可能存在⼀定问题。⽐如在 应对短时间内的⾼并发请求时,由于令牌数有限,引⼊过⼤的并发请求会导致严重 的性能问题,也可能会造成请求失败或者拒绝。

    /**
     * 每秒处理数(放入令牌数量)
     */
    private long putTokenRate;
    
    /**
     * 最后刷新时间
     */
    private long refreshTime;

    /**
     * 令牌桶容量
     */
    private long capacity;
    
    /**
     * 当前桶内令牌数
     */
    private long currentToken = 0L;

    /**
     * 漏桶算法
     * @return
     */
    boolean tokenBucketTryAcquire() {

        long currentTime = System.currentTimeMillis();  //获取系统当前时间
        long generateToken = (currentTime - refreshTime) / 1000 * putTokenRate; //生成的令牌 =(当前时间-上次刷新时间)* 放入令牌的速率
        currentToken = Math.min(capacity, generateToken + currentToken); // 当前令牌数量 = 之前的桶内令牌数量+放入的令牌数量
        refreshTime = currentTime; // 刷新时间
        
        //桶里面还有令牌,请求正常处理
        if (currentToken > 0) {
            currentToken--; //令牌数量-1
            return true;
        }
        
        return false;
    }

3.限流粒度

限流粒度是指在进行流量限制时所采用的控制粒度

  1. 针对某个方法限流:这种限流粒度是指对特定的方法进行流量限制,即单位时间内最多允许同时执行X个操作使用这个方法。这可以用于保护某个关键方法不被过度调用,以防止系统过载或资源耗尽。例如,一个系统中可能存在一个查询数据库的方法,为了保护数据库不被大量并发请求压垮,可以对这个方法进行限流,确保每个时刻最多只有X个操作在使用该方法。
  2. 针对某个用户限流:这种限流粒度是针对单个用户的操作进行流量限制,即单个用户在单位时间内最多执行X次操作。这可以用于防止某些用户过度使用系统资源或滥用API接口。例如,在一个Web应用程序中,某个用户可能频繁发送请求,导致服务器负载过高,通过针对该用户进行限流,可以限制其请求的频率,保护系统的稳定性。
  3. 针对某个用户X方法限流:这种限流粒度结合了前两种粒度,针对单个用户在单位时间内对特定方法的调用进行限制。例如,单个用户在单位时间内最多执行X次某个方法。这可以用于在保护关键方法的同时,也防止某些用户对该方法的滥用。这种限流粒度更加精细,可以根据具体情况对特定用户和方法进行细粒度的控制。

通过合理设置限流粒度,可以更好地保护系统的稳定性和性能,防止由于流量过大而导致的系统崩溃或性能下降。同时,也可以防止某些用户或恶意行为对系统造成不必要的负担或损害。

4.限流实现

1.本地限流(单机限流)

每个服务器单独限流,⼀般适⽤于单体项⽬,就是你的项⽬只有⼀个服务器。 在 Java 中,有很多第三⽅库可以⽤来实现单机限流: Guava RateLimiter:这是⾕歌 Guava 库提供的限流⼯具,可以对单位时间内的请求 数量进⾏限制。

import
com.google.common.util.concurrent.RateLimiter;
public static void main(String[] args) {
// 每秒限流5个请求
RateLimiter limiter = RateLimiter.create(5.0);
while (true) {
if (limiter.tryAcquire()) {
// 处理请求
} else {
// 超过流量限制,需要做何处理
}
}
}

2.分布式限流 (集群限流)

分布式限流(集群限流)适用于以下场景:

  1. 高并发系统:在开发高并发系统时,为了保护系统并提升性能,常常需要采用限流措施。分布式限流可以在多个应用服务器之间进行协调,限制整个集群的请求流量,从而保护系统免受过载和崩溃的风险。
  2. 稀缺资源场景:对于稀缺资源的场景,如秒杀、抢购等,需要限制并发请求量,以避免资源被过度消耗或滥用。分布式限流可以在整个集群范围内对这些请求进行限制,确保系统的稳定性和公平性。
  3. 写服务场景:对于写服务场景,如评论、下单等,需要限制请求的速率,以减轻数据库和服务器的压力。分布式限流可以根据集群的负载情况和容量限制,对写服务进行限速,防止系统过载。
  4. 频繁复杂查询场景:对于频繁复杂查询的场景,如评论的最后几页等,需要限制查询的请求量,避免对数据库产生过大的负载。分布式限流可以限制这类查询的请求速率,保护数据库的稳定性和性能。

总之,分布式限流适用于需要在多个应用服务器之间进行协调限流的场景,可以保护系统免受过载和崩溃的风险,并确保资源的合理使用和公平性。在选择是否使用分布式限流时,需要综合考虑系统的需求、特点以及集群的规模等因素。

3.Redisson 限流实现

Redisson 内置了⼀个限流工具类,可以帮助你利⽤ Redis 来存储、来统计。

①Redis安装包

链接:https://pan.baidu.com/s/1JFVJ4rLo3yYQAHdClYuuAg?pwd=togj 
提取码:togj 

 启动成功:

②引⼊ Redisson 依赖

        <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.21.3</version>
        </dependency>

③application.yml

  redis:
    database: 1
    host: localhost
    port: 6379
    timeout: 5000

 

④创建 RedissonConfig 配置类,⽤于初始化 RedissonClient 对象单例:

@Data
@ConfigurationProperties(prefix = "spring.redis")
@Configuration
public class RedissonConfig {
    private Integer database;
    private String host;
    private Integer port;
    private String password;
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setDatabase(database)
                .setAddress("redis://" + host + ":" + port)
                .setPassword(password);
        RedissonClient redisson = Redisson.create();
        return redisson;
    }
}

⑤测试

@SpringBootTest
class RedisLimiterTest {

    @Resource
    private RedissonClient redissonClient;

    public  void doRateLimit(String key) {
    // 创建⼀个限流器
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
    // 每秒最多访问 2 次
    // 参数1 type:限流类型,可以是⾃定义的任何类型,⽤于区分不同的限流策
    // 参数2 rate:限流速率,即单位时间内允许通过的请求数量。
    // 参数3 rateInterval:限流时间间隔,即限流速率的计算周期⻓度。
    // 参数4 unit:限流时间间隔单位,可以是秒、毫秒等。
        rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
    // 每当⼀个操作来了后,请求⼀个令牌
        boolean canOp = rateLimiter.tryAcquire(1);
       if (!canOp){
           throw  new RuntimeException("请求过于频繁");
       }
    }


    @Test
    void doRateLimit() throws InterruptedException {
    // 模拟⼀下操作
        String userId = "1";
    // 瞬间执⾏2次,每成功⼀次,就打印'成功'
        for (int i = 0; i < 3; i++) {
            doRateLimit(userId);
            System.out.println("成功");
        }

    }
}

因为限流器设置为 1秒能接受2个请求。但是测试中  1秒发送了3个请求,故此在第三次请求的时候,会抛出异常。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 分布式限流的本质是通过在分布式系统中限制请求数量或请求速率,来保证系统的可用性和稳定性。它的目的是防止系统被过度请求导致的资源耗尽,如内存溢出、网络阻塞或数据库瘫痪等。分布式限流的实现方式通常包括通过预先分配的令牌桶或漏桶等算法来控制请求的速率,从而达到限流的目的。 ### 回答2: 分布式限流的本质是通过分布式系统的协同工作来限制并发访问量,保证系统的稳定性和可靠性。 在传统的单机限流模式下,系统通过对单一服务节点进行限制访问数量,但随着互联网的发展和用户量的增加,单机限流往往无法满足需求。分布式限流采用了多台服务器协同工作的方式,将限流逻辑转移到分布式网关或代理层上。 分布式限流的本质是通过网关或代理层的协同工作来限制并发访问量。具体实现可以通过以下几个步骤: 1. 请求进入分布式网关或代理层:所有的请求都会首先进入分布式网关或代理层,这些网关或代理层可以是负载均衡器、反向代理、API网关等。 2. 限流策略设置:网关或代理层会根据预设的限流策略来判断是否允许请求通过。限流策略可以包括每秒允许通过的请求数、每分钟允许通过的请求数、每个用户允许的请求数等。 3. 限流算法实现:网关或代理层会根据限流策略实现相应的限流算法,例如漏桶算法、令牌桶算法等。这些算法可以根据当前系统的负载情况和预设的参数来动态地调整限制并发访问量。 4. 请求转发或拒绝:根据限流算法的结果,网关或代理层会将请求转发到后端的服务节点,或者直接拒绝请求。拒绝请求可以返回错误信息或者重定向到其他页面。 通过以上步骤,分布式限流可以实现对并发访问量的限制,保证系统的稳定性和可靠性。同时,分布式限流还可以根据系统负载情况动态调整限制参数,以适应不同规模和需求的系统。 ### 回答3: 分布式限流的本质是通过将请求的处理分散到多个节点中,从而实现对系统资源的控制和保护。在高并发的场景下,如果没有限制,大量请求同时涌入系统,容易导致系统资源耗尽,出现性能问题甚至系统崩溃。 分布式限流的本质是将限流操作从单个节点扩展到多个节点,通过集群间的协调和通信,实现对请求的限制和分配。其核心思想是通过集中式的限流策略和算法,将请求分配到不同的节点进行处理,确保每个节点的负载均衡,避免由于单节点处理过多请求而造成的性能问题。 分布式限流的关键点在于如何判断请求是否超出系统的承载能力,并如何合理地分配请求到各个节点。常见的限流算法包括令牌桶算法、漏桶算法等,通过设置合理的参数和规则,对请求进行限制和分配。此外,还可以通过流量控制、速率限制等手段进行限流操作。 分布式限流的本质是为了保证系统的稳定性和可靠性,避免由于并发量过大而导致的系统故障。通过将请求分散到多个节点中,可以降低单个节点的压力,提高系统的整体性能和吞吐量。同时,分布式限流也可以用于保护系统免受恶意攻击和异常请求的影响,提高系统的安全性。 综上所述,分布式限流的本质是通过多节点的协作和限制策略,实现对系统资源的控制和分配,以确保系统的稳定性、高性能和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱编程的小白白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值