Java 非阻塞速率受限线程池执行器

Java 非阻塞速率受限线程池执行器,java,threadpoolexecutor,rate-limiting,Java,Threadpoolexecutor,Rate Limiting,我使用多个连接同时访问HTTP服务器。我想对客户端进行节流,以响应服务器指示请求进入速度过快的情况。我不想改变我正在使用的HTTP库,而是想扩展它 为此,如何在以下约束条件下实现 执行者有一个可选的利率限制 禁用速率限制时,它会尽快执行任务(ThreadPoolExecutor的正常行为) 启用速率限制时,每秒最多可执行N任务 速率限制适用于所有执行器线程,而不是每个线程 不允许爆发。也就是说,如果限制为每秒10个请求,我希望每100ms开始一个请求。我不希望所有线程同时启动,然后在第二个线程

我使用多个连接同时访问HTTP服务器。我想对客户端进行节流,以响应服务器指示请求进入速度过快的情况。我不想改变我正在使用的HTTP库,而是想扩展它

为此,如何在以下约束条件下实现

  • 执行者有一个可选的利率限制
  • 禁用速率限制时,它会尽快执行任务(ThreadPoolExecutor的正常行为)
  • 启用速率限制时,每秒最多可执行
    N
    任务
  • 速率限制适用于所有执行器线程,而不是每个线程
  • 不允许爆发。也就是说,如果限制为每秒10个请求,我希望每100ms开始一个请求。我不希望所有线程同时启动,然后在第二个线程的剩余时间保持空闲
  • 利率限制是动态的。如果请求失败,速率将降低。如果请求成功,则速率会增加
  • 当没有任务准备好执行时(考虑到速率限制),线程被认为是空闲的。也就是说,我希望
    ThreadPoolExecutor
    将这些线程标记为空闲,并根据需要将其降速,而不是在达到速率限制之前阻塞线程。另一方面,线程应该在执行下一个任务时再次旋转
