【Reactor第八篇】WebFlux 服務編排

六七十三發表於2023-04-25

WebFlux 服務編排是指使用 WebFlux 框架來編排多個非同步服務的執行順序和資料流動,從而構建出一個完整的、基於事件驅動的響應式應用程式。

WebFlux服務編排的優勢如下:

  1. 高效能:WebFlux基於響應式程式設計模型,可以使用少量的執行緒處理大量的請求,從而提高系統的併發能力和吞吐量。
  2. 非同步處理:WebFlux可以非同步處理請求和響應,避免執行緒的阻塞和等待,提高系統的併發能力和效能。
  3. 高可靠性:WebFlux基於事件驅動的程式設計模型,可以更好地處理錯誤和異常,從而提高系統的可靠性和穩定性。
  4. 簡潔清晰:WebFlux的程式碼簡潔清晰,可以使用函數語言程式設計風格來編寫業務邏輯,提高程式碼的可讀性和可維護性。
  5. 可擴充套件性:WebFlux可以輕鬆地整合其他的響應式元件和服務,例如Reactive Streams、Spring Cloud、RSocket等,從而提高系統的可擴充套件性和靈活性。

綜上所述,WebFlux服務編排可以幫助我們構建高效能、高可靠性、可擴充套件性強的響應式應用程式,提高系統的併發能力和效能,從而更好地滿足現代應用程式的需求。

一個示例

public Mono> getOrderDetails(String orderId) {
    return Mono.fromCallable(() -> {
        // 查詢訂單基本資訊
        return "order info";
    })
    .flatMap(orderInfo -> {
        // 查詢訂單商品資訊
        return Mono.fromCallable(() -> {
            return "order item info";
        });
    })
    .flatMap(orderItemInfo -> {
        // 查詢訂單配送資訊
        return Mono.fromCallable(() -> {
            return "order delivery info";
        });
    })
    .flatMap(orderDeliveryInfo -> {
        // 查詢訂單支付資訊
        return Mono.fromCallable(() -> {
            return "order payment info";
        });
    });
}

為什麼使用 fromCallable,就是上面說的,WebFlux 編排的是非同步服務,而不是同步服務。

但是實際線上不要使用 fromCallable,會導致建立很多個執行緒,高併發場景下會導致資源競爭激烈,從而服務效能急劇下降。

1 序列

1.1 不需要 invoker1 的結果

long start = System.currentTimeMillis();

Mono<String> invoke1 = Invoker1.invoke1();
Mono<String> result = invoke1.flatMap(p -> Invoker2.invoke2())
      .map(s -> {
         return s.toString();
      });
// result: invoker2, 耗時:3592(序列)
System.out.println("result: " + result.block() + ", 耗時:" + (System.currentTimeMillis() - start));

1.2 需要返回 invoker1 的結果

long start = System.currentTimeMillis();

Mono<String> invoke1 = Invoker1.invoke1();
Mono<String> result = invoke1.flatMap(p -> {
   return Invoker2.invoke2().map(s -> {
      return p + s;
   });
});

// result: invoker1invoker2, 耗時:3554(序列)
System.out.println("result: " + result.block() + ", 耗時:" + (System.currentTimeMillis() - start));

2 並行

2.1 zip 方法

zip() 方法可以一次組裝任意個Mono,適用於有多個Mono的情況

long start = System.currentTimeMillis();
Mono<String> invoke1 = Invoker1.invoke1();
Mono<String> invoker2 = Invoker2.invoke2();
Mono<String> result = Mono.zip(invoke1, invoker2)
      .map(s-> {
         String t1 = s.getT1();
         String t2 = s.getT2();
         return String.format("invoke1:%s, invoke2: %s", t1, t2);
      });
// invoker1invoker2耗時:2650 (並行)
System.out.println("result: " + result.block() + ",耗時:" + (System.currentTimeMillis() - start));

2.2 zipWith 方法

zipWith() 每次組裝一個Mono物件,使用於組裝Mono個數比較少的情況。

long start = System.currentTimeMillis();
Mono<String> invoke1 = Invoker1.invoke1();
Mono<String> invoker2 = Invoker2.invoke2();
Mono<String> result = invoke1.zipWith(invoker2)
      .map(s -> {
         return String.format("invoke1:%s, invoke2: %s", s.getT1(), s.getT2());
      });
// invoker1invoker2耗時:2469 (並行)
System.out.println(result.block() + ",耗時:" + (System.currentTimeMillis() - start));

3 前提

這裡的 invoker 就是第三方系統呼叫。

保證 invoker 是在獨立的執行緒中執行,這樣 invoker 不會影響業務處理。

public class Invoker1 {

   public static Mono<String> invoke1() {
      return Mono.
            fromSupplier(() -> {
               try {
                  Thread.sleep(1000);
               } catch (InterruptedException e) {
                  throw new RuntimeException(e);
               }
               return "invoker1";
            })
            .subscribeOn(Schedulers.parallel())
            .doOnError(e -> {
               System.out.println("error invoker1");
            });
   }
}
public class Invoker2 {

   public static Mono<String> invoke2() {
      return Mono.fromSupplier(() -> {
               try {
                  Thread.sleep(2000);
               } catch (InterruptedException e) {
                  throw new RuntimeException(e);
               }
               return "invoker2";
            })
            .subscribeOn(Schedulers.parallel())
            .doOnError(e -> {
               System.out.println("error invoker2");
            });
   }
}

相關文章