Reactorの第9章、WebFlux、パーソナルセンターをリファクタリング、効果絶大

1 リファクタリングの背景

元の開発者は長い間離れており、誰もコードの詳細を知りません. メンテナンス期間の後、次の問題が見つかりました:

個人センターシステムの特徴は、様々な業務のインターフェースを組み立て、個人センター業務に必要なデータを出力することである. システム全体で、数十のサードパーティの業務ラインインターフェースを呼び出す. レイアウトが無理だと、応答時間が長くなる可能性がある.特にウィンドウ ビジネスでは、新しいポップアップ ウィンドウが継続的に接続され、インターフェイス全体が使用できなくなる可能性があります。

2 全体構造

service: 最小のビジネス オーケストレーション ユニットであり、request メソッドはインフラストラクチャのサード パーティ インターフェイスを調整して呼び出し、apply メソッドはサード パーティ インターフェイス呼び出しの結果を組み立て、結果はサービスのビジネス リターンです。

インフラストラクチャ: これはサード パーティへの非同期の非ブロッキング呼び出しであり、ビジネス ロジックは含まれません。サービスは、実際の業務に応じて 0 個以上のインフラ サービスを配置できます。

実際の最適化プロセスでは、30 以上のインフラストラクチャ サードパーティ コールと 40 以上のサービスを抽象化しました。小規模で独立したクラスであるため、学生、特に新入生が親しむためのコストが削減されます。境界も比較的明確で、論理的にまとまっています。

2 配置例

各サービスは、1 つまたは複数のサードパーティ インフラストラクチャ コールによって組み立てられ、配置されるビジネス ユニットです. 非同期で処理できるすべての内部処理は、非同期処理と、非同期で処理できないシリアル + パラレル メソッドを使用します.

2.1 シリアル

シリアル化する必要がある場合は flatMap メソッドを使用でき、次の形式を参照できます。

この方法では、S1 が実行され、次に S2 が実行されます。

擬似コードは次のとおりです。

Mono.from(service1.func())
				.flatMap(service1Res-> {
          return service2.func();
        })
复制代码

2.2 パラレル

zip と zipWith、zipWith は一度に 1 つの Mono を組み立て、zip は一度に複数の Mono を組み立てることができます。

サンプルコードは次のとおりです。

service1.zipWith(service2)

Mono.zip(service1, service2, service3)
复制代码

zip を使用して複数のサービスをアセンブルし、service1、service2、...、service6 を並行して実行し、doOnError を使用してエラーを処理し、onErrorReturn を使用して例外リターンを処理し、doOnFinally を使用して呼び出しの量と時間を監視するサンプル コードです。インターフェース全体。

Mono.zip(service1, service2, service3, service4, service5, service6)
				.map(t -> {
					String service1Ret = t.getT1();
					String service2Ret = t.getT2();
          // ....
					return "组合结果";
				})
        // 异常返回
				.onErrorReturn(new DTO())
				.doOnError(e -> {
					// 异常详情日志;异常请求量监控
				})
				.doFinally(e -> {
          // 请求量、耗时监控
				});
复制代码

2.3 並列 - ただし、データを含む最初の結果のみを取得する

弹窗类业务与一般service不通,它需要调用很多的业务的数据出不同的弹窗,但是每次都只能给用户展示确定的一个。但是如果串行的话,随着上线的弹窗越来越多,整个弹窗接口的耗时会越来越长。

但是如果改成异步的话,又无法控制弹窗之间的优先级,优先级对于公司整体业务来说是必要的,把重要的业务放在高优的位置上,做到资源最大利用,才能实现利润的最大化,从而做到基业长青。

Flux 有个flatMapSequential方法,它能完美解决这个问题,看看它的注释:

Transform the elements emitted by this Flux asynchronously into Publishers, then flatten these inner publishers into a single Flux, but merge them in the order of their source element.

将此Flux发出的元素异步地转换为 publisher,然后将这些内部 publisher 扁平化为单个Flux,但按照源元素的顺序合并它们。

如上图所示,总共有S1、S2、S3、S4按顺序的四个弹窗,会并行执行S1到S4,如果S1和S2没有数据,S3有数据,则会返回S3。

伪代码如下:

Flux<Map<String, Object>> monoFlux = Flux.fromIterable(serviceList)
				.flatMapSequential(serviceName -> {
					    
							})
							.onErrorContinue((err, i) -> {
                  // 某个service异常或者无数据,继续执行
							});
				})
				.onErrorContinue((err, i) -> {
          // 服务异常,继续执行
				});
Mono<Map<String, Object>> mono = monoFlux.elementAt(0, Maps.newHashMap());        
复制代码

这里就是异步执行所有弹窗service,运行过程中某个弹窗异常或者无数据返回,则继续下一个。通过monoFlux.elementAt(0, Maps.newHashMap())获取第一个有数据的弹窗。

4 重构效果

4.1 后端指标

相比于原来的后端系统,所有接口耗时都有大幅度降低,:

  • 头部身份信息接口响应速度提升:26%。
  • 卡片各业务线入口接口响应速度提升:87%。
  • 弹窗和浮标接口响应速度提升:146%。

经过 flatMapSequential 编排弹窗之后,耗时从220ms,降到160ms,绝对值下降了60ms,下降了 28%;

4.2 新需求开发和维护

新需求开发更快,QA 测试更快。

原来开发一个弹窗,需要考虑的事情很多:

  1. 开发的时候需要考虑代码放在哪个层级上,是否与其他弹窗有耦合,
  2. 弹窗优先级需要通过if-else实现,很容易出错;
  3. 弹窗自测很麻烦,需要注释调其他弹窗;
  4. QA 需要测试所有弹窗的优先级是否有问题;

现在开发一个弹窗,只需要增加一个service类,然后把service配置再优先级列表中即可。

4.3 其他

框架使用了响应式框架 Spring WebFlux,也支持本地启动,编写了service层和基础设施层的单测case,提升开发效率。

删除了原来的业务网关层,使用公司层面的网关系统,配置即生效;删除了原来业务网关中的业务逻辑代码,把相关逻辑移动到业务层中,解除了原来的多层之间的耦合关系。

现在各个service之前相互独立,异常不会相互影响。

おすすめ

転載: juejin.im/post/7229159531895734333