滑動窗口限流的基本原理: 將數據分散到足夠多的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();