면접관 : 소스 코드 분석을 -RateLimiter 제한에 대해 이야기

RateLimiter두 가지 구현 클래스입니다 SmoothBurstySmoothWarmingUp점을 제외하고는 토큰 버킷 알고리즘 구현 변형, SmoothBursty토큰의 추가 속도가 일정, 그리고 SmoothWarmingUp따뜻한 기간이있을 것이다, 예열 시간 토큰 플러스 느린 속도로 일정 속도에 도달 할 때까지 천천히 증가. 그것의 응용 시나리오는, QPS를 시작할 최적의 조건을 달성하기 위해 일정 시간 후에 따뜻하게 견딜 수있는 시스템 작다.

 

기본 사용

사용 RateLimiter 매우 간단 : 

//이 방법 만들기 당 생성 수신 토큰 개수 
RateLimiter rateLimiter RateLimiter.create = (1. )
 에 대해 ( int로 I = 0; I는 <5; I는 ++. ) {
     // 획득 방법 필요한 들어오는 토큰의 수, 불충분 한 토큰 기다리고있을 때, 프로세스가 시간을 기다려야 반환이 
    두 번 있는 waittime rateLimiter.acquire = (1. ) 
    에서 System.out.println (에 System.currentTimeMillis () / 1000 ","+ 있는 waittime) 
}

 

다음과 같이 출력은 다음과 같습니다

1,548,070,953 0.0 
1,548,070,954, 0.998356 
1,548,070,955, 0.998136 
1,548,070,956, 0.99982

 

이 불충분 한 토큰의 경우, 그 참고 acquire방법이 호출을 차단하지 않습니다,하지만 머리의 다음 호출에서 계산됩니다. 두 번째 전화가 1 초 블로킹 예를 들어, 첫 번째 통화 토큰 버킷 아닌 토큰 있지만, 첫 번째 통화를 차단하지만 않았다. 즉, 토큰에서 각각의 호출은 (토큰 버킷이 부족한 경우) 법안을 발하기 위해 다음 호출을 확인하는 것입니다.

RateLimiter rateLimiter = RateLimiter.create (1 );
이중 있는 waittime rateLimiter.acquire = (1,000 ); 
에서 System.out.println (에 System.currentTimeMillis () / 1000 + ','+ 있는 waittime); 
있는 waittime = rateLimiter.acquire (1 ); 
에서 System.out.println (에 System.currentTimeMillis () / 1000 + ','+있는 waittime);

 

다음과 같이 출력은 다음과 같습니다

1,548,072,250 0.0 
1,548,073,250, 999.998773

 

이 디자인의 목적은 다음과 같습니다 :

Last, but not least: consider a RateLimiter with rate of 1 permit per second, currently completely unused, and an expensive acquire(100) request comes. It would be nonsensical to just wait for 100 seconds, and /then/ start the actual task. Why wait without doing anything? A much better approach is to /allow/ the request right away (as if it was an acquire(1) request instead), and postpone /subsequent/ requests as needed. In this version, we allow starting the task immediately, and postpone by 100 seconds future requests, thus we allow for work to get done in the meantime instead of waiting idly.

각 요청을 기반으로 지불이 불필요한 대기 시간이 될 것입니다 경우 간단히했습니다. 예를 들어, 초당 1, 처음에 어떤 토큰 버킷의 속도 토큰 증가는 다음 태스크 대기 100S를 시작하기 위해 필요, 요청이 100 토큰을 필요로했다. 따라서, 더 좋은 방법은 먼저 요청, 지연 후 다음 요청을 해제하는 것입니다.

또한, RateLimiter는있다 tryAcquire, 그렇지 않으면 즉시 false를 돌려 즉시 충분한 토큰 경우 메소드가 true를 반환합니다.

소스 코드 분석

이 문서에서는 분석 SmoothBursty구현을.

처음 보면 SmoothBursty몇 가지 주요 분야 :

// 桶中最多存放多少秒的令牌数
final double maxBurstSeconds;
//桶中的令牌个数
double storedPermits;
//桶中最多能存放多少个令牌,=maxBurstSeconds*每秒生成令牌个数
double maxPermits;
//加入令牌的平均间隔,单位为微秒,如果加入令牌速度为每秒5个,则该值为1000*1000/5
double stableIntervalMicros;
//下一个请求需要等待的时间
private long nextFreeTicketMicros = 0L;

RateLimiter 생성

방법을 만들 RateLimiter의 창조 봐.

// permitsPerSecond 초당 토큰의 수를 생성하는 
공공  정적 RateLimiter을 (만들기 더블 {permitsPerSecond를)
     반환 (permitsPerSecond, SleepingStopwatch.createFromSystemTimer는 ()) 만들기; 
} 

// SleepingStopwatch 주로 타이밍에 사용 잠 
정적 RateLimiter이 (작성 더블 permitsPerSecond, SleepingStopwatch을 스톱워치) {
     // SmoothBursty 생성 
    rateLimiter rateLimiter = 새로운 새 SmoothBursty (스톱워치, 1.0 / * maxBurstSeconds * / ) 
    rateLimiter.setRate (permitsPerSecond)는, 
    반환 rateLimiter 단계; 
}
주요 메소드를 작성하는 것은 SmoothBursty 인스턴스를 생성하고 setRate 메소드를 호출하는 것입니다. 여기에 참고 1. maxBurstSeconds는 코드 0 . 

