6 种服务限流的实现方式

固定窗口算法

固定窗口算法

  • 原理:固定窗口算法通过在单位时间内维护一个计数器,限制在每个固定的时间段内请求通过的次数,以达到限流的效果。

  • 优点:算法实现简单,容易理解。

  • 缺点:不能应对突发流量,可能导致短时间内流量激增,从而影响服务稳定性。

java 复制代码
public class FixedWindowRateLimiter {
    private long windowSize; // 时间窗口大小,单位毫秒
    private int maxRequestCount; // 允许通过请求数
    private AtomicInteger count = new AtomicInteger(0); // 当前窗口通过的请求计数
    private long windowBorder; // 窗口右边界

    public FixedWindowRateLimiter(long windowSize, int maxRequestCount) {
        this.windowSize = windowSize;
        this.maxRequestCount = maxRequestCount;
        windowBorder = System.currentTimeMillis() + windowSize;
    }

    public synchronized boolean tryAcquire() {
        long currentTime = System.currentTimeMillis();
        if (windowBorder < currentTime) {
            windowBorder += windowSize;
            count.set(0);
        }
        if (count.intValue() < maxRequestCount) {
            count.incrementAndGet();
            return true;
        } else {
            return false;
        }
    }
}

滑动窗口算法

滑动窗口算法

  • 原理:滑动窗口算法在固定窗口的基础上进行了改进,将时间窗口划分为多个小块,每个小块维护独立的计数器,每次滑动时,减去前一个时间块内的请求数量,并添加一个新的时间块到末尾。

  • 优点:流量控制更精细,能够更平滑地处理请求。

  • 缺点:需要额外的存储空间来维护每一块时间内的计数器,实现相对复杂。

arduino 复制代码
public class SlidingWindowRateLimiter {
    private long windowSize; // 时间窗口大小,单位毫秒
    private int shardNum; // 分片窗口数
    private int maxRequestCount; // 允许通过请求数
    private int[] shardRequestCount; // 各个窗口内请求计数
    private int totalCount; // 请求总数
    private int shardId; // 当前窗口下标
    private long tinyWindowSize; // 每个小窗口大小,毫秒
    private long windowBorder; // 窗口右边界

    public SlidingWindowRateLimiter(long windowSize, int shardNum, int maxRequestCount) {
        this.windowSize = windowSize;
        this.shardNum = shardNum;
        this.maxRequestCount = maxRequestCount;
        shardRequestCount = new int[shardNum];
        tinyWindowSize = windowSize / shardNum;
        windowBorder = System.currentTimeMillis();
    }

    public synchronized boolean tryAcquire() {
        long currentTime = System.currentTimeMillis();
        if (currentTime > windowBorder) {
            do {
                shardId = (++shardId) % shardNum;
                totalCount -= shardRequestCount[shardId];
                shardRequestCount[shardId] = 0;
                windowBorder += tinyWindowSize;
            } while (windowBorder < currentTime);
        }
        if (totalCount < maxRequestCount) {
            shardRequestCount[shardId]++;
            totalCount++;
            return true;
        } else {
            return false;
        }
    }
}

漏桶算法

漏桶算法

  • 原理:漏桶算法通过以固定速率出水,将外部请求比作水,不断注入水桶中,如果水超过桶的最大容量则被丢弃,以此来控制数据的传输速率。

  • 优点:能够有效地控制数据的传输速率,平滑处理请求。

  • 缺点:不管系统负载如何,所有请求都得排队,可能导致系统资源的浪费。

csharp 复制代码
public class LeakyBucketRateLimiter {
    private int capacity; // 桶的容量
    private AtomicInteger water = new AtomicInteger(0); // 桶中现存水量
    private long leakTimeStamp; // 开始漏水时间
    private int leakRate; // 水流出的速率,即每秒允许通过的请求数

    public LeakyBucketRateLimiter(int capacity, int leakRate) {
        this.capacity = capacity;
        this.leakRate = leakRate;
    }

