Phương thức điều chỉnh các cuộc gọi đến M yêu cầu trong N giây


137

Tôi cần một thành phần / lớp điều chỉnh việc thực thi một số phương thức để thực hiện các cuộc gọi M tối đa trong N giây (hoặc ms hoặc nanos, không thành vấn đề).

Nói cách khác, tôi cần đảm bảo rằng phương thức của tôi được thực hiện không quá M lần trong một cửa sổ trượt N giây.

Nếu bạn không biết lớp hiện tại, vui lòng đăng các giải pháp / ý tưởng của bạn về cách bạn sẽ thực hiện điều này.



3
Có một số câu trả lời tuyệt vời cho vấn đề này tại stackoverflow.com/questions/667508/ trên
skaffman

> Tôi cần đảm bảo rằng phương thức của tôi> được thực hiện không quá M lần trong một> cửa sổ trượt trong N giây. Gần đây tôi đã viết một bài đăng trên blog về cách làm điều này trong .NET. Bạn có thể tạo một cái gì đó tương tự trong Java. Giới hạn tỷ lệ tốt hơn trong .NET
Jack Leitch

Câu hỏi ban đầu nghe có vẻ giống như vấn đề được giải quyết trong bài đăng trên blog này: [Throttler không đồng bộ đa kênh của Java] ( cordinc.com/blog/2010/04/java-multichannel-asyn syncous.html ). Đối với tốc độ của các cuộc gọi M trong N giây, bộ điều tiết được thảo luận trong blog này đảm bảo rằng bất kỳ khoảng thời gian N nào trên dòng thời gian sẽ không chứa nhiều hơn các cuộc gọi M.
Hbf

Câu trả lời:


81

Tôi sẽ sử dụng bộ đệm vòng thời gian với kích thước cố định là M. Mỗi lần phương thức được gọi, bạn kiểm tra mục cũ nhất và nếu ít hơn N giây trong quá khứ, bạn thực hiện và thêm mục khác, nếu không bạn sẽ ngủ cho sự khác biệt thời gian.


4
Đáng yêu. Chỉ cần những gì tôi cần. Thử nhanh cho thấy ~ 10 dòng để thực hiện điều này và dung lượng bộ nhớ tối thiểu. Chỉ cần suy nghĩ về an toàn chủ đề và xếp hàng các yêu cầu đến.
vtrubnikov

5
Đó là lý do tại sao bạn sử dụng DelayQueue từ java.util.conc hiện. Nó ngăn chặn vấn đề của nhiều luồng hoạt động trên cùng một mục.
erickson

5
Đối với trường hợp đa luồng, cách tiếp cận nhóm mã thông báo có thể là lựa chọn tốt hơn, tôi nghĩ vậy.
Michael Borgwardt

1
Bạn có biết thuật toán này được gọi như thế nào nếu nó có bất kỳ tên nào không?
Vlado Pandžić

80

Thứ phù hợp với tôi là Google Guava RateLimiter .

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

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

19
Tôi sẽ không đề xuất giải pháp này vì Guava RateLimiter sẽ chặn luồng và điều đó sẽ làm cạn kiệt nhóm luồng dễ dàng.
kaviddiss

18
@kaviddiss nếu bạn không muốn chặn thì hãy sử dụngtryAquire()
slf 10/03/2015

7
Vấn đề với việc triển khai RateLimiter hiện tại (ít nhất là đối với tôi) là nó không cho phép các khoảng thời gian lớn hơn 1 giây và do đó tốc độ ví dụ 1 mỗi phút.
John B

4
@ John B Theo như tôi hiểu, bạn có thể đạt được 1 yêu cầu mỗi phút với RateLimiter bằng cách sử dụng RateLimiter.create (60.0) + RateLimiter.acquire (60)
splitByZero

2
@radiantRazor Rat006iter.create (1.0 / 60) và có được () đạt được 1 cuộc gọi mỗi phút.
bizentass

30

Nói một cách cụ thể, bạn sẽ có thể thực hiện điều này với a DelayQueue. Khởi tạo hàng đợi với các M Delayedthể hiện với độ trễ ban đầu được đặt thành không. Khi các yêu cầu đến phương thức xuất hiện, takemột mã thông báo, khiến phương thức bị chặn cho đến khi yêu cầu điều chỉnh được đáp ứng. Khi mã thông báo đã được thực hiện, addmã thông báo mới vào hàng đợi có độ trễ là N.


1
Vâng, điều này sẽ làm các mẹo. Nhưng tôi không đặc biệt thích DelayQueue vì nó đang sử dụng (thông qua PriortyQueue) một hàm băm nhị phân cân bằng (có nghĩa là rất nhiều so sánh về offervà tăng trưởng mảng có thể) và tất cả đều nặng đối với tôi. Tôi đoán cho người khác điều này có thể hoàn toàn ổn.
vtrubnikov