@Override 
최종  무효 doSetRate ( 더블 permitsPerSecond, nowMicros) { 
    재 동기화 (nowMicros) 
    더블 stableIntervalMicros = SECONDS.toMicros (1L) / permitsPerSecond;
     은이 .stableIntervalMicros의 =의 stableIntervalMicros, 
    doSetRate (permitsPerSecond, stableIntervalMicros); 
} 

무효 (재 동기화를 nowMicros를) {
     // 현재 시간이 nextFreeTicketMicros보다 큰 경우, 지침은 기다리지 않고, 토큰 요청이 아니라 보완 된이 요청을 빚 
    IF (nowMicros>nextFreeTicketMicros가) {
       // 토큰을 추가하는 데 필요한 시간을 계산, coolDownIntervalMicros는 stableIntervalMicros 반환 
      더블 newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros을 ();
      // maxPermits의 것보다 더 이상 갱신 토큰 버킷, 
      = 분 storedPermits ( maxPermits, storedPermits + newPermits)
       // 여기서 제 1 설정 nowMicros 
      nextFreeTicketMicros = 용의 nowMicros; 
    } 
} 

@Override 
무효화 doSetRate ( 더블 permitsPerSecond, 더블 stableIntervalMicros)을 {
     더블 oldMaxPermits = 이 본 .maxPermits는; 
    maxPermitsMaxBurstSeconds * = permitsPerSecond;
     IF (oldMaxPermits == Double.POSITIVE_INFINITY를 같은) {
         // 우리는 특별한 경우이-케이스, 우리가 얻을하지 storedPermits ==겠습니까 NaN3를 아래 
        storedPermits = maxPermits; 
    } 다른 {
         // 첫 번째 호출의 oldMaxPermits 0 storedPermits (토큰 버킷의 수)도 0 
        storedPermits = 
                (oldMaxPermits == 0.0 )
                         0.0? // 초기 상태 
                        : storedPermits * maxPermits / oldMaxPermits; 
    } 
}

 

setRate방법은 세트 maxPermits=maxBurstSeconds * permitsPerSecond와; maxBurstSeconds되도록 1 maxBurstSeconds 토큰의 개수는 1 초에 저장된다.

합니다 SmoothBursty만을 통해 비공개 클래스한다는 RateLimiter.create생성 방법 및이 방법은 maxBurstSeconds 우리가 단지 버킷 크기를 만들 수 있다는 것을 의미 1.0의 죽음을 작성하는 것입니다이다의 permitsPerSecond * 1 SmoothBursty물론,하지 반사의 방법으로 객체 ( 범위)이 구아바의 GitHub의 창고에서 몇 가지 문제 (이다 issue1 , 2 호 , issue3 , issue4하는 ) 외부 설정하고자 maxBurstSeconds 하지만, 관리의 반환을 보지 못했다. 오픈 소스 프로젝트에서 유일한 제품이됩니다 vjtools 가이 제기되었다, 질문을 학생들이 RateLimiter가되었다되어 구아바의 유일한 제품이 될 것입니다, 확장 .

내가 이해하지 못했다이 디자인 구아바를 들어, ~ 맑은 친구가 다음을 말할 수있다

중 파 SmoothBursty객체가 생성되고, 우리는 그 분석 acquire방법을.

방법을 습득

public double acquire(int permits) {
    // 计算本次请求需要休眠多久(受上次请求影响)
    long microsToWait = reserve(permits);
    // 开始休眠
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
 
final long reserve(int permits) {
    checkPermits(permits);
    synchronized (mutex()) {
      return reserveAndGetWaitLength(permits, stopwatch.readMicros());
    }
}

final long reserveAndGetWaitLength(int permits, long nowMicros) {
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    return max(momentAvailable - nowMicros, 0);
}

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    // 这里调用了上面提到的resync方法,可能会更新桶中的令牌值和nextFreeTicketMicros
    resync(nowMicros);
    // 如果上次请求花费的令牌还没有补齐,这里returnValue为上一次请求后需要等待的时间,否则为nowMicros
    long returnValue = nextFreeTicketMicros;
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    // 缺少的令牌数
    double freshPermits = requiredPermits - storedPermitsToSpend;
    // waitMicros为下一次请求需要等待的时间;SmoothBursty的storedPermitsToWaitTime返回0
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);
    // 更新nextFreeTicketMicros
    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    // 减少令牌
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
}

 

acquire中会调用reserve方法获得当前请求需要等待的时间,然后进行休眠。reserve方法最终会调用到reserveEarliestAvailable,在该方法中会先调用上文提到的resync方法对桶中的令牌进行补充(如果需要的话),然后减少桶中的令牌,以及计算这次请求欠的令牌数及需要等待的时间(由下次请求负责等待)。

如果上一次请求没有欠令牌或欠的令牌已经还清则返回值为nowMicros,否则返回值为上一次请求缺少的令牌个数*生成一个令牌所需要的时间。

End

本文讲解了RateLimiter子类SmoothBursty的源码,对于另一个子类SmoothWarmingUp的原理大家可以自行分析。相对于传统意义上的令牌桶,RateLimiter的实现还是略有不同,主要体现在一次请求的花费由下一次请求来承担这一点上。

本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

추천

출처www.cnblogs.com/yuxiang1/p/11347015.html