软件系统限流的底层原理解析

目录

1. 限流的目的

2. 限流算法的实现

2.1 固定窗口计数器算法

2.2 滑动窗口算法

2.3 漏桶算法

2.4 令牌桶算法

3. 限流的实现方式

3.1 应用层限流

Go语言基于Gin中间件限流

Java中间件限流

应用级 - 单机

分布式

3.2 代理层限流

Nginx配置示例

3.3 硬件层限流

4. 限流策略

阈值设置

请求分类

反馈机制

5. 限流的考虑因素

公平性

灵活性

透明性


来源:软件系统限流的底层原理解析

在软件架构中,限流是一种控制资源使用和保护系统安全的重要机制。它通过限制在一定时间内可以处理的请求数量,来防止系统过载。

1. 限流的目的

限流主要有两个目的:

  • 防止系统过载:确保系统在高负载情况下仍能保持稳定运行。

  • 保证服务质量:为所有用户提供公平的服务,避免某些用户占用过多资源。

2. 限流算法的实现

2.1 固定窗口计数器算法

固定窗口计数器算法是一种基本的限流方法,它通过在固定时间窗口内跟踪请求的数量来实现限流。

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class FixedWindowCounter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count;
    private final int limit;
    private long windowStart;
    private final long durationMillis;

    public FixedWindowCounter(int limit, long duration, TimeUnit timeUnit) {
        this.limit = limit;
        this.durationMillis = timeUnit.toMillis(duration);
        this.windowStart = System.currentTimeMillis();
    }

    public boolean allow() {
        lock.lock();
        try {
            long now = System.currentTimeMillis();
            if (now > windowStart + durationMillis) {
                count = 0;
                windowStart = now;
            }
            if (count < limit) {
                count++;
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        FixedWindowCounter limiter = new FixedWindowCounter(10, 1, TimeUnit.MINUTES);
        for (int i = 0; i < 15; i++) {
            if (limiter.allow()) {
                System.out.println("Request " + (i + 1) + " allowed");
            } else {
                System.out.println("Request " + (i + 1) + " rejected");
            }
        }
    }
}

实现原理:固定窗口计数器算法通过设置一个固定的时间窗口(例如每分钟)和一个在这个窗口内允许的请求数量限制(例如10个请求)。在每个时间窗口开始时,计数器重置为零,随着请求的到来,计数器递增。当计数器达到限制时,后续的请求将被拒绝,直到窗口重置。

优点

  • 实现简单直观。

  • 容易理解和实现。

  • 可以保证在任何给定的固定时间窗口内,请求的数量不会超过设定的阈值。

缺点

  • 在窗口切换的瞬间可能会有请求高峰,因为计数器重置可能导致大量请求几乎同时被处理。

  • 无法平滑地处理突发流量,可能导致服务体验不佳。

固定窗口计数器算法适用于请求分布相对均匀的场景,但在请求可能在短时间内集中到达的场景下,可能需要考虑更复杂的限流算法,如滑动窗口或令牌桶算法。

2.2 滑动窗口算法

滑动窗口算法是固定窗口计数器算法的一个改进,它通过覆盖多个时间段来平滑请求流量,避免瞬时高峰。这种算法通常需要使用更高级的数据结构,如时间轮(Timing Wheel),来实现。

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class SlidingWindowLimiter {
    private final ReentrantLock lock = new ReentrantLock();
    private final int limit;
    private final int[] counters;
    private long windowStart;
    private final long windowDurationMillis;
    private final long intervalMillis;

    public SlidingWindowLimiter(int limit, long windowDuration, long interval, TimeUnit timeUnit) {
        this.limit = limit;
        this.windowDurationMillis = timeUnit.toMillis(windowDuration);
        this.intervalMillis = timeUnit.toMillis(interval);
        if (this.intervalMillis == 0) {
            throw new IllegalArgumentException("Interval must not be zero");
        }
        this.counters = new int[(int) (windowDuration / interval)];
        this.windowStart = System.currentTimeMillis();
    }

    public boolean allow() {
        lock.lock();
        try {
            if (System.currentTimeMillis() - windowStart > windowDurationMillis) {
                slideWindow();
            }

            long now = System.currentTimeMillis();
            int index = (int) ((now - windowStart) / intervalMillis) % counters.length;
            if (counters[index] < limit) {
                counters[index]++;
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    private void slideWindow() {
        // 滑动窗口,忽略最旧的时间段
        System.arraycopy(counters, 1, counters, 0, counters.length - 1);
        // 重置最后一个时间段的计数器
        counters[counters.length - 1] = 0;
        // 更新窗口开始时间
        windowStart += intervalMillis;
    }

    public static void main(String[] args) {
        SlidingWindowLimiter limiter = new SlidingWindowLimiter(5, 100, 10, TimeUnit.MILLISECONDS);

        for (int i = 0; i < 100; i++) {
            if (limiter.allow()) {
                System.out.println("Request " + (i + 1) + " allowed");
            } else {
                System.out.println("Request " + (i + 1) + " rejected");
            }
        }
    }
}

实现原理:滑动窗口算法通过将时间分为多个小的时间段,每个时间段内维护一个独立的计数器。当一个请求到达时,它会被分配到当前时间所在的小时间段,并检查该时间段的计数器是否已达到限制。如果未达到,则允许请求并增加计数;如果已达到,则拒绝请求。随着时间的推移,旧的时间段会淡出窗口,新的时间段会加入。

优点

  • 相比固定窗口算法,滑动窗口算法能够更平滑地处理请求,避免瞬时高峰。

  • 可以提供更细致的流量控制。

缺点

  • 实现相对复杂,需要维护多个计数器和时间索引。

  • 对内存和计算的要求更高。

滑动窗口算法适用于需要平滑流量控制的场景,尤其是在面对突发流量时,能够提供比固定窗口计数器更优的流量控制效果。

2.3 漏桶算法

漏桶算法是一种经典的流量控制方法,特别适合于平滑突发流量,确保数据以均匀的速率被处理。

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class LeakyBucket {
    private final LinkedBlockingQueue<Boolean> queue;
    private final int capacity;

    public LeakyBucket(int capacity) {
        this.capacity = capacity;
        this.queue = new LinkedBlockingQueue<>(capacity);
    }

    // 将请求放入队列,如果队列满了,返回 false,表示请求被丢弃
    public boolean push() {
        return queue.offer(true);
    }

    // 从队列中取出请求并模拟处理过程
    public void process() {
        while (true) {
            try {
                queue.poll(100, TimeUnit.MILLISECONDS); // 尝试从队列中取出请求,等待时间设为100毫秒
                System.out.println("Request processed at " + java.time.LocalDateTime.now());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }

    public static void main(String[] args) {
        LeakyBucket lb = new LeakyBucket(5); // 创建一个容量为5的漏桶

        // 启动请求处理循环
        new Thread(lb::process).start();

        // 模拟请求
        for (int i
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值