5
Trên thực tế, trong ứng dụng này, vì phần tử mới được thêm vào heap sẽ luôn luôn là phần tử tối đa trong heap (nghĩa là có độ trễ dài nhất), thường là một so sánh cho mỗi lần thêm. Ngoài ra, mảng sẽ không bao giờ phát triển nếu thuật toán được triển khai chính xác, vì một phần tử chỉ được thêm vào sau khi lấy một phần tử.
erickson

3
Tôi cũng thấy điều này hữu ích trong trường hợp bạn không muốn các yêu cầu xảy ra trong các vụ nổ lớn bằng cách giữ kích thước M và trì hoãn N tương đối nhỏ theo thứ tự số lượng nhỏ. ví dụ. M = 5, N = 20ms sẽ cung cấp một cú nổ kepping 250 / giây để xảy ra ở kích thước 5.
FUD

Liệu quy mô này cho một triệu vòng / phút và khi các yêu cầu đồng thời được cho phép? Tôi sẽ cần thêm một triệu trì hoãn. Ngoài ra các trường hợp góc sẽ có độ trễ cao - trường hợp có nhiều luồng đang gọi poll () và nó sẽ bị khóa mỗi lần.
Aditya Joshee

@AdityaJoshee Tôi đã không điểm chuẩn nó, nhưng nếu tôi có thời gian, tôi sẽ cố gắng để có cảm giác về chi phí. Một điều cần lưu ý là bạn không cần 1 triệu mã thông báo hết hạn sau 1 giây. Bạn có thể có 100 mã thông báo hết hạn sau 10 mili giây, 10 mã thông báo hết hạn sau mili giây, v.v ... Điều này thực sự buộc tốc độ tức thời phải gần với tốc độ trung bình, tăng đột biến, có thể gây ra sao lưu tại máy khách, nhưng đó là hậu quả tự nhiên giới hạn tỷ lệ. 1 triệu RPM hầu như không giống như điều tiết. Nếu bạn có thể giải thích trường hợp sử dụng của bạn, tôi có thể có ý tưởng tốt hơn.
erickson

21

Đọc lên thuật toán xô Token . Về cơ bản, bạn có một thùng chứa mã thông báo trong đó. Mỗi khi bạn thực hiện phương thức, bạn lấy mã thông báo. Nếu không có thêm mã thông báo, bạn chặn cho đến khi bạn nhận được một mã thông báo. Trong khi đó, có một số tác nhân bên ngoài bổ sung các mã thông báo theo một khoảng thời gian cố định.

Tôi không biết về một thư viện để làm điều này (hoặc bất cứ điều gì tương tự). Bạn có thể viết logic này vào mã của bạn hoặc sử dụng AspectJ để thêm hành vi.


3
Cảm ơn đề nghị, algo thú vị. Nhưng nó không chính xác những gì tôi cần. Ví dụ, tôi cần giới hạn thực hiện trong 5 cuộc gọi mỗi giây. Nếu tôi sử dụng xô Token và 10 yêu cầu đến cùng một lúc, 5 cuộc gọi đầu tiên sẽ nhận tất cả các mã thông báo có sẵn và thực hiện trong giây lát, trong khi 5 cuộc gọi còn lại sẽ được thực hiện trong khoảng thời gian cố định là 1/5 giây. Trong tình huống như vậy, tôi cần 5 cuộc gọi còn lại để được thực hiện trong một lần nổ chỉ sau 1 giây.
vtrubnikov

