18 Haziran 2019 Salı

RateLimiter Sınıfı

Giriş
Şu satırı dahil ederiz.
import com.google.common.util.concurrent.RateLimiter;
Bu sınıfla ile kaynaklara erişime sınırlama konuluyor. Bu sınıfa aynı zamanda "Message Throttling" da deniliyor.

Kaynaklara Sınır Koymak
Rate Limiting Algorithms yazısına taşıdım

Guava Ne Kullanıyor?
Guava Spike Control Policy yöntemini kullanıyor. Kalıtım şöyle
RateLimiter <- SmoothRateLimiter <- SmoothWarmingUp 
                                                        <- SmoothBursty
Açıklaması şöyle
The configured rate is distributed evenly across the interval. (more like a sliding window)
Sınıfın Hatası Var
Açıklaması şöyle.
It seems like Guava RateLimiter can only work up to a limit of 1,000,000 aquire calls per second. If you try to do 1,000,001 aquire calls per second the call to aquire will not wait at all (return value of aquire() is always 0.0) -> no throttling occurs.
Under Utilization
Açıklaması şöyle
A SmoothRateLimiter has four member variables:

storedPermits - used to model past underutilization as described in Design section.
maxPermits - maximum number of storedPermits.
stableIntervalMicros - the interval between two requests at the stable rate. Its value should be 1 / rate.
nextFreeTicketMicros - the time when next permit can be granted. It is updated after each permit is granted.
Eğer SmoothRateLimiter sınıfındaki kod şöyle. Eğer under utilization varsa storedPersmit alanı en fazla maxPersmits olacak kadar tekrar dolduruluyor. storedPermits alanı doluysa, RateLimiter bu alan tekrar 0 oluncaya kadar acqiure() metodlarına hemen cevap veriyor. Yani beklemiyor. Bu da en bazı durumlarda RateLimiter sınıfının kendisini tekrar toparlayıncaya kadar bazı istekleri geçirdiği anlamına gelir.
void resync(long nowMicros) {
  // if nextFreeTicket is in the past, resync to now
  if (nowMicros > nextFreeTicketMicros) {
    double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
    storedPermits = min(maxPermits, storedPermits + newPermits);
    nextFreeTicketMicros = nowMicros;
  }
}
C++
Benzer bir şeyi C++ ile şöyle yaparız
#include <chrono>

class RateController {
  using clk_t        = std::chrono::system_clock;

public:
  RateController(unsigned limit, long time_slice_seconds)
      : n_(limit), time_slice_(time_slice_seconds) {}

  bool check() {
    auto duration_since_epoch = clk_t::now().time_since_epoch();
    long curr_time_slice =
        std::chrono::duration_cast<std::chrono::seconds>(duration_since_epoch).count() /
        time_slice_; // integer division!

    if (curr_time_slice != last_time_slice_) {
      last_time_slice_ = curr_time_slice;
      count_           = 0;
    }
    ++count_;
    return (count_ <= n_);
  }

private:
  unsigned n_;
  long     time_slice_;
  long     last_time_slice_ = 0;
  unsigned count_           = 0;
  ;
};

acquire metodu
Şöyle yaparız.
while (true) {
  rateLimiter.acquire();
  ...
}
create metodu - SmoothBursty
İmzası şöyle
static RateLimiter create(double permitsPerSecond)
Metod altta şöyledir.
static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond) {

  RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */); 
  rateLimiter.setRate(permitsPerSecond);
  return rateLimiter;
}
Örnek
Saniyede bir oran için şöyle yaparız.
RateLimiter msgLimiter  = RateLimiter.create(1);
Örnek
Elimizde şöyle bir kod olsun. RateLimiter dolu başlar. Bekleme aralıkları sabit olduğu için hep aynı süre bekleriz
RateLimiter rt = new RateLimiter.create(2);
System.out.println(rt.acquire()); System.out.println(rt.acquire()); System.out.println(rt.acquire()); System.out.println(rt.acquire()); System.out.println(rt.acquire()); System.out.println(rt.acquire());
Çıktı olarak şunu alırız
0.0
0.498957
0.497851
0.499391
0.499364
Örnek
Elimizde şöyle bir kod olsun. Saniyede 2 istek işler.
RateLimiter rl = RateLimiter.create(2);
System.out.println(rl.getRate());
System.out.println(rl.tryAcquire());
System.out.println(rl.tryAcquire());
System.out.println(rl.tryAcquire());
Çıktı olarak şunu alırız. İkinci istek false döner çünkü istekleri eşit dağıtmaya çalışır.
2.0
true
false
false
create metodu - SmoothWarmingUp
İmzası şöyle
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)
Metod altta şöyledir.
static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond,
long warmupPeriod, TimeUnit unit, double coldFactor) {

  RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit,
coldFactor);
  rateLimiter.setRate(permitsPerSecond);
  return rateLimiter;
}
Açıklaması şöyle
The RateLimiterSmoothWarmingUp method has a warm-up period after the startup. It gradually increases the distribution rate to the configured value.

For example, you can use the following snippet to create a ratelimiter with an average token creation rate of 2 tokens/s, and a warm-up period of 3 seconds. The token bucket does not create a token every 0.5 seconds immediately after startup, because a warm-up period of 3 seconds has been set. Instead, the system gradually increases the token creation rate to the pre-set value within 3 seconds and then creates tokens at a fixed rate. This feature is suitable for scenarios where the system needs some time to warm up after startup.

RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS);
Örnek
Elimizde şöyle bir kod olsun. RateLimiter dolu başlar. 4 saniye boyunca yavaştır yani ısınır. Isınması gittikçe daha hızlanarak sürer. 4 saniye sonunda normal hızında yani saniyede 2 tane token üretmeye başlar.
RateLimiter rt = new RateLimiter.create(2,4, TimeUnit.SECONDS);
System.out.println(rt.acquire()); System.out.println(rt.acquire()); System.out.println(rt.acquire()); System.out.println(rt.acquire()); System.out.println(rt.acquire()); System.out.println(rt.acquire());
Çıktı olarak şunu alırız
0.0
1.3743
1.123576
0.874151
0.624409 
0.50001
tryAcquire metodu
Şöyle yaparız.
while (true) {
  
  if (rateLimiter.tryAcquire()) {
    ...
  }
}

Hiç yorum yok:

Yorum Gönder