簡易的窗口限流器

滑動窗口限流的基本原理: 將數據分散到足夠多的bucket中去,然後以一定的速率產生新的bucket,並且移除最久遠的bucket。

 

 

 

package windowlimit;

public class Bucket {

    private int accessNum = 0;

    private int errorNum = 0;


    public Bucket() {
    }

    public int getAccessNum() {
        return accessNum;
    }

    public int getErrorNum() {
        return errorNum;
    }

    public void reset() {
        this.accessNum = 0;
        this.errorNum = 0;
    }

    public void logAccess() {
        this.accessNum++;
    }

    public void logError() {
        this.errorNum++;
    }

}

 

package windowlimit;

import java.util.concurrent.*;

public class BucketArray {

    private final Bucket[] buckets;

    private final long interval;

    private final int windowsNum;

    private int totalAccessNum;

    private Future<?> future = null;

    private static ScheduledExecutorService sharedExecutor = null;

    public BucketArray(final int windowsNum) {
        buckets = new Bucket[windowsNum];
        interval = 1000 / windowsNum;
        this.windowsNum = windowsNum;
        for (int i = 0; i < windowsNum; i++) {
            buckets[i] = new Bucket();
        }
        this.startTimer(interval);
    }

    public void addAccessLog() {
        synchronized (this) {
            Bucket bucket = buckets[windowsNum - 1];
            bucket.logAccess();
        }
    }

    public int getTotalAccessNum() {
        return this.totalAccessNum;
    }

    public synchronized void startTimer(long delayMillis) {

        if (this.future == null) {
            Runnable task = new Runnable() {
                public void run() {
                    try {
                        synchronized (BucketArray.this) {
                            int total = 0;
                            Bucket first = buckets[0];

                            for (int i = 0; i < windowsNum - 1; i++) {
                                int j = i + 1;
                                buckets[i] = buckets[j];
                                total = total + buckets[i].getAccessNum();
                            }

                            first.reset();
                            buckets[windowsNum - 1] = first;
                            BucketArray.this.totalAccessNum = totalAccessNum;
                            System.out.println("DataPublisher-----totalNum=" + total);
                        }

                    } catch (Exception var2) {
                        var2.printStackTrace();
                    }
                }
            };

            this.future = this.getExecutor().scheduleAtFixedRate(task, delayMillis, delayMillis, TimeUnit.MILLISECONDS);
        }
    }


    protected synchronized ScheduledExecutorService getExecutor() {
        if (sharedExecutor == null) {
            sharedExecutor = Executors.newScheduledThreadPool(1, new PublishThreadFactory());
        }

        return sharedExecutor;
    }

    private static final class PublishThreadFactory implements ThreadFactory {
        PublishThreadFactory() {
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "DataPublisher");
            t.setDaemon(true);
            return t;
        }
    }
}

 

更正兩個錯誤  2022-02-23

BucketArray.this.totalAccessNum = totalAccessNum;    需要更正爲  BucketArray.this.totalAccessNum = total;

另外就是  synchronized (this)  和  synchronized (BucketArray.this) 中的this和BucketArray.this 是兩個不同的指針,因此這種用法是錯誤的。

非靜態內部類new出來的對象包含有包含類對象的指針,因此可以直接訪問包含對象的屬性,方法。  但是有可能會造成內存泄漏,因此當BucketArray沒有用處時,一定要手動停止定時器。

靜態內部類則不包含指針。

 

import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

public class BucketArray {

    private final Bucket[] buckets;

    private final long interval;

    private final int windowsNum;

    private int totalAccessNum;

    private Future<?> future = null;

    private final Object lock = new Object();

    private static ScheduledExecutorService sharedExecutor = null;

    public BucketArray(final int windowsNum) {
        buckets = new Bucket[windowsNum];
        interval = 1000 / windowsNum;
        this.windowsNum = windowsNum;
        for (int i = 0; i < windowsNum; i++) {
            buckets[i] = new Bucket();
        }
        this.startTimer(interval);
    }

    public void addAccessLog() {
        synchronized (lock) {
            Bucket bucket = buckets[windowsNum - 1];
            bucket.logAccess();
        }
    }

    public int getTotalAccessNum() {
        return this.totalAccessNum;
    }

    public synchronized void startTimer(long delayMillis) {

        if (this.future == null) {
            Runnable task = new Runnable() {
                public void run() {
                    try {
                        synchronized (lock) {
                            int total = 0;
                            Bucket first = buckets[0];

                            for (int i = 0; i < windowsNum - 1; i++) {
                                int j = i + 1;
                                buckets[i] = buckets[j];
                                total = total + buckets[i].getAccessNum();
                            }

                            first.reset();
                            buckets[windowsNum - 1] = first;
                            BucketArray.this.totalAccessNum = total;
                        }

                    } catch (Exception var2) {
                        var2.printStackTrace();
                    }
                }
            };

            this.future = this.getExecutor().scheduleAtFixedRate(task, delayMillis, delayMillis, TimeUnit.MILLISECONDS);
        }
    }

    public synchronized void stopTimer() {
        if (this.future != null) {
            this.future.cancel(false);
            this.future = null;
        }
    }



    protected synchronized ScheduledExecutorService getExecutor() {
        if (sharedExecutor == null) {
            sharedExecutor = Executors.newScheduledThreadPool(1, new PublishThreadFactory());
        }

        return sharedExecutor;
    }

    private static final class PublishThreadFactory implements ThreadFactory {
        PublishThreadFactory() {
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "DataPublisher");
            t.setDaemon(true);
            return t;
        }
    }

}

 

改進版本的startTimer。  

public synchronized void startTimer(long delayMillis) {

    if (this.future == null) {
        Runnable task = new Runnable() {
            public void run() {
                try {
                    synchronized (lock) {
                        int total = BucketArray.this.totalAccessNum;
                        Bucket first = buckets[0];
                        Bucket last = buckets[windowsNum-1] ;
                        System.arraycopy(buckets,1,buckets,0,windowsNum-1);
                        total = total + last.getAccessNum() - first.getAccessNum() ;
                        BucketArray.this.totalAccessNum = total;
                        first.reset();

                        buckets[windowsNum-1] = first;
                    }
                } catch (Exception var2) {
                    var2.printStackTrace();
                }
            }
        };

        this.future = this.getExecutor().scheduleAtFixedRate(task, delayMillis, delayMillis, TimeUnit.MILLISECONDS);
    }
}

 

疑問, 這部分代碼是不是可以不加鎖, 因爲移動,統計並不會阻塞往last計數,只是這時的統計數量會少一而已。

int total = BucketArray.this.totalAccessNum;
Bucket first = buckets[0];
Bucket last = buckets[windowsNum-1] ;
System.arraycopy(buckets,1,buckets,0,windowsNum-1);
total = total + last.getAccessNum() - first.getAccessNum() ;
BucketArray.this.totalAccessNum = total;
first.reset();

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章