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

结论

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

相关推荐
禁默14 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood20 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑23 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528726 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶27 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
zfoo-framework34 分钟前
【jenkins插件】
java
风_流沙40 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
颜淡慕潇1 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构