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优缺点

优点:

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

缺点:

  • 需要维护令牌桶和令牌生成速度等状态信息,实现较为复杂。
  • 当令牌桶溢出时,会导致请求被拒绝,影响用户体验。
相关推荐
LJianK110 分钟前
Java中的类、普通类,抽象类,接口的区别
java·开发语言
花月C10 分钟前
线性动态规划(Linear DP)
算法·动态规划·代理模式
Dev7z11 分钟前
基于MATLAB的5G物理层文本传输系统仿真与性能分析
开发语言·5g·matlab
小智社群11 分钟前
贝壳获取小区的名称
开发语言·前端·javascript
lsx20240621 分钟前
Python3 OS模块详解
开发语言
LiLiYuan.37 分钟前
【Java线程 vs 虚拟机线程】
java·开发语言
hetao173383739 分钟前
2025-03-24~04-06 hetao1733837 的刷题记录
c++·算法
_深海凉_42 分钟前
LeetCode热题100-环形链表
算法·leetcode·链表
FlDmr4i2843 分钟前
.NET 10 & C# 14 New Features 新增功能介绍-扩展成员Extension Members
开发语言·c#·.net
原来是猿43 分钟前
Linux进程信号详解(三):信号保存
开发语言·c++·算法