實現一個流控程序,控制客戶端每秒調用某個遠程服務不超過N次,客戶端是會多線程併發調用。
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class FlowConcurrentController {
// 每秒併發訪問控制數量
final static int MAX_QPS = 10;
// 併發控制信號量
final static Semaphore semaphore = new Semaphore(MAX_QPS);
// 監控每秒併發訪問次數(理論上accessCount.get() <= 10)
final static AtomicInteger accessCount = new AtomicInteger(0);
// 模擬遠程訪問
private static void remoteCall(int i, int j) {
System.out.println(String.format("%s - %s: %d %d", new Date(), Thread.currentThread(), i, j));
}
private static void releaseWork() { // 每秒release一次
// release semaphore thread
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
semaphore.release(accessCount.get());
accessCount.set(0);
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
}
// 模擬併發訪問控制
private static void simulateAccess(final int m, final int n)
throws Exception { // m : 線程數;n : 調用數
ExecutorService pool = Executors.newFixedThreadPool(100);
for (int i = m; i > 0; i--) {
final int x = i;
pool.submit(new Runnable() {
@Override
public void run() {
for (int j = n; j > 0; j--) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.acquireUninterruptibly(1);
accessCount.incrementAndGet();
remoteCall(x, j);
}
}
});
}
pool.shutdown();
pool.awaitTermination(1, TimeUnit.HOURS);
}
public static void main(String[] args) throws Exception {
// 開啓releaseWork
releaseWork();
// 開始模擬lots of concurrent calls: 100 * 1000
simulateAccess(100, 1000);
}
}
上面的代碼中存在一個小問題,就是accessCount的釋放後,存在負數的情況,也就是說高併發的情況下每秒會存在>MAX_QPS次的併發訪問次數,還不能做到非常精確控制。
期待大家更加簡單和輕量的方式。