前言
RPC 服務中,每個服務的容量都是有限的,即資源有限,只能承受住給定的網路請求,所以,在設計 RPC 框架的時候,一定要考慮流量控制這個問題。而 Java 中,實現流量控制有很多中方式,今天說 2 種。
Semaphore 實現流控
程式碼:
static Semaphore semaphore = new Semaphore(100);
public static void main(String[] args) {
Executor timeTask = Executors.newScheduledThreadPool(1);
((ScheduledExecutorService) timeTask).scheduleAtFixedRate(
() -> semaphore.release(100 - semaphore.availablePermits()), 1000, 1000,
TimeUnit.MILLISECONDS);
Executor pool = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
final int num = i;
pool.execute(() -> {
for (; ; ) {
for (int j = 0; j < 200; j++) {
if (semaphore.tryAcquire()) {
callRpc(num, j);
} else {
System.err.println("call fail");
}
}
}
});
}
}
private static void callRpc(int num, int j) {
System.out.println(String.format("%s - %s: %d %d", new Date(), Thread.currentThread(), num, j));
}
複製程式碼
程式碼中,我們模擬了 100 個執行緒,每個執行緒無限呼叫 RPC。
同時使用另一個定時任務,定時更新 Semaphore 可用許可為 100。
客戶端執行緒呼叫時,會嘗試獲取訊號量,當獲取成功時,才會呼叫呼叫 RPC,反之,列印失敗。
這個小程式實現了每秒鐘限制 100 個請求的 RPC 的流量控制。
AtomicInteger 實現流控
程式碼:
static AtomicInteger count = new AtomicInteger();
public static void main(String[] args) {
Executor timeTask = Executors.newScheduledThreadPool(1);
((ScheduledExecutorService) timeTask).scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
count.getAndSet(100);
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
Executor pool = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
final int num = i;
pool.execute(() -> {
for (; ; ) {
for (int j = 0; j < 200; j++) {
if (count.get() >= 0) {// 快速判斷,否則大量的 CAS 操作將會定時任務更新計數器 count
if (count.decrementAndGet() >= 0) {
callRpc(num, j);
}
}
}
}
});
}
}
private static void callRpc(int num, int j) {
System.out.println(String.format("%s - %s: %d %d", new Date(), Thread.currentThread(), num, j));
}
複製程式碼
這段程式碼和上面的類似,只是使用的 API 不同,這裡使用的是 CAS。通過對 CAS 遞減,達到流控的目的。
注意,這裡有一個雙重判斷,先判斷 count.get() >= 0,為什麼呢?
如果直接使用 decrementAndGet 方法,則會使用 CAS,100 個執行緒併發使用 CAS ,將會導致定時任務的 CAS 操作不夠及時。
所以,先判斷,是否小於0 ,如果小於0了,就不必嘗試 CAS,避免影響定時任務。