Java实现限流算法(四种)Demo

1.固定窗口限流

1.1 代码实现

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

1.3 优缺点

2.滑动窗口限流

2.1 代码实现

2.2 测试说明

2.3 优缺点

3.漏桶算法

3.1代码实现

3.2测试说明

3.3优缺点

4.令牌桶算法

4.1代码实现

4.2测试说明

4.3优缺点


1.固定窗口限流

1.1 代码实现

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

@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 代码实现

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代码实现

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代码实现

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

优点:

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

缺点:

  • 需要维护令牌桶和令牌生成速度等状态信息,实现较为复杂。
  • 当令牌桶溢出时,会导致请求被拒绝,影响用户体验。
令牌桶算法是一种常见的限流算法,它可以控制请求的速率,防止系统被过多的请求压垮。下面是Java实现令牌桶算法的步骤和代码逻辑: 1. 定义一个令牌桶类,包含以下属性: - 最后一次令牌发放时间 - 桶的容量 - 令牌生成速度 - 当前令牌数量 2. 实现一个获取令牌的方法,该方法会在每次请求到来时被调用,具体实现如下: - 计算当前令牌数量 - 判断当前令牌数量是否足够 - 如果令牌数量不足,则拒绝请求 - 如果令牌数量足够,则领取令牌,并执行业务逻辑 3. 使用ScheduledExecutorService定时生成令牌,具体实现如下: - 每隔一段时间生成一定数量的令牌 - 如果令牌数量超过桶的容量,则不再生成令牌 下面是Java实现令牌桶算法的代码逻辑: ``` @Slf4j public class TokensLimiter { private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); // 最后一次令牌发放时间 public long timeStamp = System.currentTimeMillis(); // 桶的容量 public int capacity = 10; // 令牌生成速度10/s public int rate = 10; // 当前令牌数量 public int tokens; public void acquire() { scheduledExecutorService.scheduleWithFixedDelay(() -> { long now = System.currentTimeMillis(); // 当前令牌数 tokens = Math.min(capacity, (int) (tokens + (now - timeStamp) * rate / 1000)); // 每隔0.5秒发送随机数量的请求 int permits = (int) (Math.random() * 9) + 1; log.info("请求令牌数:" + permits + ",当前令牌数:" + tokens); timeStamp = now; if (tokens < permits) { // 若不到令牌,拒绝 log.info("限流了"); } else { // 还有令牌,领取令牌 tokens -= permits; log.info("剩余令牌=" + tokens); } }, 1000, 500, TimeUnit.MILLISECONDS); } public static void main(String[] args) { TokensLimiter tokensLimiter = new TokensLimiter(); tokensLimiter.acquire(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值