วิธีการควบคุมปริมาณเรียกร้องไปยังคำขอ M ใน N วินาที


140

ฉันต้องการคอมโพเนนต์ / คลาสที่ควบคุมการดำเนินการของวิธีการบางอย่างเพื่อเรียก M สูงสุดใน N วินาที (หรือ ms หรือนาโนไม่สำคัญ)

กล่าวอีกนัยหนึ่งฉันต้องแน่ใจว่าวิธีการของฉันดำเนินการไม่เกิน M ครั้งในหน้าต่างบานเลื่อนที่ N วินาที

หากคุณไม่ทราบว่าชั้นเรียนที่มีอยู่โปรดโพสต์วิธีแก้ปัญหา / แนวคิดของคุณว่าคุณจะนำสิ่งนี้ไปใช้อย่างไร



4
มีคำตอบที่ดีสำหรับปัญหานี้ที่stackoverflow.com/questions/667508/…
skaffman

> ฉันต้องตรวจสอบให้แน่ใจว่าวิธีการของฉัน> ดำเนินการไม่เกิน M ครั้งในหน้าต่างบานเลื่อน> N วินาที ฉันเพิ่งเขียนบล็อกโพสต์เกี่ยวกับการดำเนินการนี้ใน. NET คุณอาจสร้างสิ่งที่คล้ายกันใน Java ได้ การ จำกัด อัตราที่ดีขึ้นใน. NET
Jack Leitch

คำถามเดิมฟังดูเหมือนปัญหาในบล็อกโพสต์นี้: [Java Multi-Channel Asynchronous Throttler] ( cordinc.com/blog/2010/04/java-multichannel-asynchronous.html ) สำหรับอัตราการโทร M ใน N วินาทีตัวควบคุมที่พูดถึงในบล็อกนี้รับประกันว่าช่วงเวลาใด ๆของความยาว N บนไทม์ไลน์จะไม่มีการโทรเกิน M
Hbf

คำตอบ:


85

ฉันจะใช้บัฟเฟอร์วงแหวนของการประทับเวลาที่มีขนาดคงที่ของ M ทุกครั้งที่มีการเรียกวิธีนี้คุณจะตรวจสอบรายการที่เก่าที่สุดและถ้าน้อยกว่า N วินาทีในอดีตคุณจะดำเนินการและเพิ่มรายการอื่นมิฉะนั้นคุณจะเข้าสู่โหมดสลีป สำหรับความแตกต่างของเวลา


4
น่ารัก. สิ่งที่ฉันต้องการ ความพยายามอย่างรวดเร็วแสดงให้เห็น ~ 10 บรรทัดในการใช้งานนี้และมีหน่วยความจำน้อย เพียงแค่ต้องคิดถึงความปลอดภัยของเธรดและการจัดคิวคำขอที่เข้ามา
vtrubnikov

5
นั่นเป็นเหตุผลที่คุณใช้ DelayQueue จาก java.util.concurrent ช่วยป้องกันปัญหาของเธรดหลายเธรดที่ทำหน้าที่ในรายการเดียวกัน
erickson

5
สำหรับเคสแบบมัลติเธรดวิธีการฝากข้อมูลโทเค็นอาจเป็นทางเลือกที่ดีกว่าฉันคิดว่า
Michael Borgwardt

1
คุณรู้หรือไม่ว่าอัลกอริทึมนี้เรียกว่าอย่างไรหากมีชื่อเลย?
Vlado Pandžić

84

สิ่งที่ทำงานออกจากกล่องสำหรับฉันคือ Google ฝรั่งRateLimiter

// Allow one request per second
private RateLimiter throttle = RateLimiter.create(1.0);

private void someMethod() {
    throttle.acquire();
    // Do something
}

19
ฉันไม่แนะนำวิธีแก้ปัญหานี้เนื่องจาก Guava RateLimiter จะบล็อกเธรดและจะทำให้เธรดพูลหมดได้อย่างง่ายดาย
kaviddiss

20
@kaviddiss ถ้าคุณไม่ต้องการบล็อกให้ใช้tryAquire()
slf

