まず最初に、Wang Xiao 氏の記事 [インターフェイス最適化の一般的なソリューションの概要] に感謝したいと思いますが、たまたま最近、彼はプルデンシャル ファイナンシャル マネジメントの BFF レイヤーで集計クエリ サービスの管理を最適化していました。同氏は、記事の直列章と並列章に焦点を当て、同期から非同期への移行プロセス、完全非同期化によって生じる問題点、ガバナンスの考え方と改善などについて、実践的な経験を共有しました。
これらの経験を共有することで、皆さんの仕事にインスピレーションを与え、役立つことを願っています。ご質問やご提案がございましたら、お気軽にお問い合わせください。ご清聴とご支援に感謝いたします。
流通チャネルの違いに応じて推奨されるウェルスマネジメント商品(ファンド、証券会社、保険、銀行のウェルスマネジメントなど)も異なり、各チャネルやグループごとに見られる商品や特徴データも異なります。BFF 層はすべてのデータを集約して分散するため、BFF 層の集約は多数の基盤となるアトミック サービスに依存するため、主な問題は、多数のアップストリーム インターフェイスに依存するシナリオで TP99 と可用性を確保することです。
場合:
典型的な商品推奨インターフェイスを例にとると、ローカル商品プール キャッシュ、アルゴリズム推奨サービス、商品基本情報サービス、位置クエリ サービス、クラウド ラベル サービス、クーポン設定サービス、利用可能なクーポン サービス、およびその他のデータ サービスに依存する必要があります。 ServN… ...など、アップストリームのアトミック インターフェイスのほとんどは、単一のバッチ クエリに対するサポートが制限されているため、極端な場合には、単一のプッシュ製品インターフェイスでは、一度に 1 ~ n 個のプッシュ製品を推奨します。 10 個の動的属性、少なくとも (1~n)*10 個の io 呼び出しを開始します。
変革前のプロセスと問題点:
プロセス:
質問:
まず、論理プロセスが強く結合されており、多くのアップストリーム サービスとダウンストリーム サービスが強力に同期されています。
2 つ目は、リンクが長く、特定の上流サービスが不安定な場合、リンク全体の障害を引き起こしやすいことです。
変革後のプロセスと達成目標:
プロセス:
目標:
変換の目標も非常に明確で、既存のロジックを変換して弱い依存関係の割合をできるだけ増やすことです。第一に、非同期プリロードに便利です。第二に、弱い依存関係の代表を削除して、敷設することができます。操作をダウングレードし、特定のリンク ジッターによって引き起こされる全体的な影響を軽減するための基盤。
最初の変換後の新たな問題 [解決に重点を置く]:
論理的には、デカップリングは比較的単純であり、事前パラメータまたは冗長ロードにすぎませんが、今回は説明しません。
技術的には、変換の初期段階の非同期ロジックは主に @Async("tpXXX") でマークされており、これも最も速い実装方法ですが、主にガバナンスに関連する次の問題もあります。
プロジェクトと担当者が反復を続けると、@Async アノテーションがあちこちに飛び交います。
異なる担当者が他のモジュールに精通していない場合、異なるスレッド プールを共有できるかどうかを定義することができず、ほとんどの担当者が新しいスレッド プールを宣言するため、スレッド プール リソースがあふれてしまいます。
一部の不当な呼び出しシナリオでは、過剰な @Async または無効なアノテーションのネストが発生します。
ダウングレード メカニズムには反復的なコードが多すぎるため、さまざまなダウングレード スイッチを手動で頻繁に宣言する必要があります。
jsf はある程度のサポートを提供していますが、統一されたリクエスト レベルのキャッシュ メカニズムがありません。
スレッド プールのコンテキスト転送の問題。
スレッド プールのステータスの統合された監視とアラームが不足しているため、実際の操作プロセス中に各スレッド プールのステータスを観察することができず、スレッド プールのパラメータを毎回設定する必要がある場合があります。
エントリーポイント:
ほとんどのプロジェクトは com.xx.package.xxx.client などの個別の IO 呼び出しレイヤーをカプセル化するため、これを変換と管理に焦点を当てるエントリ ポイントとして使用します。
最終目標:
シンプルな実装とアプリケーション、古いコード変換に適しており、変換コストを可能な限り削減します。
io 呼び出しテンプレートを抽象化し、io 呼び出し層のカプセル化仕様を統一し、io 呼び出しに必要な拡張属性宣言を標準化し、スレッド プール割り当て、タイムアウト、キャッシュ、ヒューズ、ダウングレードなどのデフォルト設定を提供します。
@Async 呼び出しを最適化し、すべての io 非同期操作が io 呼び出し層に一律に縮小され、コールバック メカニズムがテンプレート層に実装され、古いコードはテンプレートを継承するだけで非同期コールバックを実現できます。
リクエストレベルのキャッシュ実装は、デフォルトで r2m をサポートします。
リクエスト レベルのヒューズ ダウングレード サポートにより、上流で障害が発生した場合にサービスがある程度の自己統治を実現できるようになります。
スレッド プールの一元管理。MDC パラメータを自動的に渡すコンテキストのサポートを提供します。
スレッドプールのステータスを自動的に視覚的に監視し、アラームを実現します。
構成センターの動的設定をサポートします。
実装:
1.io呼び出し抽象テンプレート
テンプレートの主な機能は、標準化と強化です。現在、デフォルト テンプレートとキャッシュ テンプレートの 2 つのテンプレートが提供されています。中心となるアイデアは、スレッド プールのグループ化やキャッシュ テンプレートなど、IO 操作に関係する動作のほとんどを宣言することです。現在のサービスが属するリクエスト グループ化 委託コンポーネント 宣言された属性に従って実装を拡張します。例は次のとおりです。
主な目的は、コード レベルでデフォルトの宣言を提供することですが、日常業務の観点から見ると、そのほとんどは開発中にコード レベルで設定できます。
2.代理店
此委托属于整个执行过程的桥接实现,io封装实现继承抽象模板后,由模板创建委托代理实例,主要用于对io封装进行增强实现,比如调用前、调用后、以及调用失败自动调用声明的降级方法等处理。
可以理解为:模板专注请求行为,委托关注对象行为进行组合增强。
3. 执行器选型
基于前面的实现目标,减少自研成本,调研目前已有框架,如 hystrix、sentinel、resilience4j,由于主要目的是期望支持线程池级别的壁舱模式实现,且hystrix集成度要优于resilience4j,最终选型默认集成hystrix,备选resilience4j, 以此实现线程池的动态创建管理、熔断降级、半连接重试等机制,HystrixCommander实现如下:
4. hystrix 适配 concrete 动态配置
继承concrete.PropertiesNotifier, 注册HystrixPropertiesNotifier监听器,缓存配置中心所有以hystrix起始的key配置;
实现HystrixDynamicProperties,注册ConcreteHystrixDynamicProperties替换默认实现,最终支持所有的hystrix配置项,具体用法参考hystrix文档。
5. hystrix 线程池上下文传递改造
hystrix已经提供了改造点,主要是对HystrixConcurrencyStrategy#wrapCallable方法重写实现即可,在submit任务前暂存主线程上下文进行传递。
6. hystrix、jsf、spring注册线程池状态多维可视化监控、报警
主要依赖以下三个自定义组件,注册一个状态监控处理器,单独启动一个线程,定期(每秒)收集所有实现数据上报模板的实例,通过指定的通道实现状态数据推送,目前默认使用PFinder上报:
ThreadPoolMonitorHandler 定义一个线程状态监控处理器,定期执行上报过程;
ThreadPoolEndpointMetrics 定义要上报的数据模板,包括应用实例、线程类型(spring、jsf、hystrix……)、类型线程分组、以及线程池的几个核心参数;
AbstractThreadPoolMetricsPublisher 定义监控处理器执行上报时依赖的通道(Micrometer、PFinder、UMP……)。
例如以下是hystrix的状态收集实现,最终可实现基于机房、分组、实例、线程池类型、名称等不同维度的状态监控:
PFinder实际效果:支持不同维度组合查看及报警
7. 提供统一await future工具类
由于大部分调用是基于列表形式的异步结果List<Future<T>>、Map<String,Future<T>>,并且hystrix目前暂不支持返回CompletableFuture,方便统一await,提供工具类:
8. 其他小功能
除了sgm traceId支持,同时内置自定义的traceId实现,主要是处理sgm在子线程内打印traceId需要在控制台手动添加监控方法的问题以及提供对部分无sgm环境的链路Id支持,方便日志跟踪;
比如针对jsf调用,基于jsf过滤器实现跨应用级别的前后请求id传递支持;
默认增加jsf过滤器实现日志打印,同时支持provider、consume的动态日志打印开关,方便线上随时开关jsf日志,不再需要在client层重复logger.isDebugerEnabled();
代理层自动上报io调用方法、fallback等信息至ump,方便监控报警。
日常使用示例:
1. 一个最简单的io调用封装
仅增加继承即可支持异步回调,不重写线程池分组时使用默认分组。
2. 一个支持请求级别熔断的io调用封装
默认支持的熔断级别是服务级别,老服务仅需要继承原请求参数,实现FallbackRequest接口即可,可防止因为某一个特殊参数引起的整体接口熔断。
3. 一个支持请求级别缓存、接口级别熔断降级、独立线程池的io调用封装
4. 上层调用,实际效果
直接将一个商品列表转换成一个异步属性绑定任务;
利用工具类await List<Future<T>>;
在上层无感知的状态下,实现线程池的管理、熔断、降级、或缓存逻辑的增强,且可根据pfinder监控的可视化线程池状态,通过concrete实时调整线程池及超时或熔断参数;
举例:比如某接口频繁500ms超时,可通过配置直接打开短路返回降级结果,或者调低超时为100ms,快速触发熔断,默认10s内请求总数达到20个,50%失败时打开断路器,每隔5s半链接重试。
本篇主要是思考如何依赖现有框架、环境的能力,从代码层面系统化的实现相关治理规范。
最后仍引用王晓老师文章结尾来结束
接口性能问题形成的原因思考我相信很多接口的效率问题不是一朝一夕形成的,在需求迭代的过程中,为了需求快速上线,采取直接累加代码的方式去实现功能,这样会造成以上这些接口性能问题。变换思路,更高一级思考问题,站在接口设计者的角度去开发需求,会避免很多这样的问题,也是降本增效的一种行之有效的方式。
以上,共勉!
-end-
本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。