2013-02-01 2 views
3

업데이트을 thottling : 나는 내가 단지 방법을 분당 80 번 호출 할 수 있습니까 시나리오가 자바 7자바 메커니즘을

로 업그레이드없이 기회 자바 1.6.34에있어. 나는이있는 ApiThrottler 클래스를 작성하고 싶습니다

public class WidgetService { 
    // Can only call this method 80x/min, otherwise it 
    // it just doesn't do anything 
    public void doSomething(Fizz fizz); 
} 

: 당신은 너무 많은 시간을 호출하는 경우는 API를 (호출을 무시) 사실은 제 3 자에 의해 작성된 서비스 API이고, 그것은 "종료" boolean canRun() 메서드를 사용하여 Java 클라이언트에 doSomething(Fizz) 메서드를 호출 할 수 있는지 여부를 알릴 수 있습니다. (물론 항상 라고 할 수 있지만, 우리는 우리의 속도를 초과 한 경우를 호출하는 것은 의미가 없습니다.)

그래서 나를 이렇게 같은 코드를 작성할 수 있습니다 무언가 :

// 80x/min 
ApiThrottler throttler = new ApiThrottler(80); 

WidgetService widgetService = new DefaultWidgetService(); 

// Massive list of Fizzes 
List<Fizz> fizzes = getFizzes(); 

for(Fizz fizz : fizzes) 
    if(throttler.canRun()) 
     widgetService.doSomething(fizz); 

이것은 반드시 API (ApiThrottler#canRun) 일 필요는 없지만 그럼에도 불구하고 WidgetService#doSomething(Fizz)까지까지 일시 중지/잠자기 상태가 될 수있는 견고한/안정적인 메커니즘이 필요합니다.

이것은 우리가 우리가 메커니즘 및 자바 통지 (wait()/notify()) 모델을 잠금 어떤 종류를 사용할 수처럼 나를을 느낄 하게하는, 다중 스레드를 사용하는 영역으로 향하고있는 것처럼 나를을 느낄 있습니다. 그러나이 분야에 대한 경험이 없기 때문에 가장 세련된 솔루션을 둘러 볼 수는 없습니다. 미리 감사드립니다.

답변

5

아마도 가장 좋은 옵션 중 하나는 세마포어 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html 클래스를 사용하고 매분 80 회의 허가를 부여하는 것입니다. 타이머 클래스 http://docs.oracle.com/javase/7/docs/api/java/util/Timer.html을 사용하여이를 수행 할 수 있습니다. 호출자 스레드는 세마포어에서 acquire()를 호출하여 서비스에 대한 호출을 수행 할 때마다 사용권을 소비하며, 모든 허가가 이미 배출되면 차단합니다.

언급 한 바와 같이 wait/notify 및 정수 카운터를 타이머 또는 별도의 스레드를 사용하여 코딩 할 수는 있지만 더 복잡한 java.util.concurrent API 사용에 비해 더 복잡합니다. 위에서 설명한대로.

그것은 다음과 가까이 볼 수 있습니다 :

class Throttler implements TimerTask { 
    final Semaphore s = new Semaphore(80); 
    final Timer timer = new Timer(true); 

    Throttler() { 
    timer.schedule(this, 0, 60*1000); //schedule this for 1 min execution 
    } 

    run() { //called by timer 
    s.release(80 - s.availablePermits()); 
    } 

    makeCall() { 
    s.acquire(); 
    doMakeCall(); 
    } 

} 

이 또한 심지어 더 좋은 솔루션은 구아바에서 com.google.common.util.concurrent.RateLimiter을 사용하는 것입니다

자바 5에서 시작하여 작동합니다. 그것은 다음과 같이 할 수 있습니다

class Throttler { 
    final RateLimiter rateLimiter = RateLimiter.create(80.0/60.0); 

    makeCall() { 
    rateLimiter.acquire(); 
    doMakeCall(); 
    } 
} 

의미가 RateLimiter 대부분의 아마 당신의 상황에 더 잘 맞는 것으로, 세마포어 솔루션에 비해 약간 다릅니다.

+0

감사합니다 @hgrey (+1) - 의사 코드를 볼 기회가 있습니까? 나는 호출자 스레드가 어떻게 생겼는지, 타이머 스레드와 통신하는 방법을 시각화하는 데 어려움을 겪고 있습니다. 다시 한 번 감사드립니다! – IAmYourFaja

+0

니스! 다시 한 번 감사드립니다. 마지막 하나의 후속 조치 :'doMakeCall'은 내 'widgetService.doSomething (Fizz)'호출을 수행하는 곳이라고 가정합니다. 그리고 '호출자'를 1 분 실행하도록 예약하려면 어떤 Java 클래스를 사용해야합니까? 지금까지 모든 위대한 도움에 다시 한번 감사드립니다! – IAmYourFaja

+0

예, domakeCall – hgrey

1

나는 최근에 이렇게 썼습니다. 유일한 변경 사항은 함수가 실행 완료 될 때 콜백이 필요하다는 것입니다. 따라서 실행이 불가능하면 콜백을 직접 호출합니다.

추가 변경 사항은이 호출이 아마도 비동기식이기 때문에 두 번째 호출시 호출이 진행 중일 수 있다는 것입니다. 그런 경우에는 방금 전화를 무시했습니다.

내 throttler에는 call이라는 헬퍼 함수가 있으며이 함수는 호출 할 함수와 콜백을 사용합니다. 이것은 C++에서 제공되었으므로 Action 및 Listener 인터페이스의 형태가됩니다.

이 방법은 Semaphore 기반 솔루션에 비해 요청을 순차적으로 처리하므로을 너무 자주 호출하지 않아도됩니다. 이 작업이 완료되면

interface Callback{ 
    public void OnFunctionCalled(); 
} 

class APIThrottler 
    //ctor etc 
    boolean CanCall(); 

    public boolean IsInProgress(); 

    public void SetInProgress(boolean inProgress = true); 

    public void Mark(){/*increment counter*/; SetInProgress(false);} // couldnt think of a better name.. 

    public void Call(Callable event, Callback callback){ 
     If(IsInProgress()) 
      return; 
     else if(CanCall()) 
     { 
      SetInProgress(); 
      event.Call(); 
     } 
     else 
      callback.OnFunctionCalled(); 

    } 
} 