7
ปัญหาเกี่ยวกับการใช้งาน RateLimiter ในปัจจุบัน (อย่างน้อยสำหรับฉัน) คือไม่อนุญาตให้มีช่วงเวลานานกว่า 1 วินาทีดังนั้นอัตราเช่น 1 ต่อนาที
John B

4
@ จอห์น B เท่าที่ฉันเข้าใจคุณสามารถบรรลุ 1 คำขอต่อนาทีด้วย RateLimiter โดยใช้ RateLimiter.create (60.0) + rateLimiter.acquire (60)
DivideByZero

3
@radiantRazor Ratelimiter.create (1.0 / 60) และรับ () ได้ 1 ครั้งต่อนาที
bizentass

30

ในแง่ที่เป็นรูปธรรมคุณควรจะสามารถใช้สิ่งนี้ได้ด้วยไฟล์DelayQueue. เริ่มต้นคิวด้วยM Delayedอินสแตนซ์โดยตั้งค่าความล่าช้าเป็นศูนย์ในตอนแรก เมื่อมีการร้องขอไปยังเมธอดจะtakeมีโทเค็นซึ่งทำให้เมธอดบล็อกจนกว่าจะตรงตามข้อกำหนดการควบคุมปริมาณ เมื่อโทเค็นได้รับการถ่าย, โทเค็นใหม่ในคิวที่มีความล่าช้าของaddN


1
ใช่นี่จะเป็นการหลอกลวง แต่ฉันไม่ชอบ DelayQueue เป็นพิเศษเพราะมันใช้ (ผ่าน PriortyQueue) แฮชไบนารีที่สมดุล (ซึ่งหมายถึงการเปรียบเทียบจำนวนมากofferและการเติบโตของอาร์เรย์ที่เป็นไปได้) และมันค่อนข้างหนักสำหรับฉัน ฉันเดาว่าสำหรับคนอื่น ๆ นี่อาจจะโอเคอย่างสมบูรณ์
vtrubnikov

5
ที่จริงแล้วในแอปพลิเคชันนี้เนื่องจากองค์ประกอบใหม่ที่เพิ่มเข้าไปในฮีปมักจะเป็นองค์ประกอบสูงสุดในฮีป (กล่าวคือมีการหน่วงเวลานานที่สุด) โดยปกติจะต้องมีการเปรียบเทียบหนึ่งรายการต่อการเพิ่ม นอกจากนี้อาร์เรย์จะไม่โตขึ้นหากใช้อัลกอริทึมอย่างถูกต้องเนื่องจากองค์ประกอบหนึ่งจะถูกเพิ่มหลังจากรับองค์ประกอบเดียวเท่านั้น
erickson

3
ฉันพบว่าสิ่งนี้มีประโยชน์เช่นกันในกรณีที่คุณไม่ต้องการให้คำขอเกิดขึ้นเป็นจำนวนมากโดยการรักษาขนาด M และความล่าช้า N ให้ค่อนข้างเล็กตามลำดับมิลลิวินาที เช่น. M = 5, N = 20ms จะให้ทะลุ 250 / วินาที kepping burst ที่จะเกิดขึ้นในขนาด 5
FUD

ขนาดนี้จะเป็นล้านรอบต่อนาทีและอนุญาตให้มีการร้องขอพร้อมกันได้หรือไม่ ฉันจะต้องเพิ่มองค์ประกอบล่าช้าอีกหนึ่งล้านรายการ นอกจากนี้กรณีที่มุมจะมีเวลาแฝงสูงเช่นกรณีที่เธรดหลายชุดกำลังเรียกแบบสำรวจความคิดเห็น () และจะล็อกทุกครั้ง
Aditya Joshee

