N 초 안에 최대 M 호출까지 일부 메소드 실행을 조절하는 구성 요소 / 클래스가 필요합니다 (또는 ms 또는 nano는 중요하지 않습니다).
즉, N 초 슬라이딩 창에서 내 메소드가 M 번 이상 실행되지 않아야합니다.
기존 수업을 모르는 경우 솔루션 / 아이디어를 구현하는 방법을 자유롭게 게시하십시오.
N 초 안에 최대 M 호출까지 일부 메소드 실행을 조절하는 구성 요소 / 클래스가 필요합니다 (또는 ms 또는 nano는 중요하지 않습니다).
즉, N 초 슬라이딩 창에서 내 메소드가 M 번 이상 실행되지 않아야합니다.
기존 수업을 모르는 경우 솔루션 / 아이디어를 구현하는 방법을 자유롭게 게시하십시오.
답변:
고정 크기 M의 타임 스탬프 링 버퍼 를 사용합니다 . 메서드가 호출 될 때마다 가장 오래된 항목을 확인하고 과거에 N 초 미만인 경우 다른 항목을 실행하고 추가합니다. 시차.
나를 위해 즉시 사용 된 것은 Google Guava RateLimiter 입니다.
// Allow one request per second
private RateLimiter throttle = RateLimiter.create(1.0);
private void someMethod() {
throttle.acquire();
// Do something
}
tryAquire()
구체적으로,을 사용하여이를 구현할 수 있어야합니다 DelayQueue
. M
Delayed
지연 시간이 0으로 설정된 인스턴스를 사용 하여 큐를 초기화하십시오 . 메소드에 대한 요청이 들어 오면 take
토큰은 조절 요구 사항이 충족 될 때까지 메소드를 차단합니다. 토큰을 가져 오면 add
지연 시간이 대기열에있는 새 토큰입니다 N
.
offer
와 가능한 배열 성장 을 의미 함)를 사용하고 있기 때문에 특히 마음에 들지 않습니다 . 다른 사람들에게는 이것이 완벽하게 맞는 것 같습니다.
토큰 버킷 알고리즘을 읽으십시오 . 기본적으로 토큰이 들어있는 버킷이 있습니다. 메소드를 실행할 때마다 토큰을 가져옵니다. 더 이상 토큰이 없으면 토큰을 얻을 때까지 차단합니다. 한편, 일정한 간격으로 토큰을 보충하는 외부 행위자가 있습니다.
이 작업을 수행하는 라이브러리를 알지 못합니다. 이 논리를 코드에 쓰거나 AspectJ를 사용하여 동작을 추가 할 수 있습니다.
분산 시스템에서 작동하는 Java 기반 슬라이딩 윈도우 속도 제한 기가 필요한 경우 https://github.com/mokies/ratelimitj 프로젝트를 살펴볼 수 있습니다 .
요청을 분당 50 개로 제한하는 Redis 지원 구성은 다음과 같습니다.
import com.lambdaworks.redis.RedisClient;
import es.moki.ratelimitj.core.LimitRule;
RedisClient client = RedisClient.create("redis://localhost");
Set<LimitRule> rules = Collections.singleton(LimitRule.of(1, TimeUnit.MINUTES, 50)); // 50 request per minute, per key
RedisRateLimit requestRateLimiter = new RedisRateLimit(client, rules);
boolean overLimit = requestRateLimiter.overLimit("ip:127.0.0.2");
Redis 구성에 대한 자세한 내용 은 https://github.com/mokies/ratelimitj/tree/master/ratelimitj-redis를 참조 하십시오 .
응용 프로그램에 따라 다릅니다.
하는 경우 상상 여러 스레드가 몇 가지 할 수있는 토큰을 원하는 전 세계적으로 속도 - 제한 조치 와 허용 버스트 없음을 즉, 10 초 당 10 개 조치를 제한 할 (하지만 당신은 10 개 행동이 첫 번째, 두 번째에서 일하고자 다음 남아 있지 않습니다 9 초 중지).
DelayedQueue의 단점은 스레드 요청 토큰이 요청을 이행하는 순서가 아닐 수도 있다는 단점이 있습니다. 토큰을 기다리는 여러 스레드가 차단 된 경우 다음 스레드를 사용할 스레드가 확실하지 않습니다. 내 견해로는 스레드가 영원히 대기 할 수도 있습니다.
한 가지 해결책은 두 개의 연속 작업 사이에 최소 시간 간격을두고 요청한 순서와 동일한 순서로 작업을 수행하는 것입니다.
구현은 다음과 같습니다.
public class LeakyBucket {
protected float maxRate;
protected long minTime;
//holds time of last action (past or future!)
protected long lastSchedAction = System.currentTimeMillis();
public LeakyBucket(float maxRate) throws Exception {
if(maxRate <= 0.0f) {
throw new Exception("Invalid rate");
}
this.maxRate = maxRate;
this.minTime = (long)(1000.0f / maxRate);
}
public void consume() throws InterruptedException {
long curTime = System.currentTimeMillis();
long timeLeft;
//calculate when can we do the action
synchronized(this) {
timeLeft = lastSchedAction + minTime - curTime;
if(timeLeft > 0) {
lastSchedAction += minTime;
}
else {
lastSchedAction = curTime;
}
}
//If needed, wait for our time
if(timeLeft <= 0) {
return;
}
else {
Thread.sleep(timeLeft);
}
}
}
minTime
여기서 무엇을 의미합니까? 무엇을합니까? 그것에 대해 설명 할 수 있습니까?
minTime
다음 토큰을 사용하기 전에 토큰을 사용한 후 통과해야하는 최소 시간입니다.
요청한 내용이 아니지만 ThreadPoolExecutor
N 초 안에 M 요청 대신 M 개의 동시 요청을 처리하도록 설계된도 유용 할 수 있습니다.
간단한 스로틀 링 알고리즘을 구현했습니다. http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html 링크를 사용해보십시오
알고리즘에 대한 간략한 설명
이 알고리즘은 Java Delayed Queue 기능을 활용합니다 . 예상되는 지연 시간 (밀리 초 TimeUnit의 경우 1000 / M) 으로 지연된 오브젝트를 작성 하십시오 . 동일한 객체를 지연 대기열에 넣으면 인턴이 이동 창을 제공합니다. 그런 다음 각 메소드 호출 이 대기열에서 객체를 가져 오기 전에 지정된 지연 후에 만 반환되는 차단 호출이며 메소드 호출 후에 업데이트 된 시간 (여기서는 현재 밀리 초)으로 객체를 대기열에 넣는 것을 잊지 마십시오 .
여기서 지연이 다른 여러 지연된 객체를 가질 수도 있습니다. 이 방법은 또한 높은 처리량을 제공합니다.
아래의 구현은 임의의 요청 시간 정밀도를 처리 할 수 있으며 각 요청에 대해 O (1) 시간 복잡성을 가지며 O (1) 공간 복잡성과 같은 추가 버퍼가 필요하지 않으며 대신 토큰을 해제하는 데 백그라운드 스레드가 필요하지 않습니다. 토큰은 마지막 요청 이후 경과 된 시간에 따라 해제됩니다.
class RateLimiter {
int limit;
double available;
long interval;
long lastTimeStamp;
RateLimiter(int limit, long interval) {
this.limit = limit;
this.interval = interval;
available = 0;
lastTimeStamp = System.currentTimeMillis();
}
synchronized boolean canAdd() {
long now = System.currentTimeMillis();
// more token are released since last request
available += (now-lastTimeStamp)*1.0/interval*limit;
if (available>limit)
available = limit;
if (available<1)
return false;
else {
available--;
lastTimeStamp = now;
return true;
}
}
}
이 간단한 접근법을 사용하십시오.
public class SimpleThrottler {
private static final int T = 1; // min
private static final int N = 345;
private Lock lock = new ReentrantLock();
private Condition newFrame = lock.newCondition();
private volatile boolean currentFrame = true;
public SimpleThrottler() {
handleForGate();
}
/**
* Payload
*/
private void job() {
try {
Thread.sleep(Math.abs(ThreadLocalRandom.current().nextLong(12, 98)));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.print(" J. ");
}
public void doJob() throws InterruptedException {
lock.lock();
try {
while (true) {
int count = 0;
while (count < N && currentFrame) {
job();
count++;
}
newFrame.await();
currentFrame = true;
}
} finally {
lock.unlock();
}
}
public void handleForGate() {
Thread handler = new Thread(() -> {
while (true) {
try {
Thread.sleep(1 * 900);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
currentFrame = false;
lock.lock();
try {
newFrame.signal();
} finally {
lock.unlock();
}
}
}
});
handler.start();
}
}
Apache Camel 은 다음과 같이 Throttler 메커니즘을 지원 합니다.
from("seda:a").throttle(100).asyncDelayed().to("seda:b");
위의 LeakyBucket 코드에 대한 업데이트입니다. 이것은 초당 1000 개 이상의 요청에 대해 작동합니다.
import lombok.SneakyThrows;
import java.util.concurrent.TimeUnit;
class LeakyBucket {
private long minTimeNano; // sec / billion
private long sched = System.nanoTime();
/**
* Create a rate limiter using the leakybucket alg.
* @param perSec the number of requests per second
*/
public LeakyBucket(double perSec) {
if (perSec <= 0.0) {
throw new RuntimeException("Invalid rate " + perSec);
}
this.minTimeNano = (long) (1_000_000_000.0 / perSec);
}
@SneakyThrows public void consume() {
long curr = System.nanoTime();
long timeLeft;
synchronized (this) {
timeLeft = sched - curr + minTimeNano;
sched += minTimeNano;
}
if (timeLeft <= minTimeNano) {
return;
}
TimeUnit.NANOSECONDS.sleep(timeLeft);
}
}
위의 단위 테스트 :
import com.google.common.base.Stopwatch;
import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class LeakyBucketTest {
@Test @Ignore public void t() {
double numberPerSec = 10000;
LeakyBucket b = new LeakyBucket(numberPerSec);
Stopwatch w = Stopwatch.createStarted();
IntStream.range(0, (int) (numberPerSec * 5)).parallel().forEach(
x -> b.consume());
System.out.printf("%,d ms%n", w.elapsed(TimeUnit.MILLISECONDS));
}
}
minTimeNano
여기에 무슨 뜻입니까? 설명 할 수 있습니까?
간단한 속도 제한 기의 고급 버전입니다.
/**
* Simple request limiter based on Thread.sleep method.
* Create limiter instance via {@link #create(float)} and call {@link #consume()} before making any request.
* If the limit is exceeded cosume method locks and waits for current call rate to fall down below the limit
*/
public class RequestRateLimiter {
private long minTime;
private long lastSchedAction;
private double avgSpent = 0;
ArrayList<RatePeriod> periods;
@AllArgsConstructor
public static class RatePeriod{
@Getter
private LocalTime start;
@Getter
private LocalTime end;
@Getter
private float maxRate;
}
/**
* Create request limiter with maxRate - maximum number of requests per second
* @param maxRate - maximum number of requests per second
* @return
*/
public static RequestRateLimiter create(float maxRate){
return new RequestRateLimiter(Arrays.asList( new RatePeriod(LocalTime.of(0,0,0),
LocalTime.of(23,59,59), maxRate)));
}
/**
* Create request limiter with ratePeriods calendar - maximum number of requests per second in every period
* @param ratePeriods - rate calendar
* @return
*/
public static RequestRateLimiter create(List<RatePeriod> ratePeriods){
return new RequestRateLimiter(ratePeriods);
}
private void checkArgs(List<RatePeriod> ratePeriods){
for (RatePeriod rp: ratePeriods ){
if ( null == rp || rp.maxRate <= 0.0f || null == rp.start || null == rp.end )
throw new IllegalArgumentException("list contains null or rate is less then zero or period is zero length");
}
}
private float getCurrentRate(){
LocalTime now = LocalTime.now();
for (RatePeriod rp: periods){
if ( now.isAfter( rp.start ) && now.isBefore( rp.end ) )
return rp.maxRate;
}
return Float.MAX_VALUE;
}
private RequestRateLimiter(List<RatePeriod> ratePeriods){
checkArgs(ratePeriods);
periods = new ArrayList<>(ratePeriods.size());
periods.addAll(ratePeriods);
this.minTime = (long)(1000.0f / getCurrentRate());
this.lastSchedAction = System.currentTimeMillis() - minTime;
}
/**
* Call this method before making actual request.
* Method call locks until current rate falls down below the limit
* @throws InterruptedException
*/
public void consume() throws InterruptedException {
long timeLeft;
synchronized(this) {
long curTime = System.currentTimeMillis();
minTime = (long)(1000.0f / getCurrentRate());
timeLeft = lastSchedAction + minTime - curTime;
long timeSpent = curTime - lastSchedAction + timeLeft;
avgSpent = (avgSpent + timeSpent) / 2;
if(timeLeft <= 0) {
lastSchedAction = curTime;
return;
}
lastSchedAction = curTime + timeLeft;
}
Thread.sleep(timeLeft);
}
public synchronized float getCuRate(){
return (float) ( 1000d / avgSpent);
}
}
그리고 단위 테스트
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class RequestRateLimiterTest {
@Test(expected = IllegalArgumentException.class)
public void checkSingleThreadZeroRate(){
// Zero rate
RequestRateLimiter limiter = RequestRateLimiter.create(0);
try {
limiter.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void checkSingleThreadUnlimitedRate(){
// Unlimited
RequestRateLimiter limiter = RequestRateLimiter.create(Float.MAX_VALUE);
long started = System.currentTimeMillis();
for ( int i = 0; i < 1000; i++ ){
try {
limiter.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long ended = System.currentTimeMillis();
System.out.println( "Current rate:" + limiter.getCurRate() );
Assert.assertTrue( ((ended - started) < 1000));
}
@Test
public void rcheckSingleThreadRate(){
// 3 request per minute
RequestRateLimiter limiter = RequestRateLimiter.create(3f/60f);
long started = System.currentTimeMillis();
for ( int i = 0; i < 3; i++ ){
try {
limiter.consume();
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long ended = System.currentTimeMillis();
System.out.println( "Current rate:" + limiter.getCurRate() );
Assert.assertTrue( ((ended - started) >= 60000 ) & ((ended - started) < 61000));
}
@Test
public void checkSingleThreadRateLimit(){
// 100 request per second
RequestRateLimiter limiter = RequestRateLimiter.create(100);
long started = System.currentTimeMillis();
for ( int i = 0; i < 1000; i++ ){
try {
limiter.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long ended = System.currentTimeMillis();
System.out.println( "Current rate:" + limiter.getCurRate() );
Assert.assertTrue( (ended - started) >= ( 10000 - 100 ));
}
@Test
public void checkMultiThreadedRateLimit(){
// 100 request per second
RequestRateLimiter limiter = RequestRateLimiter.create(100);
long started = System.currentTimeMillis();
List<Future<?>> tasks = new ArrayList<>(10);
ExecutorService exec = Executors.newFixedThreadPool(10);
for ( int i = 0; i < 10; i++ ) {
tasks.add( exec.submit(() -> {
for (int i1 = 0; i1 < 100; i1++) {
try {
limiter.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}) );
}
tasks.stream().forEach( future -> {
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
long ended = System.currentTimeMillis();
System.out.println( "Current rate:" + limiter.getCurRate() );
Assert.assertTrue( (ended - started) >= ( 10000 - 100 ) );
}
@Test
public void checkMultiThreaded32RateLimit(){
// 0,2 request per second
RequestRateLimiter limiter = RequestRateLimiter.create(0.2f);
long started = System.currentTimeMillis();
List<Future<?>> tasks = new ArrayList<>(8);
ExecutorService exec = Executors.newFixedThreadPool(8);
for ( int i = 0; i < 8; i++ ) {
tasks.add( exec.submit(() -> {
for (int i1 = 0; i1 < 2; i1++) {
try {
limiter.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}) );
}
tasks.stream().forEach( future -> {
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
long ended = System.currentTimeMillis();
System.out.println( "Current rate:" + limiter.getCurRate() );
Assert.assertTrue( (ended - started) >= ( 10000 - 100 ) );
}
@Test
public void checkMultiThreadedRateLimitDynamicRate(){
// 100 request per second
RequestRateLimiter limiter = RequestRateLimiter.create(100);
long started = System.currentTimeMillis();
List<Future<?>> tasks = new ArrayList<>(10);
ExecutorService exec = Executors.newFixedThreadPool(10);
for ( int i = 0; i < 10; i++ ) {
tasks.add( exec.submit(() -> {
Random r = new Random();
for (int i1 = 0; i1 < 100; i1++) {
try {
limiter.consume();
Thread.sleep(r.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}) );
}
tasks.stream().forEach( future -> {
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
long ended = System.currentTimeMillis();
System.out.println( "Current rate:" + limiter.getCurRate() );
Assert.assertTrue( (ended - started) >= ( 10000 - 100 ) );
}
}
내 솔루션 : 간단한 util 메소드로 래퍼 클래스를 만들도록 수정할 수 있습니다.
public static Runnable throttle (Runnable realRunner, long delay) {
Runnable throttleRunner = new Runnable() {
// whether is waiting to run
private boolean _isWaiting = false;
// target time to run realRunner
private long _timeToRun;
// specified delay time to wait
private long _delay = delay;
// Runnable that has the real task to run
private Runnable _realRunner = realRunner;
@Override
public void run() {
// current time
long now;
synchronized (this) {
// another thread is waiting, skip
if (_isWaiting) return;
now = System.currentTimeMillis();
// update time to run
// do not update it each time since
// you do not want to postpone it unlimited
_timeToRun = now+_delay;
// set waiting status
_isWaiting = true;
}
try {
Thread.sleep(_timeToRun-now);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// clear waiting status before run
_isWaiting = false;
// do the real task
_realRunner.run();
}
}};
return throttleRunner;
}
자바 스레드 디 바운스 및 스로틀 에서 가져 오기