는 함수 콜백을 실시하면 (또는 함수 자체가 동기 인 경우에서) 당신은 Mark()해야합니다.

이것은 대부분 구현 한 것이며 차이점은 x 초 (또는 분)에 한 번만 처리했다는 것입니다.

+0

좋은 해결책입니다. +1 – EJP

+0

감사합니다. @Karthik T (+1) - 일부 유사 코드로 답변을 업데이트 할 수 있도록 API가 어떻게 생겼는지를 알 수 있습니까? 다시 한 번 감사드립니다! – IAmYourFaja

+0

2 + 1s하지만 1 개의 실제 upvote를 봅니다 : P. 어떤 의사 코드를 쓸 수 있는지 알아 봅시다.Java에 대한 내 친숙한 익숙하지 않은 사람을 용서해주세요. –

0

간단한 카운터를 사용하여이 작업을 수행 할 수 있습니다. "permit"카운터를 80으로 초기화하자. "함수"를 호출하기 전에 먼저 허가 취득을 시도한다. 끝나면 놓아주세요. 그런 다음 카운터를 매초 80 초로 재설정하는 반복 타이머를 설정하십시오.

class Permit extends TimerTask 
{ 
    private Integer permit = 80; 

    private synchronized void resetPermit() { this.permit = 80; } 

    public synchronized boolean acquire() { 
     if(this.permit == 0) return false; 
     this.permit--; 
     return true; 
    } 

    public synchronized void release() { this.permit++; } 

    @Override public void run() { resetPermit(); } 
} 

이렇게 매 초마다 허용을 재설정하는 타이머를 설정하십시오. schedule 메소드의 첫 번째 매개 변수는 TimerTask의 인스턴스입니다 (위의 Permit 클래스 객체 전달). run() 메소드는 두 번째 인수 (여기서는 1000 밀리 초/1 초)에 지정된 모든 기간에 대해 호출됩니다. 'true'인수는 이것이 반복되는 데몬 타이머임을 나타냅니다.

Timer timer = new Timer(true); 
timer.schedule(permit, 1000); 

다음 함수를 호출해야 할 때마다 먼저 허가를 얻을 수 있는지 확인하십시오. 당신이

void myFunction() { 
    if(!permit.acquire()) { 
     System.out.println("Nah.. been calling this 80x in the past 1 sec"); 
     return; 
    } 

    // do stuff here 

    permit.release(); 
} 

참고 위의 허가 클래스 메소드에 synchronized 키워드의 사용을 완료 할 때 해제하는 것을 잊지 마세요 -이 1 개 이상의 스레드가 동시에 동일한 개체 인스턴스의 방법 중 하나를 실행하지 않도록해야합니다

0

시간의 기록을 유지할 수 있으며 마지막 순간에 최대 80 개의 레코드를 만들 수 있습니다.

// first=newest, last=oldest 
final LinkedList<Long> records = new LinkedList<>(); 

synchronized public void canRun() throws InterruptedException 
{ 
    while(true) 
    { 
     long now = System.currentTimeMillis(); 
     long oneMinuteAgo = now - 60000; 

     // remove records older than one minute 
     while(records.getLast() < oneMinuteAgo) 
      records.removeLast(); 

     if(records.size()<80) // less than 80 records in the last minute 
     { 
      records.addFirst(now); 
      return; // can run 
     } 

     // wait for the oldest record to expire, then check again 
     wait(1 + records.getLast() - oneMinuteAgo); 
    } 
} 

0 번째 두 번째로, 우리는 80 호출을 발행 할 수 있으며, 다음 60 초에, 우리는 또 다른 80 개 통화를 발행, 분을 기다립니다. 다른 끝은 양 끝의 시계 부정확성 또는 네트워크 임의 지연으로 인해 1 분 안에 160 건의 전화를 측정 할 수 있습니다. 안전을 위해 시간대를 확대하십시오. 대신 70 초당 80 회의 통화로 스로틀을 실시하십시오.

관련 문제