java后端服务,常见的限流算法策略

文章介绍了计数器限流、令牌桶限流、漏桶限流和滑动窗口限流的原理,以及如何在Java中实现这几种限流算法,如使用AtomicInteger和ReentrantLock进行计数,以及ScheduledExecutorService实现令牌补充。
摘要由CSDN通过智能技术生成

1、计数器限流,一般用在单一维度的访问频率限制上,比如短信验证码每隔60s 只能发送一次,或者接口调用次数等。

      例如系统在指定的时间段内能同时处理 100 个请求,保存一个计数器,处理了一个请求,计数器就加一,一个请求处理完毕之后计数器减一。

计数限流算法策略实现步骤:

  1. 设定时间窗口: 将时间划分为固定的窗口,例如每秒、每分钟等。
  2. 维护计数器: 在每个时间窗口内,维护一个请求计数器,记录当前时间窗口内的请求数。
  3. 限制请求数: 如果请求计数超过设定的阈值,则拒绝多余的请求。
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CountLimit {
    private final int limit; // 限制每秒请求数
    private final long windowMillis; // 时间窗口大小(毫秒)
    private final AtomicInteger requestCount = new AtomicInteger(0);
    private final Lock lock = new ReentrantLock();
    private long lastWindowStart;

    public CountLimit(int limit, int windowSeconds) {
        this.limit = limit;
        this.windowMillis = windowSeconds * 1000L;
        this.lastWindowStart = System.currentTimeMillis();
    }

    public boolean tryConsume() {
        long currentTime = System.currentTimeMillis();
        long currentWindowStart = currentTime - currentTime % windowMillis;

        lock.lock();
        try {
            // 判断是否进入新的时间窗口
            if (currentWindowStart > lastWindowStart) {
                requestCount.set(1);
                lastWindowStart = currentWindowStart;
                // 处理请求
                return true;
            } else {
                // 检查请求数是否超过限制
                int count = requestCount.incrementAndGet();
                if (count <= limit) {
                    // 处理请求
                    return true;
                } else {
                    // 请求被拒绝
                    return false;
                }
            }
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        CountLimit countLimit = new CountLimit(5, 1); // 每秒限制5个请求

        // 模拟请求
        for (int i = 0; i < 10; i++) {
            if (countLimit.tryConsume()) {
                System.out.println("处理请求:" + i);
            } else {
                System.out.println("请求被限流:" + i);
            }
        }
    }
}

在示例中,CountLimit 类通过维护一个请求计数器和使用 ReentrantLock 进行线程安全的计数和限流。通过调用 tryConsume 方法,可以判断请求是否被允许处理。

令牌桶限流算法策略:

        令牌桶是一种经典的限流算法,它通过维护一个固定容量的令牌桶,按照固定速率往桶中放入令牌。请求需要获取令牌才能执行,如果桶中没有足够的令牌,则请求被阻塞或拒绝。

        每个客户端请求进来的时候,必须要从令牌桶获得一个令牌才能访问,否则排队等待。在流量低峰的时候,令牌桶会出现堆积,因此当出现瞬时高峰的时候,有足够多的令牌可以获取,因此令牌桶能够允许瞬时流量的处理。网关层面的限流、或者接口调用的限流,都可以使用令牌桶算法,像 Google 的 Guav和 Redisson 的限流,都用到了令牌桶算法。

步骤如下: 

  1. 维护令牌桶: 维护一个容量固定的令牌桶,以固定的速率往桶中放入令牌。
  2. 获取令牌: 每个请求在执行前,需要获取一个令牌,如果桶中有足够的令牌,则允许执行;否则,等待或拒绝。

代码如下:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TokenBucket {
    private final int capacity; // 令牌桶容量
    private final long refillRate; // 令牌补充速率(毫秒/令牌)
    private final BlockingQueue<Object> tokens; // 令牌桶队列
    private final ScheduledExecutorService scheduler; // 令牌补充调度器

    public TokenBucket(int capacity, int tokensPerSecond) {
        this.capacity = capacity;
        this.refillRate = 1000L / tokensPerSecond; // 转换成毫秒/令牌
        this.tokens = new LinkedBlockingQueue<>(capacity);
        this.scheduler = new ScheduledThreadPoolExecutor(1);

        // 初始化令牌桶
        for (int i = 0; i < capacity; i++) {
            tokens.offer(new Object());
        }

        // 定时补充令牌
        scheduler.scheduleAtFixedRate(this::refillTokens, refillRate, refillRate, TimeUnit.MILLISECONDS);
    }

    public boolean tryConsume() {
        return tokens.poll() != null;
    }

    private void refillTokens() {
        int missingTokens = capacity - tokens.size();
        for (int i = 0; i < missingTokens; i++) {
            tokens.offer(new Object());
        }
    }

    public static void main(String[] args) {
        TokenBucket tokenBucket = new TokenBucket(5, 2); // 每秒生成2个令牌

        // 模拟请求
        for (int i = 0; i < 10; i++) {
            if (tokenBucket.tryConsume()) {
                System.out.println("处理请求:" + i);
            } else {
                System.out.println("请求被限流:" + i);
            }

            try {
                Thread.sleep(500); // 模拟请求间隔
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在示例中,TokenBucket 类通过使用 LinkedBlockingQueue 维护令牌桶队列,通过调用 tryConsume 方法来判断请求是否被允许处理。令牌桶的补充由定时任务进行。 

漏桶限流算法策略:

        漏桶算法,它是一种恒定速率的限流算法,不管请求量是多少,服务端的 处理效率是恒定的。基于 MQ 来实现的生产者消费者模型,其实算是一种漏桶限流 算法。

规则如下:

请求来了放入桶中
桶内请求量满了拒绝请求
服务定速从桶内拿请求处理

 漏桶算法的原理如下:

  1. 首先,将请求流量比作水流,将漏桶的容量设置为允许的最大流量。
  2. 然后,以恒定的速度将请求流量放入漏桶中。
  3. 最后,如果漏桶已满,则将请求流量拒绝。
import java.util.concurrent.atomic.AtomicInteger;

public class LeakyBucket {

    private final int capacity;
    private final AtomicInteger counter;

    public LeakyBucket(int capacity) {
        this.capacity = capacity;
        this.counter = new AtomicInteger(0);
    }

    public boolean accept() {
        int current = counter.get();
        if (current < capacity) {
            counter.incrementAndGet();
            return true;
        } else {
            return false;
        }
    }
}

该代码定义了一个 LeakyBucket 类,用于实现漏桶算法。该类有两个属性:

  • capacity:漏桶的容量
  • counter:漏桶中的水量

该类提供了一个 accept() 方法,用于判断是否允许请求通过。该方法首先获取漏桶中的水量,如果水量小于容量,则将水量加 1,并返回 true,否则返回 false

以下是使用该代码进行限流的示例:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        LeakyBucket bucket = new LeakyBucket(100);

        // 模拟 100 个请求
        for (int i = 0; i < 100; i++) {
            if (bucket.accept()) {
                // 处理请求
                System.out.println("处理请求 " + i);
            } else {
                // 拒绝请求
                System.out.println("拒绝请求 " + i);
            }
            Thread.sleep(10);
        }
    }
}

该示例将漏桶的容量设置为 100,并模拟 100 个请求。当请求流量小于容量时,所有请求都将被处理。当请求流量超过容量时,部分请求将被拒绝。

以下是该示例的运行结果:

处理请求 0
处理请求 1
处理请求 2
...
处理请求 98
拒绝请求 99
拒绝请求 100

限流窗口算法策略:

        滑动窗口限流,本质上也是一种计数器,只是通过以时间为维度的可滑动 窗口设计,来减少了临界值带来的并发超过阈值的问题。 每次进行数据统计的时候,只需要统计这个窗口内每个时间刻度的访问量就可以了。 Spring Cloud里面的熔断框架Hystrix ,以及Spring Cloud Alibaba里面的Sentinel 都采用了滑动窗口来做数据统计。

滑动窗口限流算法的原理如下:

  1. 首先,将时间划分为多个窗口,每个窗口的大小为固定时间段。
  2. 然后,统计每个窗口内的请求数量。
  3. 如果某个窗口内的请求数量超过阈值,则拒绝该窗口内的请求。

以下是用 Java 后端服务进行滑动窗口限流的代码示例:

import java.util.concurrent.atomic.AtomicInteger;

public class SlidingWindow {

    private final int windowSize;
    private final AtomicInteger counter;

    public SlidingWindow(int windowSize) {
        this.windowSize = windowSize;
        this.counter = new AtomicInteger(0);
    }

    public boolean accept() {
        int current = counter.get();
        if (current < windowSize) {
            counter.incrementAndGet();
            return true;
        } else {
            return false;
        }
    }
}

该代码定义了一个 SlidingWindow 类,用于实现滑动窗口限流算法。该类有两个属性:

  • windowSize:窗口的大小
  • counter:窗口内的请求数量

该类提供了一个 accept() 方法,用于判断是否允许请求通过。该方法首先获取窗口内的请求数量,如果请求数量小于窗口大小,则将请求数量加 1,并返回 true,否则返回 false

以下是使用该代码进行限流的示例:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        SlidingWindow window = new SlidingWindow(100);

        // 模拟 100 个请求
        for (int i = 0; i < 100; i++) {
            if (window.accept()) {
                // 处理请求
                System.out.println("处理请求 " + i);
            } else {
                // 拒绝请求
                System.out.println("拒绝请求 " + i);
            }
            Thread.sleep(10);
        }
    }
}

该示例将窗口的大小设置为 100,并模拟 100 个请求。当请求流量小于窗口大小时,所有请求都将被处理。当请求流量超过窗口大小时,部分请求将被拒绝。

以下是该示例的运行结果:

处理请求 0
处理请求 1
处理请求 2
...
处理请求 98
拒绝请求 99
拒绝请求 100

滑动窗口限流算法的优缺点

滑动窗口限流算法具有以下优点:

  • 可以有效控制突发流量

滑动窗口限流算法也具有以下缺点:

  • 需要维护窗口内的请求数量,可能会增加系统的开销  

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值