Java实现限流算法(四种)

1.固定窗口限流

[1.1 代码实现](#1.1 代码实现)

[1.2 postman测试(批量测试20次)](#1.2 postman测试(批量测试20次))

[1.3 优缺点](#1.3 优缺点)

2.滑动窗口限流

[2.1 代码实现](#2.1 代码实现)

[2.2 测试说明](#2.2 测试说明)

[2.3 优缺点](#2.3 优缺点)

3.漏桶算法

3.1代码实现

3.2测试说明

3.3优缺点

4.令牌桶算法

4.1代码实现

4.2测试说明

4.3优缺点


1.固定窗口限流

1.1 代码实现

java 复制代码
public class FixWindowLimiter {
    /**
     * 每秒限制请求数
     */
    private static final long perSecondLimit = 2;
    /**
     * 上一个窗口的开始时间
     */
    public static long preStartTime = System.currentTimeMillis();
    /**
     * 计数器
     */
    private static int counter;
 
    public static synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        // 假设窗口时间位1秒,在窗口期内判断计数器是否超过限制的请求数
        if (now - preStartTime < 1000) {
        	// 计数器小于限制数时放行,否则拒绝请求
            if (counter < perSecondLimit) {
                counter++;
                return true;
            } else {
                return false;
            }
        }
        // 时间窗口过期,重置计数器和时间戳
        counter = 0;
        preStartTime = now;
        return true;
    }
}

这里限定每秒内的请求数为2,当请求数大于2的时候,拒绝请求。下面是测试的controller

java 复制代码
@Controller
public class infoController {
    @GetMapping("/fix")
    @ResponseBody
    public String fix(String name){
        System.out.println(name);
        if (FixWindowLimiter.tryAcquire()){
            return "true";
        }else{
            return "false";
        }
    }
}

1.2 postman测试(批量测试20次)

前两次返回true,说明请求可以正常访问程序;第三次返回false,说明请求被限流。

1.3 优缺点

优点:

  • 实现简单,容易理解。
  • 适用于突发流量较小的场景。

缺点:

  • 无法处理时间窗口的临界突变问题。
  • 对于高并发场景,难以保证系统稳定性。
  • 无法实现更加精细的限流控制。

2.滑动窗口限流

滑动窗口算法相当于对固定窗口算法的一种改进。在滑动窗口算法中,主要是通过维护一个固定大小的时间窗口 ,并随着时间的推移向前滑动来计算窗口内的请求总数。这样可以在不牺牲实时性的情况下,平滑地处理流量变化。

2.1 代码实现

java 复制代码
package com.cocoa.rateLimiter;

import java.util.concurrent.TimeUnit;

public class SlidingWindowLimiter {
    // 固定时间窗口大小,单位毫秒
    private long windowSize;
    // 固定窗口拆分的小窗口数
    private int windowNum;
    // 每个窗口允许通过最大请求数
    private int maxRequestCount;
    // 各个窗口内请求计数
    private int[] perWindowCount;
    // 请求总数
    private int totalCount;
    // 当前窗口下标
    private int windowId;
    // 每个小窗口大小,毫秒
    private long perWindowSize;
    // 窗口右边界
    private long windowRightBorder;
    
    /**
     * 构造函数
     * 
     * @param windowSize 固定时间窗口大小
     * @param windowNum 固定窗口拆分的小窗口数
     * @param maxRequestCount  每个窗口允许通过最大请求数
     */
    public SlidingWindowLimiter(long windowSize, int windowNum, int maxRequestCount) {
        this.windowSize = windowSize;
        this.windowNum = windowNum;
        this.maxRequestCount = maxRequestCount;
        perWindowCount = new int[windowNum];
        perWindowSize = windowSize / windowNum;
        windowRightBorder = System.currentTimeMillis();
    }
    
    /**
     * 限流方法
     * @return
     */
    public synchronized boolean tryAcquire() {
        long currentTime = System.currentTimeMillis();
        if (currentTime > windowRightBorder){// 窗口移动
            do {
                windowId = (++windowId) % windowNum;
                totalCount -= perWindowCount[windowId];
                perWindowCount[windowId]=0;
                windowRightBorder += perWindowSize;
            }while (windowRightBorder < currentTime);
        }
        if (totalCount < maxRequestCount){
            System.out.println("tryAcquire success, windowId = "+windowId);
            perWindowCount[windowId]++;
            totalCount++;
            return true;
        }else{
            System.out.println("tryAcquire fail, windowId = "+windowId);
            return false;
        }
    }
	
	/**
     * 测试方法
     * @param args
     * @throws InterruptedException
     */
	public static void main(String[] args) throws InterruptedException {
        // 10个小窗口,每个窗口100ms, 可以接收的最大请求数为10
        SlidingWindowLimiter slidingWindowLimiter = new SlidingWindowLimiter(1000, 10, 10);
        //TimeUnit.MILLISECONDS.sleep(900);// 构建窗口
        for (int i = 0; i < 40; i++) {
            boolean acquire = slidingWindowLimiter.tryAcquire();
            if (acquire){
                System.out.println("任务" + (i + 1)  + "执行任务 " + System.currentTimeMillis());
            }else{
                System.out.println("任务" + (i + 1) + "被限流 "+ System.currentTimeMillis());
            }
            TimeUnit.MILLISECONDS.sleep(50);
        }
    }
}

2.2 测试说明

每个任务延时50ms,测试结果如下:前10个任务正常执行,后面的任务被拒绝。

前10次将窗口占满,后续部分窗口移动。

2.3 优缺点

优点:

  • 可以根据业务需求灵活调整窗口的大小和时间间隔,实现更加精细的限流控制。
  • 解决了固定窗口算法的窗口边界问题,避免突发流量压垮服务器。

缺点:

  • 窗口的大小和时间间隔需要根据具体业务场景进行调整,实现较为复杂。
  • 需要统计窗口内的请求次数,计算较为复杂。

3.漏桶算法

漏桶算法是一种基于固定速率的流量控制算法。在漏桶算法中,请求像水一样不断地注入漏桶,而漏桶会按照固定的速率将水漏掉。如果注入的速率持续大于漏出的速率,则会出现限流的效果。漏桶算法可以限制请求的速率,并且可以防止出现过载的情况。如果入口流量过大,漏桶可能会溢出,导致数据丢失。

3.1代码实现

java 复制代码
package com.cocoa.rateLimiter;

import java.util.concurrent.TimeUnit;

public class LeakyBucketLimiter {
    /**
     * 桶的最大容量
     */
    public long capacity = 10;
    /**
     * 桶内当前水量
     */
    public long count = 0;
    /**
     * 漏水速率(每秒5次)
     */
    public long rate = 5;
    /**
     * 上次漏水时间
     */
    public static long lastLeakTime = System.currentTimeMillis();
    /**
     * 限流方法,返回true表示通过
     */
    public synchronized boolean tryAcquire() {
        // 调用漏水方法
        this.leak();
        // 判断是否超过最大请求数量
        if (count < capacity) {
            count++;
            return true;
        }
        return false;
    }

    /**
     * 漏水方法,计算并更新这段时间内漏水量
     */
    private void leak() {
        // 获取系统当前时间
        long currentTime = System.currentTimeMillis();
        // 计算这段时间内,需要流出的水量
        long leakWater = (currentTime - lastLeakTime) * rate / 1000;
        count = Math.max(count - leakWater, 0);
        lastLeakTime = currentTime;
    }

    public static void main(String[] args) throws InterruptedException {
        LeakyBucketLimiter limiter = new LeakyBucketLimiter();
        for (int i = 0; i < 30; i++){
            if (limiter.tryAcquire()){
                System.out.println("任务" + (i + 1) + "成功执行");
            }else {
                System.out.println("任务" + (i + 1) + "被限流");
            }
            TimeUnit.MILLISECONDS.sleep(200);// 每次都可以漏出一个请求(每200ms可以成功执行一个请求,漏桶让出一个位置)
            // TimeUnit.MILLISECONDS.sleep(100);// 100ms 会导致每次无法漏出请求,漏桶满了无法继续接收新的请求
        }
    }
}

3.2测试说明

3.3优缺点

优点:

  • 可以限制请求的速率,并且不会出现过载的情况。
  • 可以实现较为精细的限流控制。

缺点:

  • 如果入口流量过大,超过了桶的容量,那么就需要丢弃部分请求
  • 由于速率是固定的,即使下游能够处理更大的流量,漏桶也不允许突发流量通过。

4.令牌桶算法

令牌桶算法是一种基于令牌的流量控制算法。在令牌桶算法中,系统会向令牌桶中不断添加令牌,每个请求会消耗掉一个令牌,如果令牌桶中没有足够的令牌,则请求会被拒绝。令牌桶算法可以限制请求的速率,同时不会出现过载的情况。

4.1代码实现

java 复制代码
package com.cocoa.rateLimiter;


import java.util.concurrent.TimeUnit;

public class TokenBucketRateLimiter {
    /**
     * 桶的最大容量
     */
    public long capacity = 10;
    /**
     * 桶内当前的令牌数量
     */
    public long count = 0;
    /**
     * 令牌生成速率(每秒5次)
     */
    public long tokenRate = 5;
    /**
     * 上次生成令牌的时间
     */
    public long lastGenerateTime = System.currentTimeMillis();

    /**
     * 限流方法,返回true表示通过
     */
    public boolean limit() {
        // 调用生成令牌方法
        this.generateTokens();
        // 判断桶内是否还有令牌
        if (count > 0) {
            count--;
            return true;
        }
        return false;
    }

    /**
     * 生成令牌方法,计算并更新这段时间内生成的令牌数量
     */
    private void generateTokens() {
        long currentTime = System.currentTimeMillis();
        // 计算这段时间内,需要生成的令牌数量
        long tokens = (currentTime - lastGenerateTime) * tokenRate / 1000;
        count = Math.min(count + tokens, capacity);
        lastGenerateTime = currentTime;
    }

    public static void main(String[] args) throws InterruptedException {
        TokenBucketRateLimiter limiter = new TokenBucketRateLimiter();
        TimeUnit.MILLISECONDS.sleep(1000);// 生成5个令牌
        limiter.generateTokens();
        for (int i = 0; i < 20; i++) {
            if (limiter.limit()){
                System.out.println("任务" + (i + 1)  + "执行任务 ");
            }else{
                System.out.println("任务" + (i + 1)  + "被限流 ");
            }
        }
    }
}

4.2测试说明

4.3优缺点

优点:

  • 令牌桶算法可以处理突发流量。当桶满时,能够以最大速度处理请求。
  • 与漏桶算法相比,令牌桶算法提供了更大的灵活性。

缺点:

  • 需要维护令牌桶和令牌生成速度等状态信息,实现较为复杂。
  • 当令牌桶溢出时,会导致请求被拒绝,影响用户体验。
相关推荐
顽疲4 分钟前
从零用java实现 小红书 springboot vue uniapp (6)用户登录鉴权及发布笔记
java·vue.js·spring boot·uni-app
神经网络的应用12 分钟前
C++程序设计例题——第三章程序控制结构
c++·学习·算法
oscar99924 分钟前
Maven项目中不修改 pom.xml 状况下直接运行OpenRewrite的配方
java·maven·openrewrite
南宫生24 分钟前
力扣-数据结构-3【算法学习day.74】
java·数据结构·学习·算法·leetcode
工业甲酰苯胺30 分钟前
聊一聊 C#线程池 的线程动态注入
java·开发语言·c#
zfenggo31 分钟前
c/c++ 无法跳转定义
c语言·开发语言·c++
m0_7482402537 分钟前
docker--压缩镜像和加载镜像
java·docker·eureka
向宇it41 分钟前
【从零开始入门unity游戏开发之——C#篇30】C#常用泛型数据结构类——list<T>列表、`List<T>` 和数组 (`T[]`) 的选择
java·开发语言·数据结构·unity·c#·游戏引擎·list
葡萄架子42 分钟前
Python中的logger作用(from loguru import logger)
java·前端·python
hakesashou1 小时前
python怎么看矩阵维数
开发语言·python