使用併發工具實現 RPC 呼叫流量控制

莫那·魯道發表於2018-05-19

前言

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,避免影響定時任務。

相關文章