我所调查的
  • 假设执行延迟在事件排队时已知,而在我的例子中,速率可能在任务排队和执行之间变化
  • 这可能是答案的一部分,但仅凭这一点还不够

    • 回答我自己的问题:

      • 不可能有一个完全无阻塞的解决方案。即使是
        ScheduledThreadPoolExecutor
        也会让至少一个线程等待队列返回新任务
      • ThreadPoolExecutor
        位于
        阻塞队列的顶部。当没有剩余任务时,它会阻塞
        BlockingQueue.take()
      • 该解决方案有3个活动部分:
    • 限速器
    • 一种
      阻塞队列
      ,在速率限制器允许使用元素之前隐藏元素
    • 位于
      阻塞队列
      顶部的
      线程池执行器
    • 限速器 我根据算法提供自己的费率限制器,以克服
      费率限制器
      。可以找到源代码


      封锁队列 我实现了一个
      BlockingDeque
      (它扩展了
      BlockingQueue
      ),因为将来我想尝试将失败的任务推回到队列前面

      RateLimitedBlockingDeque.java

      import java.time.Duration;
      import java.util.Collection;
      import java.util.Iterator;
      import java.util.NoSuchElementException;
      import java.util.concurrent.BlockingDeque;
      import java.util.concurrent.LinkedBlockingDeque;
      import java.util.concurrent.TimeUnit;
      import static org.bitbucket.cowwoc.requirements.core.Requirements.requireThat;
      
      /**
       * A blocking deque of elements, in which an element can only be taken when the deque-wide delay has expired.
       * <p>
       * The optional capacity bound constructor argument serves as a way to prevent excessive expansion. The capacity, if
       * unspecified, is equal to {@link Integer#MAX_VALUE}.
       * <p>
       * Even though methods that take elements, such as {@code take} or {@code poll}, respect the deque-wide delay the
       * remaining methods treat them as normal elements. For example, the {@code size} method returns the count of both
       * expired and unexpired elements.
       * <p>
       * This class and its iterator implement all of the <em>optional</em> methods of the {@link Collection} and {@link
       * Iterator} interfaces.
       *
       * @param <E> the type of elements in the deque
       * @author Gili Tzabari
       */
      public final class RateLimitedBlockingDeque<E> implements BlockingDeque<E>
      {
          private final int capacity;
          private final LinkedBlockingDeque<E> delegate;
          private final Bucket rateLimit = new Bucket();
      
          /**
           * Creates a {@code RateLimitedBlockingDeque} with a capacity of {@link Integer#MAX_VALUE}.
           */
          public RateLimitedBlockingDeque()
          {
              this.capacity = Integer.MAX_VALUE;
              this.delegate = new LinkedBlockingDeque<>();
          }
      
          /**
           * Creates a {@code RateLimitedBlockingDeque} with the given (fixed) capacity.
           *
           * @param capacity the capacity of this deque
           * @throws IllegalArgumentException if {@code capacity} is less than 1
           */
          public RateLimitedBlockingDeque(int capacity)
          {
              this.capacity = capacity;
              this.delegate = new LinkedBlockingDeque<>(capacity);
          }
      
          /**
           * @return the capacity of the deque
           */
          public int getCapacity()
          {
              return capacity;
          }
      
          /**
           * Indicates the rate at which elements may be taken from the queue.
           *
           * @param elements the number of elements that may be taken per {@code period}
           * @param period   indicates how often elements may be taken
           * @throws NullPointerException     if {@code period} is null
           * @throws IllegalArgumentException if the requested rate is greater than element per nanosecond
           */
          public void setRate(long elements, Duration period)
          {
              synchronized (rateLimit)
              {
                  Limit newLimit = new Limit(elements, period, 0, Long.MAX_VALUE);
                  if (rateLimit.getLimits().isEmpty())
                      rateLimit.addLimit(newLimit);
                  else
                  {
                      Limit oldLimit = rateLimit.getLimits().iterator().next();
                      rateLimit.replaceLimit(oldLimit, newLimit);
                  }
              }
          }
      
          /**
           * Allows consumption of elements without limit.
           */
          public void removeRate()
          {
              synchronized (rateLimit)
              {
                  rateLimit.removeAllLimits();
              }
          }
      
          @Override
          public void addFirst(E e)
          {
              delegate.addFirst(e);
          }
      
          @Override
          public void addLast(E e)
          {
              delegate.addLast(e);
          }
      
          @Override
          public boolean offerFirst(E e)
          {
              return delegate.offerFirst(e);
          }
      
          @Override
          public boolean offerLast(E e)
          {
              return delegate.offerLast(e);
          }
      
          @Override
          public void putFirst(E e) throws InterruptedException
          {
              delegate.putFirst(e);
          }
      
          @Override
          public void putLast(E e) throws InterruptedException
          {
              delegate.putLast(e);
          }
      
          @Override
          public boolean offerFirst(E e, long timeout, TimeUnit unit) throws InterruptedException
          {
              return delegate.offerFirst(e, timeout, unit);
          }
      
          @Override
          public boolean offerLast(E e, long timeout, TimeUnit unit) throws InterruptedException
          {
              return delegate.offerLast(e, timeout, unit);
          }
      
          @Override
          public E removeFirst()
          {
              if (rateLimit.tryConsume())
                  return delegate.removeFirst();
              throw new NoSuchElementException();
          }
      
          @Override
          public E removeLast()
          {
              if (rateLimit.tryConsume())
                  return delegate.removeLast();
              throw new NoSuchElementException();
          }
      
          @Override
          public E pollFirst()
          {
              if (rateLimit.tryConsume())
                  return delegate.pollFirst();
              return null;
          }
      
          @Override
          public E pollLast()
          {
              if (rateLimit.tryConsume())
                  return delegate.pollLast();
              return null;
          }
      
          @Override
          public E takeFirst() throws InterruptedException
          {
              rateLimit.consume();
              return delegate.takeFirst();
          }
      
          @Override
          public E takeLast() throws InterruptedException
          {
              rateLimit.consume();
              return delegate.takeLast();
          }
      
          @Override
          public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException
          {
              if (rateLimit.consume(1, timeout, unit))
                  return delegate.pollFirst(timeout, unit);
              return null;
          }
      
          @Override
          public E pollLast(long timeout, TimeUnit unit) throws InterruptedException
          {
              if (rateLimit.consume(1, timeout, unit))
                  return delegate.pollLast(timeout, unit);
              return null;
          }
      
          @Override
          public E getFirst()
          {
              return delegate.getFirst();
          }
      
          @Override
          public E getLast()
          {
              return delegate.getLast();
          }
      
          @Override
          public E peekFirst()
          {
              return delegate.peekFirst();
          }
      
          @Override
          public E peekLast()
          {
              return delegate.peekLast();
          }
      
          @Override
          public boolean removeFirstOccurrence(Object o)
          {
              return delegate.removeFirstOccurrence(o);
          }
      
          @Override
          public boolean removeLastOccurrence(Object o)
          {
              return delegate.removeLastOccurrence(o);
          }
      
          @Override
          public boolean add(E e)
          {
              return delegate.add(e);
          }
      
          @Override
          public boolean offer(E e)
          {
              return delegate.offer(e);
          }
      
          @Override
          public void put(E e) throws InterruptedException
          {
              putLast(e);
          }
      
          @Override
          public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException
          {
              return delegate.offer(e, timeout, unit);
          }
      
          @Override
          public E remove()
          {
              return removeFirst();
          }
      
          @Override
          public E poll()
          {
              return pollFirst();
          }
      
          @Override
          public E take() throws InterruptedException
          {
              return takeFirst();
          }
      
          @Override
          public E poll(long timeout, TimeUnit unit) throws InterruptedException
          {
              return pollFirst(timeout, unit);
          }
      
          @Override
          public E element()
          {
              return getFirst();
          }
      
          @Override
          public E peek()
          {
              return peekFirst();
          }
      
          @Override
          public int remainingCapacity()
          {
              return delegate.remainingCapacity();
          }
      
          @Override
          public int drainTo(Collection<? super E> c)
          {
              int result = 0;
              while (true)
              {
                  E next = pollFirst();
                  if (next == null)
                      break;
                  c.add(next);
              }
              return result;
          }
      
          @Override
          public int drainTo(Collection<? super E> c, int maxElements)
          {
              int result = 0;
              do
              {
                  E next = pollFirst();
                  if (next == null)
                      break;
                  c.add(next);
              }
              while (result < maxElements);
              return result;
          }
      
          @Override
          public void push(E e)
          {
              addFirst(e);
          }
      
          @Override
          public E pop()
          {
              return removeFirst();
          }
      
          @Override
          public boolean remove(Object o)
          {
              return removeFirstOccurrence(o);
          }
      
          @Override
          public int size()
          {
              return delegate.size();
          }
      
          @Override
          public boolean contains(Object o)
          {
              return delegate.contains(o);
          }
      
          @Override
          public Object[] toArray()
          {
              return delegate.toArray();
          }
      
          @Override
          public <T> T[] toArray(T[] a)
          {
              return delegate.toArray(a);
          }
      
          @Override
          public String toString()
          {
              return delegate.toString();
          }
      
          @Override
          public void clear()
          {
              delegate.clear();
          }
      
          @Override
          public Iterator<E> iterator()
          {
              return wrap(delegate.iterator());
          }
      
          /**
           * @param delegateIterator the iterator to delegate to
           * @return an iterator that respects the rate-limit
           */
          private Iterator<E> wrap(Iterator<E> delegateIterator)
          {
              return new Iterator<E>()
              {
                  private E previousElement = null;
      
                  @Override
                  public boolean hasNext()
                  {
                      return delegateIterator.hasNext();
                  }
      
                  @Override
                  public E next()
                  {
                      return delegateIterator.next();
                  }
      
                  @Override
                  public void remove()
                  {
                      if (previousElement == null)
                          throw new IllegalStateException("next() not invoked, or remove() already invoked");
                      try
                      {
                          rateLimit.consume();
                      }
                      catch (InterruptedException e)
                      {
                          throw new IllegalStateException(e);
                      }
                      delegateIterator.remove();
                      previousElement = null;
                  }
              };
          }
      
          @Override
          public Iterator<E> descendingIterator()
          {
              return wrap(delegate.descendingIterator());
          }
      
          @Override
          public boolean addAll(Collection<? extends E> c)
          {
              requireThat("c", c).isNotNull().isNotEqualTo("this", this);
              boolean modified = false;
              for (E e: c)
                  if (add(e))
                      modified = true;
              return modified;
          }
      
          @Override
          public boolean isEmpty()
          {
              return delegate.isEmpty();
          }
      
          @Override
          public boolean containsAll(Collection<?> c)
          {
              return delegate.containsAll(c);
          }
      
          @Override
          public boolean removeAll(Collection<?> c)
          {
              Iterator<E> i = iterator();
              boolean modified = true;
              while (i.hasNext())
              {
                  E element = i.next();
                  if (c.contains(element))
                  {
                      i.remove();
                      modified = true;
                  }
              }
              return modified;
          }
      
          @Override
          public boolean retainAll(Collection<?> c)
          {
              Iterator<E> i = iterator();
              boolean modified = true;
              while (i.hasNext())
              {
                  E element = i.next();
                  if (!c.contains(element))
                  {
                      i.remove();
                      modified = true;
                  }
              }
              return modified;
          }
      
          @Override
          public int hashCode()
          {
              return delegate.hashCode();
          }
      
          @Override
          public boolean equals(Object obj)
          {
              return delegate.equals(obj);
          }
      }
      
      import java.time.Duration;
      导入java.util.Collection;
      导入java.util.Iterator;
      导入java.util.NoSuchElementException;
      导入java.util.concurrent.BlockingDeque;
      导入java.util.concurrent.LinkedBlockingDeque;
      导入java.util.concurrent.TimeUnit;
      导入静态org.bitbucket.cowwoc.requirements.core.requirements.requireThat;
      /**
      *一种元素的阻塞数据,在这种数据块中,一个元素只有在数据块范围的延迟过期时才能被获取。
      *
      *可选的容量限制构造函数参数用作防止过度扩展的方法。容量,如果
      *未指定,等于{@link Integer#MAX_VALUE}。
      *
      *即使采用元素的方法,如{@code-take}或{@code-poll},也会尊重整个deque的延迟
      *其余方法将它们视为正常元素。例如,{@code size}方法返回这两个值的计数
      *过期和未过期的元素。
      *
      *这个类及其迭代器实现了{@link集合}和{@link集合}的所有可选方法
      *迭代器}接口。
      *
      *@param deque中元素的类型
      *@作者Gili Tzabari
      */
      公共最终类RateLimitedBlockingDeque实现BlockingDeque
      {
      私人最终int能力;
      私人最终LinkedBlockingDeque代表;
      私有最终桶费率限制=新桶();
      /**
      *创建容量为{@link Integer#MAX_VALUE}的{@code RateLimitedBlockingDeque}。
      */
      公共费率限制封锁区()
      {
      this.capacity=Integer.MAX_值;
      this.delegate=new LinkedBlockingDeque();
      }
      /**
      *创建具有给定(固定)容量的{@code RateLimitedBlockingDeque}。
      *
      *@param capacity此设备的容量
      *如果{@code capacity}小于1,@将引发IllegalArgumentException
      */
      公共费率限制封锁区(国际容量)
      {
      这个。容量=容量;
      this.delegate=新的LinkedBlockingDeque(容量);
      }
      /**
      *@返回deque的容量
      */
      公共int getCapacity()
      {
      返回能力;
      }
      /**
      *指示可以从队列中提取元素的速率。
      *
      *@param elements每个{@code period}可获取的元素数
      *@param period表示元素的提取频率
      *如果{@code period}为空,@将引发NullPointerException
      *@如果请求的速率大于每纳秒元素数,则引发IllegalArgumentException
      */
      公共无效设置率(长要素、持续时间)
      {
      已同步(速率限制)
      {
      Limit newLimit=新限制(元素、周期、0、长、最大值);
      if(rateLimit.getLimits().isEmpty())
      rateLimit.addLimit(新限制);
      其他的
      {
      Limit oldLimit=rateLimit.getLimits().iterator().next();
      rateLimit.replaceLimit(旧限制、新限制);
      }
      }
      }
      /**
      *允许无限制地使用元素。
      */
      公共空隙清除剂()
      {
      已同步(速率限制)
      {
      税率限制