@AdityaJoshee ฉันยังไม่ได้เปรียบเทียบ แต่ถ้ามีเวลาฉันจะพยายามทำความเข้าใจกับค่าใช้จ่าย สิ่งหนึ่งที่ควรทราบก็คือคุณไม่จำเป็นต้องใช้โทเค็น 1 ล้านโทเค็นที่หมดอายุใน 1 วินาที คุณสามารถมี 100 โทเค็นที่หมดอายุใน 10 มิลลิวินาที 10 โทเค็นที่หมดอายุในมิลลิวินาทีเป็นต้นซึ่งจริงๆแล้วจะบังคับให้อัตราทันทีใกล้เคียงกับอัตราเฉลี่ยมากขึ้นทำให้การเพิ่มขึ้นอย่างราบรื่นซึ่งอาจทำให้เกิดการสำรองข้อมูลที่ไคลเอนต์ แต่นั่นเป็นผลตามธรรมชาติ ของการ จำกัด อัตรา 1 ล้าน RPM แทบจะไม่เหมือนการควบคุมปริมาณเลย หากคุณสามารถอธิบายกรณีการใช้งานของคุณฉันอาจมีแนวคิดที่ดีกว่านี้
erickson

21

อ่านอัลกอริธึมถัง Token โดยทั่วไปคุณมีถังที่มีโทเค็นอยู่ในนั้น ทุกครั้งที่คุณดำเนินการตามวิธีนี้คุณจะต้องใช้โทเค็น หากไม่มีโทเค็นอีกคุณจะบล็อกจนกว่าจะได้มา ในขณะเดียวกันก็มีนักแสดงภายนอกบางคนที่เติมโทเค็นในช่วงเวลาที่กำหนด

ฉันไม่รู้ว่ามีห้องสมุดให้ทำสิ่งนี้ (หรืออะไรที่คล้ายกัน) คุณสามารถเขียนตรรกะนี้ลงในโค้ดของคุณหรือใช้ AspectJ เพื่อเพิ่มลักษณะการทำงาน


3
ขอบคุณสำหรับคำแนะนำ algo ที่น่าสนใจ แต่มันไม่ใช่สิ่งที่ฉันต้องการ ตัวอย่างเช่นฉันต้องการ จำกัด การดำเนินการไว้ที่ 5 การโทรต่อวินาที หากฉันใช้ Token bucket และมีคำขอ 10 รายการในเวลาเดียวกันการเรียก 5 ครั้งแรกจะใช้โทเค็นที่มีอยู่ทั้งหมดและดำเนินการชั่วขณะในขณะที่การเรียกที่เหลืออีก 5 ครั้งจะดำเนินการในช่วงเวลาคงที่ที่ 1/5 วินาที ในสถานการณ์เช่นนี้ฉันต้องการการเรียกที่เหลืออีก 5 ครั้งเพื่อดำเนินการในการต่อเนื่องครั้งเดียวหลังจากผ่านไป 1 วินาทีเท่านั้น
vtrubnikov

