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 测试更快。
原来开发一个弹窗,需要考虑的事情很多:
- 开发的时候需要考虑代码放在哪个层级上,是否与其他弹窗有耦合,
- 弹窗优先级需要通过if-else实现,很容易出错;
- 弹窗自测很麻烦,需要注释调其他弹窗;
- QA 需要测试所有弹窗的优先级是否有问题;
现在开发一个弹窗,只需要增加一个service类,然后把service配置再优先级列表中即可。
4.3 其他
框架使用了响应式框架 Spring WebFlux,也支持本地启动,编写了service层和基础设施层的单测case,提升开发效率。
删除了原来的业务网关层,使用公司层面的网关系统,配置即生效;删除了原来业务网关中的业务逻辑代码,把相关逻辑移动到业务层中,解除了原来的多层之间的耦合关系。
现在各个service之前相互独立,异常不会相互影响。