首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

高并发场景,常用的Java限流接口实现

Java 限流接口是一种用于控制系统中流量的工具,它可以限制系统在一个设定的时间窗口内处理的请求数量,以保护系统免受过度的请求压力。

为什么要限流?

在一个高并发的系统中,当大量请求同时到达系统时,如果系统无法合理地处理这些请求,可能会导致系统资源被耗尽,甚至崩溃。限流的目的就是为了控制并发请求的数量,防止系统因为过度的请求压力而崩溃。

限流可以带来以下好处:

保护系统资源:限流可以保护有限的系统资源不被过度占用,确保系统能够正常提供服务。

提供公平性:限流可以让系统能够公平地处理请求,防止某些请求占用过多的资源导致其他请求被拒绝或延迟。

防止恶意攻击:通过限制每个用户的请求频率,可以有效防止恶意攻击或恶意刷接口。

如何实现?

常见的 Java 限流实现方式有以下几种:

计数器算法:基于固定窗口的请求计数,当请求数量超过阈值时进行限流。可以使用 AtomicLong 来实现计数器,结合定时任务每秒重置窗口内的计数器。

漏桶算法:漏桶算法通过一个固定容量的漏桶来控制请求的流量。每个请求按照固定的速率往桶中滴水,请求无法被处理时会丢弃。可以使用队列和定时任务来实现漏桶算法。

令牌桶算法:令牌桶算法与漏桶算法类似,但是桶中的令牌容量是固定的。每个请求需要从令牌桶中获取一个令牌才能被处理,当令牌桶为空时,请求被拒绝。可以使用队列和定时任务来实现令牌桶算法。

实现原理:

1.计数器算法

public class RateLimiter {

 private AtomicLong counter = new AtomicLong(0);

 private int limit;

 private int interval;

 public RateLimiter(int limit, int interval) {

   this.limit = limit;

   this.interval = interval;

   // 定时任务,每秒重置计数器

   ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

   scheduler.scheduleAtFixedRate(this::resetCounter, 0, interval, TimeUnit.SECONDS);

 }

 public boolean allowRequest() {

   long currentCount = counter.incrementAndGet();

   if (currentCount > limit) {

     return false;

   }

   return true;

 }

 private void resetCounter() {

   counter.set(0);

 }}

在上述代码中,我们使用了 AtomicLong 来实现计数器,通过调用allowRequest()方法来判断当前请求是否允许被处理。每秒钟定时任务会重置计数器,这样就限制了每秒钟处理的请求数量。

计数器算法实现简单粗暴,单机在Java中可用Atomic等原子类或Semaphore,分布式就用Redis incr。这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题。

假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。

我们刚才规定的是1分钟最多100个请求(规划的吞吐量),也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

2.漏桶算法

漏桶算法是另一种经典的限流算法之一,它的实现原理是通过一个固定容量的漏桶来控制请求的流量。漏桶的容量就像一根固定大小的水管,请求被视为流入水管的水,水根据一定速率(容量大小)流出水管。如果水管满了,水就会溢出,这些超出限制的请求将会被丢弃或拒绝处理。

漏桶的主要实现思路是在固定的时间内,让一个漏桶按照确认容量流出,若请求流入漏桶时漏桶已满,则拒绝这个请求。

public class LeakyBucketLimiter {

   private final int maxBucketSize;//滴漏速率,即每秒钟最多能处理的请求个数

   private final double leakRate;//桶内漏水速度,漏水速度会影响桶内剩余的水量

   private volatile int currentBucketSize;//当前桶中的水量

   private volatile long lastLeakTime;//上一次漏水的时间

   public LeakyBucketLimiter(int maxBucketSize, double leakRate) {

       this.maxBucketSize = maxBucketSize;

       this.leakRate = leakRate;

       this.currentBucketSize = 0;

       this.lastLeakTime = System.currentTimeMillis();

   }

   /**

    * 尝试获取请求处理的权限,返回true表示允许,返回false表示拒绝

    */

   public synchronized boolean tryAcquire(int requestCount) {

       long now = System.currentTimeMillis();

       double leakedWater = ((now - lastLeakTime) / 1000.0) * leakRate;

       if (currentBucketSize + leakedWater > maxBucketSize) {

           //桶已满,拒绝处理请求

           return false;

       }

       currentBucketSize += leakedWater;

       currentBucketSize = Math.min(currentBucketSize + requestCount, maxBucketSize);

       lastLeakTime = now;

       return true;

   }}

漏桶算法的优点是可以平滑处理请求流量,并且可以设置一个固定的处理速度,即漏桶的容量大小,保证每秒钟最多能处理的请求个数。缺点是在短时间内会拒绝大量的请求,因此可能会影响一些特定的业务场景的实现。

3.令牌桶算法

令牌桶算法是另一种常见的限流算法,类似于漏桶算法,但它与漏桶算法实现上有所不同。令牌桶算法将固定容量的桶划分为宽度固定的小块(令牌),请求需要使用令牌才能被处理。每秒钟会往桶中投放若干个令牌,每个令牌就代表一个请求,如果桶中的令牌被取完了,那么后面的请求就会直接被拒绝或被阻塞,直到有足够的令牌才能被处理。

public class TokenBucketLimiter {

   private int maxTokenSize;//桶的大小,表示最多存储的令牌个数

   private double rate;//令牌的投放速度,表示每毫秒允许添加的令牌数

   private volatile int currentTokenCount;//当前令牌数

   private volatile long lastTokenTime;//上次填充令牌时间

   public TokenBucketLimiter(int maxTokenSize, double rate) {

       this.maxTokenSize = maxTokenSize;

       this.rate = rate;

       this.currentTokenCount = maxTokenSize;

       this.lastTokenTime = System.currentTimeMillis();

   }

   /**

    * 尝试获取请求处理的权限,返回true表示允许,返回false表示拒绝

    */

   public synchronized boolean tryAcquire() {

       long now = System.currentTimeMillis();

       double newTokens = (now - lastTokenTime) * rate;

       if (newTokens > 0) {

           currentTokenCount = Math.min(currentTokenCount + (int) newTokens, maxTokenSize);

           lastTokenTime = now;

       }

       if (currentTokenCount > 0) {

           currentTokenCount--;

           return true;

       }

       return false;

   }}

令牌桶算法的优点是可以在一定程度上适应突发流量的处理,能够快速响应。而且它允许在延迟较大的情况下,一定程度上支持突发流量处理。令牌桶算法还能够平滑地处理请求,限制了请求的处理速度,提供了更好的流量控制。缺点是对短时间突发流量的处理相对较差,可能会出现短期内无法处理大量请求的情况。漏桶算法和令牌桶算法都是有效的限流算法,适用于不同的业务场景。

计数器算法简单易用,但是有临界问题。漏桶算法可以平滑处理请求流量,但可能会在短时间内拒绝大量的请求。令牌桶算法可以在一定程度上适应突发流量的处理,但可能在短时间内无法处理大量请求。根据具体业务需求和场景特点,选择合适的算法来实现限流策略。

限流是一种重要的技术手段,可以保护系统免受过度的请求压力。在实际的应用开发中,根据具体的业务场景和需求选择适合的限流算法来实现,并根据系统的实际情况设置合理的限流参数。通过合理的限流策略,可以确保系统的稳定性和可靠性。

>>>更多开发技术资源、资讯请关注访问【昂焱数据】

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OMaAx63niFmV0ca9H-fUIbuw0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券