この記事では、参考になる例外と結果(コード例)を紹介しますので、困っている方は参考にしていただければ幸いです。
分散システム開発では、さまざまなステータス コードやエラー メッセージを最も外側の呼び出し元 (通常は http/API インターフェイス) に渡す必要があり、ログイン失敗やパラメータ エラーなどのエラー メッセージを渡す必要があります。
最も外側のインターフェイスによって公開されるデータは、通常、{code, msg, data} のような json 形式です。これについては異論はありません。
しかし、分散システムにおけるノード間の RPC 呼び出しやノード内のメソッド呼び出しでは、通常、エラー情報は ServiceException または Result
結果
これは、エラー情報を送信する比較的一般的な方法です。一部の大手メーカーでは、エラー情報を直接技術仕様として設定し、全員にチームの指示を強制しています。はこのアプローチをとります。一般的な結果テンプレートは次のとおりです:
@Data public class Result<T> { private int code; // 也可以是String等 private String msg; private T data; }
システム開発におけるアプリケーションは、通常次のようになります:
Result<UserModel> userModelResult = userService.query(userId); if (!userModelResult.isSuccess() || userModelResult.getData != null) { return Result.fail(userModelResult); // 透传错误 } UserModel userModel = userModelResult.getData(); if (userModel.getStatus() != UserStatusEnum.NORMAL) { return Result.fail("user unavaliable"); // 用户不可用 } // ... 正常使用UserModel
より複雑な分散マイクロサービス環境では、類似したコードが多数あり、それぞれが依存するサービスを呼び出します。同様のフォールトトレランスロジックが伴います。
このモードは Golang 言語のエラーコード処理に似ており、すべてのステップでエラー判定を行わなければならないという点で Golang が批判されている点でもあります。
さらに残酷な現実は、Result のカプセル化にもかかわらず、バックエンド システムから透過的に送信される例外が依然として存在するということです。私がこれまでに接してきた実際のアプリケーションでは、Result カプセル化を突破するこの種の異常な透過的送信は決して特殊なケースではなく、私が担当するシステムがバックエンドで最も強力な国内取引システムを呼び出す場合、最も内部的な取引センターから TC を受け取りましたが、ビジネスは異常で、問題のトラブルシューティング中に 5 つ以上のチームが追跡されました。
ServiceException の概要
名前が示すように、このメソッドは例外割り込みを使用して、通常のロジックと例外ロジックを分割します。
システム開発では、ほとんどのエラーはサービスを直接中断し、ユーザーにエラーを直接フィードバックする必要があるため、Result
通常、ServiceException は次のように定義できます:
@Getter public class ServiceException extends RuntimeException { private final int code; private final String msg; public ApiException() { this(-1, null); } public ApiException(Code code) { this(code, null); } public ApiException(Code code, String msg) { super(msg); this.code = code; this.msg = msg; } }
システムの内部コンポーネントがデータ損失、不正アクセス、ログイン失敗、アカウントのロックアウトなどの異常な状況に遭遇した場合、コンポーネントは直接スローします。 ServiceException を使用してロジックを中断し、最も外側のフィルターまたはアスペクトが例外をキャッチし、コードとメッセージを抽出してユーザーに返します。
実際に使用されるコード ロジックは次のようなものです。
UserModel userModel = userService.query(userId); // userID不存在、不可用等隐藏在异常中 // ... 使用userModel
このメソッドは明らかに洗練されており、合理化されており、開発効率の向上とその後のメンテナンスに役立ちます。
しかし、市場では、異常な割り込みを使用するとパフォーマンスに影響を与えるという噂が数多く流れており、簡単なパフォーマンス テストを通じて、異常な割り込みのパフォーマンス時間は Result を返す時間よりも数百倍速いと結論付ける人もいます。
パフォーマンス テスト
パフォーマンスの問題を考慮して、簡単なテストも実施しました。具体的なテスト コードについては、
https を参照してください。 ://github. com/sisyphsu/b...
JMH はパフォーマンス テストに使用されています。ベンチマークと言えば、golang 言語に付属のテスト ライブラリがとても便利です。
テストの内部ビジネス ロジックは非常に単純で、System.currentTimeMillis() を 1 回呼び出して長いタイムスタンプを返すだけです。
Result
Test.test avgt 5 0.027 ± 0.001 us/op Test.testException avgt 5 1.060 ± 0.045 us/op Test.testDeep10Exception avgt 5 1.826 ± 0.122 us/op Test.testDeep100Exception avgt 5 9.802 ± 0.411 us/op
一見したところ、例外スタック深さ 100 のパフォーマンス損失は、実際に通常のメソッド呼び出しの 360 倍です。この理由に基づいて、Java 例外割り込みは重大なパフォーマンス損失を引き起こすと結論付けられます。
パフォーマンスの影響の分析
ただし、時間単位はわずかマイクロ秒、1000分の1ミリ秒、100万分の1秒であることに注意する必要があります。
あるマイクロサービスの単一 CPU スループットが 1000QPS で、そのうち 10% が不正なリクエストであると仮定すると、異常な中断によるパフォーマンスの損失はわずか 1 万分の 1 であり、サービス時間への影響はわずか 0.001 です。ほんの数ミリ秒です。
パフォーマンス テストでは、ビジネス時間はシステム時間を取得するだけであり、約 25ns かかります。このため、異常中断によるパフォーマンスの低下は「数百倍」という恐ろしい量に達しますが、ビジネスタイムが25nsから25usまたは25msに変わったらどうでしょうか?
パフォーマンスのボトルネックについて話しましょう
システムのパフォーマンスを分析するときは、その規模とパフォーマンスのボトルネックを理解し、パフォーマンスの最適化のジレンマに陥ることを忘れないでください。
大まかな例を挙げると、通常のサービスでは、インデックスを使用した DB 操作に 1 ~ 10 ミリ秒かかり、分散キャッシュへのアクセスに 3 ~ 30 ミリ秒かかり、マイクロサービス RPC のネットワーク パフォーマンスの損失は 3 ~ 10 ミリ秒かかります。クライアントとサーバー間のネットワークには 5 ~ 300 ミリ秒かかります。この場合、0.001 ミリ秒のパフォーマンス リスクを最適化することは、ゴマを拾ってスイカを失うようなものです。
以前、TCP に似た基盤となるネットワーク プロトコルについて書きました。その高頻度シナリオでは、アルゴリズムの最適化により 0.1 マイクロ秒のパフォーマンス最適化がもたらされます。これは、1 秒あたりのスループットが数倍、さらには数倍向上することを意味します。ただし、分散呼び出しの低頻度シナリオでは、このパフォーマンス上の利点は役に立ちません。
別の例として、数年前に同僚と私が DB データ テーブルの設計について話し合っていたとき、注文ステータスにどのくらいの int を使用するべきかについて真っ赤な口論になりました。 1 注文ステータス バイトに最適化されているため、長年にわたって節約できるディスク容量は 1 MB 未満です。何の役に立つのですか?
RPC での異常割り込み
Dubbo や HSF などのリモート呼び出しフレームワークの場合、異常割り込みを使用してエラー情報を転送する場合、注意すべき点の 1 つは、例外タイプが次のように設計されている必要があることです。ユニバーサル、つまり、各マイクロサービスによって参照される基本的な型。
ある工場の技術仕様書に記載されています:
1) 例外リターンメソッドを使用する場合、呼び出し元がそれをキャッチしないと実行時エラーが発生します。
2) スタック情報を追加せず、新しいカスタム例外だけを追加し、エラー メッセージについての独自の理解を追加した場合、呼び出し側の問題解決にはあまり役に立ちません。スタック情報を追加した場合、呼び出しエラーが多発した場合、データのシリアル化や送信のパフォーマンス低下も問題となります。
私はこの技術仕様にかなり不満があります。
まず第一に、ビジネス例外は呼び出し元によって最も外側の層に透過的に送信される必要があります。データの存在なし、ログイン失敗、ユーザーのロックアウトなどの例外は、呼び出し元がキャッチすると役に立たないことがよくあります。真ん中。
2 つ目はパフォーマンスの損失ですが、この低頻度のデータのシリアル化とイントラネットの送信ではどのようなパフォーマンスの損失が発生するでしょうか?スタック情報を呼び出し元に透過的に送信することは、トラブルシューティングにも役立ちます。私はかつて TC の例外スタック情報を受信しました。スタック内のパッケージによると、最下位レベルのエラーを見つけるために 3 番目と 4 番目の層を直接バイパスしました。時間を大幅に節約できました。
結論
分散マイクロサービスでは、異常な割り込みを使用することでビジネス コードを大幅に簡素化し、パフォーマンスへの影響を最小限に抑えることができます。
@NotNull、@Nullable、その他のアノテーションのおかげで、分散開発は風のように速く便利になります。複雑なサービス ネットワークでは、ビジネス例外により、開発者がエラーを正確に特定し、呼び出しチェーンに沿って層ごとに障害点を追跡するという恥ずかしい状況を回避することも容易になります。
以上が例外と結果の概要 (コード例)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。