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