5
จะเกิดอะไรขึ้นถ้าคุณเพิ่ม 5 โทเค็นในที่เก็บข้อมูลทุกวินาที (หรือ 5 - (เหลือ 5 ตัว) แทนที่จะเป็น 1 ทุก 1/5 วินาที
เควิน

@ เควินไม่สิ่งนี้จะไม่ให้เอฟเฟกต์ 'หน้าต่างบานเลื่อน' แก่ฉัน
vtrubnikov

2
@valery ใช่มันจะ. (อย่าลืมว่าที่ฝาราชสกุลที่เอ็มแม้ว่า)
เลขที่

ไม่จำเป็นต้องมี "นักแสดงภายนอก" ทุกอย่างสามารถทำได้ในเธรดเดียวหากคุณเก็บข้อมูลเมตาไว้ประมาณเวลาขอ
Marsellus Wallace

8

หากคุณต้องการตัว จำกัด อัตราหน้าต่างบานเลื่อนที่ใช้ Java ซึ่งจะทำงานในระบบกระจายคุณอาจต้องการดูที่https://github.com/mokies/ratelimitjโครงการ

การกำหนดค่าที่สำรองไว้ของ Redis เพื่อ จำกัด คำขอตาม IP ถึง 50 ต่อนาทีจะมีลักษณะดังนี้:

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");

ดูhttps://github.com/mokies/ratelimitj/tree/master/ratelimitj-redis สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการกำหนดค่า Redis


5

ขึ้นอยู่กับการใช้งาน

ลองนึกภาพกรณีที่เธรดหลายเธรดต้องการให้โทเค็นทำการกระทำที่ จำกัด อัตราทั่วโลกโดยไม่อนุญาตให้มีการต่อเนื่อง (เช่นคุณต้องการ จำกัด 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คือระยะเวลาขั้นต่ำที่ต้องผ่านหลังจากใช้โทเค็นก่อนที่โทเค็นถัดไปจะถูกใช้
Duarte Meneses

3

แม้ว่าจะไม่ใช่สิ่งที่คุณถามThreadPoolExecutorซึ่งออกแบบมาเพื่อตอบสนองคำขอพร้อมกัน M แทนที่จะเป็นคำขอ M ใน N วินาทีก็อาจมีประโยชน์เช่นกัน


2

ฉันได้ใช้อัลกอริธึมการควบคุมปริมาณอย่างง่ายลองใช้ลิงค์นี้ http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html

ข้อมูลสั้น ๆ เกี่ยวกับอัลกอริทึม

ขั้นตอนวิธีการนี้จะใช้ความสามารถของ Java ล่าช้าคิว สร้างออบเจ็กต์ที่ล่าช้าโดยมีการหน่วงเวลาที่คาดไว้ (ที่นี่ 1000 / M สำหรับมิลลิวินาทีสำหรับTimeUnit ) ใส่วัตถุเดียวกันลงในคิวที่ล่าช้าซึ่งจะช่วยให้หน้าต่างเคลื่อนที่สำหรับเรา จากนั้นก่อนที่แต่ละเมธอดจะใช้อ็อบเจ็กต์ในรูปแบบคิว take คือการเรียกบล็อคซึ่งจะส่งคืนหลังจากความล่าช้าที่ระบุเท่านั้นและหลังจากการเรียกใช้เมธอดอย่าลืมใส่อ็อบเจ็กต์ลงในคิวด้วยเวลาที่อัปเดต (ในที่นี้เป็นมิลลิวินาทีปัจจุบัน) .

นอกจากนี้เรายังสามารถมีวัตถุล่าช้าหลายรายการที่มีความล่าช้าต่างกัน วิธีนี้จะให้ปริมาณงานสูงด้วย


6
คุณควรโพสต์สรุปอัลกอริทึมของคุณ หากลิงก์ของคุณหายไปคำตอบของคุณจะไร้ประโยชน์
jwr

ขอบคุณฉันได้เพิ่มบทสรุปแล้ว
Krishas

1

การใช้งานของฉันด้านล่างสามารถจัดการความแม่นยำของเวลาในการร้องขอโดยพลการมีความซับซ้อนของเวลา 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;
        }
    }
}

0

ลองใช้แนวทางง่ายๆนี้:

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();
}

}



0

นี่เป็นการอัปเดตโค้ด LeakyBucket ด้านบน วิธีนี้ใช้ได้กับคำขอมากกว่า 1,000 คำขอต่อวินาที

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);
  }
}

และ unittest สำหรับด้านบน:

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หมายที่นี่คืออะไร? คุณสามารถอธิบาย?
แฟลช

0

นี่คือตัว จำกัด อัตราขั้นสูงเพียงเล็กน้อย

/**
 * 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 ) );
    }

}

รหัสค่อนข้างง่าย คุณเพียงแค่สร้างตัว จำกัด ด้วย maxRate หรือด้วยช่วงเวลาและอัตรา จากนั้นเพียงแค่เรียกใช้ทุกคำขอ เมื่อใดก็ตามที่ไม่เกินอัตราตัว จำกัด จะส่งกลับทันทีหรือรอสักครู่ก่อนที่จะกลับไปที่อัตราคำขอปัจจุบันที่ต่ำกว่า นอกจากนี้ยังมีวิธีอัตราปัจจุบันซึ่งส่งกลับค่าเฉลี่ยแบบเลื่อนของอัตราปัจจุบัน
Leonid Astakhov

0

วิธีแก้ปัญหาของฉัน: วิธีการใช้งานง่ายๆคุณสามารถปรับเปลี่ยนเพื่อสร้างคลาส Wrapper ได้

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;
}

ใช้จากJAVA Thread Debounce และ Throttle

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.