5
Điều gì sẽ xảy ra nếu bạn thêm 5 mã thông báo vào nhóm mỗi giây (hoặc 5 - (5 còn lại) thay vì 1 cứ sau 1/5 giây?
Kevin

@Kevin không, điều này vẫn không mang lại cho tôi hiệu ứng 'cửa sổ trượt'
vtrubnikov

2
@valery có nó sẽ. (Hãy nhớ giới hạn mã thông báo tại M)
nos

không cần một "diễn viên bên ngoài". Mọi thứ có thể được thực hiện một luồng nếu bạn giữ siêu dữ liệu xung quanh về thời gian yêu cầu.
Marsellus Wallace

8

Nếu bạn cần một bộ giới hạn tốc độ cửa sổ trượt dựa trên Java sẽ hoạt động trên một hệ thống phân tán, bạn có thể muốn xem dự án https://github.com/mokies/rat006itj .

Một cấu hình được hỗ trợ bởi Redis, để giới hạn các yêu cầu bằng IP đến 50 mỗi phút sẽ như thế này:

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

Xem https://github.com/mokies/rat006itj/tree/master/rat006itj-redis để biết thêm chi tiết về cấu hình Redis.


5

Điều này phụ thuộc vào ứng dụng.

Hãy tưởng tượng trường hợp trong đó nhiều chủ đề muốn có một dấu hiệu để làm một số hành động toàn cầu tốc độ-hạn chế với không burst cho phép (ví dụ bạn muốn giới hạn 10 hành động mỗi 10 giây nhưng bạn không muốn 10 hành động xảy ra trong giây đầu tiên và sau đó duy trì 9 giây dừng lại).

DelayedQueue có một nhược điểm: thứ tự mà chủ đề yêu cầu mã thông báo có thể không phải là thứ tự mà họ nhận được yêu cầu của họ. Nếu nhiều luồng bị chặn chờ mã thông báo, không rõ chuỗi nào sẽ lấy mã thông báo có sẵn tiếp theo. Bạn thậm chí có thể có các chủ đề chờ đợi mãi mãi, theo quan điểm của tôi.

Một giải pháp là có một khoảng thời gian tối thiểu giữa hai hành động liên tiếp và thực hiện các hành động theo cùng một thứ tự như chúng được yêu cầu.

Đây là một thực hiện:

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

những gì hiện minTimecó nghĩa là ở đây? Nó làm gì? bạn có thể giải thích về điều đó?
flash

minTimelà lượng thời gian tối thiểu cần phải vượt qua sau khi mã thông báo được tiêu thụ trước khi mã thông báo tiếp theo có thể được tiêu thụ.
Duarte Meneses

3

Mặc dù đó không phải là những gì bạn đã hỏi, ThreadPoolExecutorđược thiết kế để đáp ứng yêu cầu M đồng thời thay vì yêu cầu M trong N giây, cũng có thể hữu ích.


2

Tôi đã triển khai một thuật toán điều chỉnh đơn giản. Hãy thử liên kết này, http://krishnaprasadas.blogspot.in/2012/05/throttling-alerskym.html

Tóm tắt về Thuật toán,

Thuật toán này sử dụng khả năng của hàng đợi trễ Java . Tạo một đối tượng bị trì hoãn với độ trễ dự kiến ​​(ở đây 1000 / M cho mili giây TimeUnit ). Đặt cùng một đối tượng vào hàng đợi bị trì hoãn sẽ cung cấp cửa sổ di chuyển cho chúng ta. Sau đó, trước khi mỗi lệnh gọi phương thức lấy đối tượng tạo thành hàng đợi, hãy thực hiện một cuộc gọi chặn sẽ chỉ trả về sau độ trễ đã chỉ định và sau khi gọi phương thức, đừng quên đặt đối tượng vào hàng đợi với thời gian cập nhật (ở đây là mili giây hiện tại) .

Ở đây chúng ta cũng có thể có nhiều đối tượng bị trì hoãn với độ trễ khác nhau. Cách tiếp cận này cũng sẽ cung cấp thông lượng cao.


6
Bạn nên đăng một bản tóm tắt thuật toán của bạn. Nếu liên kết của bạn biến mất thì câu trả lời của bạn trở nên vô dụng.
viết

Cảm ơn, tôi đã thêm tóm tắt.
Krishas

1

Việc triển khai của tôi dưới đây có thể xử lý độ chính xác về thời gian yêu cầu tùy ý, nó có độ phức tạp thời gian O (1) cho mỗi yêu cầu, không yêu cầu bất kỳ bộ đệm bổ sung nào, ví dụ độ phức tạp không gian O (1), ngoài ra, nó không yêu cầu luồng nền để phát hành mã thông báo, thay vào đó mã thông báo được phát hành theo thời gian trôi qua kể từ yêu cầu cuối cùng.

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

Hãy thử sử dụng phương pháp đơn giản này:

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

Đây là bản cập nhật cho mã LeakyBucket ở trên. Điều này hoạt động cho hơn 1000 yêu cầu mỗi giây.

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

và ít nhất ở trên:

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

là những gì minTimeNanocó nghĩa là ở đây? bạn có thể giải thích?
flash

0

Đây là một phiên bản nâng cao của bộ giới hạn tỷ lệ đơn giản

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

Và bài kiểm tra đơn vị

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

}

Mã này khá đơn giản. Bạn chỉ cần tạo bộ giới hạn với maxRate hoặc với dấu chấm và tỷ lệ. Và sau đó chỉ cần gọi tiêu thụ mọi yêu cầu. Bất cứ khi nào tỷ lệ không vượt quá, bộ giới hạn sẽ trả về ngay lập tức hoặc đợi một thời gian trước khi trở về mức yêu cầu hiện tại thấp hơn. Nó cũng có phương pháp tỷ lệ hiện tại trả về trung bình trượt của tỷ lệ hiện tại.
Leonid Astakhov

0

Giải pháp của tôi: Một phương thức sử dụng đơn giản, bạn có thể sửa đổi nó để tạo một lớp bao bọc.

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

Lấy từ JAVA gỡ lỗi và điều tiết

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.