    public synchronized boolean tryAcquire() {
        if (water.get() == 0) {
            leakTimeStamp = System.currentTimeMillis();
            water.incrementAndGet();
            return water.get() < capacity;
        }
        long currentTime = System.currentTimeMillis();
        int leakedWater = (int)((currentTime - leakTimeStamp) / 1000 * leakRate);
        if (leakedWater != 0) {
            int leftWater = water.get() - leakedWater;
            water.set(Math.max(0, leftWater));
            leakTimeStamp = System.currentTimeMillis();
        }
        if (water.get() < capacity) {
            water.incrementAndGet();
            return true;
        } else {
            return false;
        }
    }
}

令牌桶算法

令牌桶算法

  • 原理:令牌桶算法通过系统以恒定的速度生成令牌,并将令牌放入令牌桶中,请求必须从令牌桶中获取一个令牌才能被处理,如果桶中没有令牌,则请求被限流。

  • 优点:允许一定程度的突发请求,能够限制服务调用的平均速率,同时允许一定程度的突发调用。

  • 缺点:无明显缺点,是较为理想的限流算法。

ini 复制代码
RateLimiter rateLimiter = RateLimiter.create(5);
for (int i = 0; i < 10; i++) {
    double time = rateLimiter.acquire();
    System.out.println("等待时间:" + time + "s");
}

中间件限流

  • 工具:Sentinel

  • 原理:Sentinel 是 Spring Cloud Alibaba 中的熔断限流组件,通过在服务层的方法上添加注解来实现限流。

  • 优点:适用于分布式、微服务架构,提供开箱即用的限流方法。

  • 缺点:需要引入额外的组件。

typescript 复制代码
@Service
public class QueryService {
    public static final String KEY = "query";
    @SentinelResource(value = KEY, blockHandler = "blockHandlerMethod")
    public String query(String name) {
        return "begin query, name=" + name;
    }
    public String blockHandlerMethod(String name, BlockException e) {
        e.printStackTrace();
        return "blockHandlerMethod for Query : " + name;
    }
}

网关限流

  • 工具:Spring Cloud Gateway

  • 原理:通过配置网关规则,使用 Redis 加 Lua 脚本的方式实现令牌桶算法进行限流。

  • 优点:可以灵活设置限流的维度,如请求路径、用户信息等。

  • 缺点:需要配置和维护 Redis。

typescript 复制代码
@Component
public class PathKeyResolver implements KeyResolver {
    public Mono<String> resolve(ServerWebExchange exchange) {
        String path = exchange.getRequest().getPath().toString();
        return Mono.just(path);
    }
}

结论

限流是确保系统稳定性的关键措施之一。文章介绍了六种限流方法,包括固定窗口算法、滑动窗口算法、漏桶算法、令牌桶算法、中间件限流和网关限流,各有优缺点,适用于不同的场景和架构。

相关推荐
Pandaconda10 分钟前
【计算机网络 - 基础问题】每日 3 题(十)
开发语言·经验分享·笔记·后端·计算机网络·面试·职场和发展
loveLifeLoveCoding11 分钟前
Java List sort() 排序
java·开发语言
草履虫·18 分钟前
【Java集合】LinkedList
java
AngeliaXue20 分钟前
Java集合(List篇)
java·开发语言·list·集合
世俗ˊ21 分钟前
Java中ArrayList和LinkedList的比较
java·开发语言
zhouyiddd25 分钟前
Maven Helper 插件
java·maven·intellij idea
攸攸太上33 分钟前
Docker学习
java·网络·学习·docker·容器
Milo_K41 分钟前
项目文件配置
java·开发语言
程序员大金1 小时前
基于SpringBoot+Vue+MySQL的养老院管理系统
java·vue.js·spring boot·vscode·后端·mysql·vim
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS网上购物商城(JAVA毕业设计)
java·vue.js·spring boot·后端·开源