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);
    }
}

结论

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

相关推荐
皮皮林5515 小时前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
IT_陈寒8 小时前
Python开发者必知的5大性能陷阱:90%的人都踩过的坑!
前端·人工智能·后端
顺风尿一寸9 小时前
从 Java NIO poll 到 Linux 内核 poll:一次系统调用的完整旅程
java
流浪克拉玛依9 小时前
Go Web 服务限流器实战:从原理到压测验证 --使用 Gin 框架 + Uber Ratelimit / 官方限流器,并通过 Vegeta 进行性能剖析
后端
程途知微9 小时前
JVM运行时数据区各区域作用与溢出原理
java
孟沐9 小时前
保姆级教程:手写三层架构 vs MyBatis-Plus
后端
星浩AI9 小时前
让模型自己写 Skills——从素材到自动生成工作流
人工智能·后端·agent
华仔啊11 小时前
为啥不用 MP 的 saveOrUpdateBatch?MySQL 一条 SQL 批量增改才是最优解
java·后端
武子康12 小时前
大数据-242 离线数仓 - DataX 实战:MySQL 全量/增量导入 HDFS + Hive 分区(离线数仓 ODS
大数据·后端·apache hive
砍材农夫12 小时前
TCP和UDP区别
后端