1. センチネルについて知る
1.1. 雪崩の問題と解決策
1.1.1. 雪崩問題
マイクロサービス呼び出しリンクでサービス障害が発生すると、リンク全体のすべてのマイクロサービスが利用できなくなり、これが雪崩となります。
1.1.2. 雪崩問題を解決するには 4 つの一般的な方法があります
1.1.2.1、タイムアウト処理
- タイムアウト時間を設定します。リクエストは一定時間が経過しても応答がない場合にエラー メッセージを返し、無限に待機しません。
1.1.2.2. バルクヘッドモード
倉庫の壁のパターンはキャビンのデザインから来ています。
客室内は隔壁によって独立した複数の空間に区切られており、船体が破損した場合にはその一部の空間のみが侵入し、船体全体が浸水することを防ぐため破損を一定の範囲内に制御する。
- Tomcat 全体のリソースを使い果たさないように、各ビジネスが使用できるスレッドの数を制限するため、スレッド分離とも呼ばれます。
1.1.2.3、サーキットブレーカー
- 業務実行の異常な割合はサーキットブレーカーによってカウントされ、しきい値を超えると業務が融合され、業務へのアクセス要求がすべて遮断されます。
サーキット ブレーカーは、サービスへのアクセス要求の数、異常な比率をカウントします。
サービス D へのアクセス要求の異常な割合が高すぎることが判明した場合、サービス D には雪崩が発生するリスクがあるとみなされ、サービス D へのアクセス要求はすべて遮断されてサーキット ブレーカーが形成されます。
1.1.2.4、電流制限
フロー制御: QPS により、トラフィックの急激な増加によるサービス障害を回避するためにビジネス アクセスを制限します。
1.1.3. 概要
雪崩問題とは何ですか?
- 呼び出しチェーン内のサービス障害によりリンク全体がアクセスできなくなるため、マイクロサービスは互いに呼び出します。
瞬間的な大量の同時トラフィックによって引き起こされるサービス障害を回避するにはどうすればよいですか?
- フロー制御
サービス障害によって引き起こされる雪崩問題を回避するにはどうすればよいですか?
- タイムアウト処理
- スレッドの分離
- サーキットブレーカーのダウングレード
次のことが考えられます。
電流制限はサービスの保護であり、瞬間的な大量の同時トラフィックによって引き起こされるサービス障害を回避し、雪崩を回避します。予防措置です。
タイムアウト処理、スレッド分離、およびダウングレード ヒューズを使用して、障害を特定の範囲内に制御し、一部のサービスが失敗した場合の雪崩を回避します。は救済策です。
1.2. サービス保護技術の比較
Spring Cloud では、複数のサービス保護テクノロジーがサポートされています。
初期には Hystrix フレームワークの方が人気がありましたが、現在中国で最も広く使用されているのは Alibaba の Sentinel フレームワークです。
センチネル | ヒストリックス | |
---|---|---|
隔離ポリシー | セマフォの分離 | スレッドプール分離/セマフォ分離 |
サーキットブレーカーのダウングレード戦略 | スローコール比率または異常比率に基づく | 故障率に基づく |
リアルタイムインジケーターの実装 | スライドウィンドウ | スライディング ウィンドウ (RxJava ベース) |
ルール設定 | 複数のデータソースをサポート | 複数のデータソースをサポート |
スケーラビリティ | 複数の拡張ポイント | プラグインフォーム |
注釈ベースのサポート | サポート | サポート |
制限する | QPSに基づいて、通話関係に基づいた電流制限をサポート | 限定的なサポート |
トラフィックシェーピング | スロースタート、均一キューイングモードをサポート | サポートしません |
システム適応型保護 | サポート | サポートしません |
コンソール | すぐに使用できるルールの設定、第 2 レベルの監視、マシン検出などの表示を行うことができます。 | 不完全 |
共通フレームワークへの適応 | サーブレット、Spring Cloud、Dubbo、gRPC など | サーブレット、Spring Cloud Netflix |
1.3、Sentinelの導入とインストール
1.3.1. Sentinel について知る
Sentinel は、Alibaba がオープンソース化したマイクロサービス トラフィック制御コンポーネントです。公式サイトアドレス: https: //sentinelguard.io/zh-cn/index.html
センチネルには次の特徴があります。
-
豊富なアプリケーション シナリオ: Sentinel は、過去 10 年間にアリババのダブル イレブン トラフィック促進の中核となるシナリオ、たとえば seckill (つまり、システム容量が耐えられる範囲内でバースト トラフィックを制御する)、メッセージのピーク シェービングとバレー フィリングなどを実行してきました。 、クラスタートラフィック制御、ダウンストリームの利用できないアプリケーションのリアルタイム融合など。
-
完全なリアルタイム監視: Sentinel はリアルタイム監視機能も提供します。コンソールでは、アプリケーションに接続されている単一マシンの第 2 レベルのデータ、さらには 500 未満の規模のクラスターの集計された実行ステータスを確認できます。
-
広範なオープンソース エコシステム: Sentinel は、Spring Cloud、Dubbo、gRPC との統合など、他のオープンソース フレームワーク/ライブラリとのすぐに使える統合モジュールを提供します。対応する依存関係を導入し、簡単な構成を実行するだけで、Sentinel にすばやくアクセスできます。
-
完璧な SPI 拡張ポイント: Sentinel は、使いやすく完全な SPI 拡張インターフェイスを提供します。拡張インターフェイスを実装することで、ロジックをすばやくカスタマイズできます。たとえば、カスタム ルールの管理、動的データ ソースの適応などです。
1.3.2. Sentinel のインストール
1) ダウンロード
Sentinel は公式に UI コンソールを提供しており、システムに現在の制限を設定するのに便利です。GitHubでダウンロードできます。
2) 走る
jar パッケージを中国語以外のディレクトリに置き、次のコマンドを実行します。
java -jar sentinel-dashboard-1.8.1.jar
Sentinel のデフォルトのポート、アカウント、パスワードを変更する場合は、次の構成を使用できます。
構成アイテム | デフォルト | 説明する |
---|---|---|
サーバポート | 8080 | サービスポート |
センチネル.ダッシュボード.認証.ユーザー名 | 番兵 | デフォルトのユーザー名 |
センチネル.ダッシュボード.認証.パスワード | 番兵 | デフォルトのパスワード |
たとえば、ポートを変更するには次のようにします。
java -Dserver.port=8090 -jar sentinel-dashboard-1.8.1.jar
3) 訪問
http://localhost:8080 ページにアクセスすると、Sentinel コンソールが表示されます。
アカウント番号とパスワードを入力する必要があります。デフォルトは「sentinel」です。
ログインすると、空白で何もありませんでした。
これは、まだマイクロサービスと統合されていないためです。
1.4. マイクロサービス統合 Sentinel
センチネルをオーダーサービスに統合し、センチネルコンソールに接続します。手順は次のとおりです。
1) センチネル依存関係を導入する
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2) コンソールの設定
application.yaml ファイルを変更し、次のコンテンツを追加します。
server:
port: 8088
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
3) 注文サービスのエンドポイントにアクセスする
ブラウザを開いて http://localhost:8088/order/101 にアクセスし、センチネルの監視を開始します。
次に、センチネル コンソールにアクセスして効果を確認します。
2. フロー制御
2.1. クラスターリンク
リクエストがマイクロサービスに入ると、最初に DispatcherServlet にアクセスし、次にコントローラー、サービス、およびマッパーに入ります。このような呼び出しチェーンはクラスター ポイント リンクと呼ばれます。クラスタ リンクで監視される各インターフェイスはリソースです。
デフォルトでは、sentinel は SpringMVC の各エンドポイント (コントローラー内のメソッドである Endpoint) を監視するため、SpringMVC の各エンドポイント (Endpoint) は呼び出しリンク内のリソースになります。
たとえば、先ほどアクセスした order-service の OrderController のエンドポイント: /order/{orderId}
フロー制御、ヒューズなどはすべてクラスター リンク内のリソースに設定されているため、対応するリソースの後ろにあるボタンをクリックしてルールを設定できます。
- フロー制御: フロー制御
- ダウングレード: ダウングレードヒューズ
- ホットスポット: ホットスポット パラメータの電流制限。電流制限の一種です。
- 認可: 権限制御のリクエスト
2.2. クイックスタート
2.2.1. 例
resource/order/{orderId} の後ろにあるフロー制御ボタンをクリックしてフォームをポップアップします。次のように、フォームに電流制限ルールを入力できます。
その意味は、リソース /order/{orderId} のスタンドアロン QPS を 1 に制限することです。つまり、1 秒あたり 1 つのリクエストのみが許可され、超過したリクエストはインターセプトされ、エラーが報告されます。
2.2.2. 練習:
要件: リソース /order/{orderId} のフロー制御ルールを設定し、QPS は 5 を超えてはならず、テストします。
1) まず、センチネル コンソールに電流制限ルールを追加します。
2) jmeter を使用してテストする
3) 結果
ご覧のとおり、毎回成功するリクエストは 5 件のみです。
2.3、フロー制御モード
フロー制限ルールを追加する場合、[詳細オプション] をクリックして 3 つのフロー制御モードから選択します。
- 直接: 現在のリソースの統計リクエスト。しきい値がトリガーされたときに現在のリソースのフローを直接制限します。これはデフォルト モードでもあります。
- 関連: 現在のリソースに関連する別のリソースをカウントし、しきい値がトリガーされたときに現在のリソースのフローを制限します。
- リンク: 指定されたリンクからこのリソースにアクセスするリクエストをカウントし、しきい値がトリガーされたときに指定されたリンクのフローを制限します。
クイックスタート テストはダイレクト モードです。
2.3.1. アソシエーションモード
アソシエーション モード: 現在のリソースに関連する別のリソースをカウントし、しきい値がトリガーされたときに現在のリソースのフローを制限します。
設定ルール:
文法の説明: /write リソースのアクセス量がしきい値をトリガーすると、/write リソースへの影響を避けるために、/read リソースのフローが制限されます。
使用シナリオ: たとえば、ユーザーは支払い時に注文ステータスを変更する必要があり、同時に注文をクエリしたいと考えています。クエリおよび変更操作はデータベース ロックをめぐって競合し、競合が発生します。ビジネス要件は、注文の支払いと更新のビジネスを優先することであるため、注文ビジネスのトリガーしきい値を変更する場合は、クエリ注文ビジネスのフローを制限する必要があります。
要件の説明:
-
OrderController で 2 つのエンドポイントを作成します: /order/query と /order/update、ビジネスを実装する必要はありません
-
フロー制御ルールを構成します。/order/ update リソースによってアクセスされる QPS が 5 を超える場合、/order/query リクエストのフローを制限します。
1) 注文クエリをシミュレートする /order/query エンドポイントを定義します。
@GetMapping("/query")
public String queryOrder() {
return "查询订单成功";
}
2) 注文の更新をシミュレートするために /order/update エンドポイントを定義します
@GetMapping("/update")
public String updateOrder() {
return "更新订单成功";
}
サービスを再起動し、Sentinel コンソールのクラスター リンクを表示します。
3) フロー制御ルールを構成する
どのエンドポイントの電流を制限するには、そのエンドポイントの後ろにあるボタンをクリックします。注文クエリ/注文/クエリを制限しているので、その後ろにあるボタンをクリックしてください:
次の形式でフロー制御ルールを入力します。
4) Jmeterでのテスト
1000 人のユーザーを 100 秒間表示できるため、QPS は 10 となり、設定したしきい値 5 を超えます。
http リクエストを見ると、リクエストのターゲットは /order/update であるため、このブレークポイントがしきい値をトリガーします。
しかし、現在の制限のターゲットは /order/query なので、ブラウザでアクセスすると、現在の制限が実際に制限されていることがわかります。
5) まとめ
アソシエーション モードは、次の条件が満たされる場合に使用できます。
- 2 つの競合するリソース
- 優先度の高いものと優先度の低いもの
2.3.2、リンクモード
リンクモード: 指定したリンクからのこのリソースへのアクセスリクエストのみを統計し、閾値を超えているかどうかを判定します。
設定例:
たとえば、次の 2 つのリクエスト リンクがあります。
-
/test1 --> /common
-
/test2 --> /common
/test2 から /common へのリクエストのみをカウントしたい場合は、次のように設定できます。
実際の事例
要件: 注文クエリ サービスと注文作成サービスがあり、どちらも商品をクエリする必要があります。注文のクエリから製品のクエリまでのリクエストの統計、および現在の制限の設定用。
ステップ:
-
ビジネスを実装せずに OrderService に queryGoods メソッドを追加する
-
OrderController で、/order/query エンドポイントを変更し、OrderService の queryGoods メソッドを呼び出します。
-
/order/save エンドポイントを OrderController に追加し、OrderService の queryGoods メソッドを呼び出します。
-
queryGoods の現在の制限ルールを設定します。/order/query から queryGoods を入力する方法の QPS 制限は 2 未満である必要があります
達成:
2.3.2.1、クエリ製品メソッドの追加
order-service サービスで、orderService クラスに queryGoods メソッドを追加します。
public void queryGoods(){
System.err.println("查询商品");
}
2.3.2.2. 注文を問い合わせるときは、製品を問い合わせます
order-service の OrderController で、/order/query エンドポイントのビジネス ロジックを変更します。
@GetMapping("/query")
public String queryOrder() {
// 查询商品
orderService.queryGoods();
// 查询订单
System.out.println("查询订单");
return "查询订单成功";
}
2.3.2.3、新しい注文の追加、製品のクエリ
order-service の OrderController で、/order/save エンドポイントを変更して、新しい注文をシミュレートします。
@GetMapping("/save")
public String saveOrder() {
// 查询商品
orderService.queryGoods();
// 查询订单
System.err.println("新增订单");
return "新增订单成功";
}
2.3.2.4. クエリ製品にリソースタグを追加する
デフォルトでは、OrderService のメソッドは Sentinel によって監視されないため、アノテーションを通じて監視するメソッドをマークする必要があります。
@SentinelResource アノテーションを OrderService の queryGoods メソッドに追加します。
@SentinelResource("goods")
public void queryGoods(){
System.err.println("查询商品");
}
リンク モードでは、異なるソースからの 2 つのリンクが監視されます。ただし、デフォルトでは、Sentinel は SpringMVC に入るすべてのリクエストに対して同じルート リソースを設定するため、リンク モードが失敗します。
SpringMVC のこのリソース集約をオフにし、order-service サービスの application.yml ファイルを変更する必要があります。
spring:
cloud:
sentinel:
web-context-unify: false # 关闭context整合
サービスを再起動し、/order/query および /order/save にアクセスすると、新しいリソースが Sentinel のクラスター ポイント リンク ルールに表示されていることがわかります。
2.3.2.5. フロー制御ルールの追加
商品リソースの後ろにあるフロー制御ボタンをクリックし、ポップアップ フォームに次の情報を入力します。
/order/query から /goods に入るリソースのみがカウントされ、QPS のしきい値は 2 で、それを超えるとフローが制限されます。
2.3.2.6、Jmeterテスト
ここには 200 人のユーザーがおり、送信は 50 秒以内に完了し、QPS は 4 で、設定したしきい値 2 を超えていることがわかります。
2.3.3. 概要
フロー制御モードとは何ですか?
• 直接: 現在のリソースを制限する
• 関連: 優先度の高いリソースはしきい値をトリガーし、優先度の低いリソースは制限されます。
• リンク: しきい値をカウントする場合、指定されたリソースから現在のリソースに入るリクエストのみがカウントされます。これは、リクエストのソースに対する現在の制限です。
2.4、フロー制御効果
フロー制御の詳細オプションには、フロー制御効果オプションもあります。
フロー制御の効果とは、リクエストがフロー制御のしきい値に達したときに実行する必要がある措置を指します。次の 3 つのタイプがあります。
-
フェイルファースト: しきい値に達すると、新しいリクエストは直ちに拒否され、FlowException がスローされます。はデフォルトの処理方法です。
-
Warm up: ウォームアップ モード。しきい値を超えるリクエストも拒否され、例外がスローされます。ただし、このモードのしきい値は動的に変化し、小さな値から最大しきい値まで徐々に増加します。
-
キュー内で待機: すべてのリクエストがキュー内で順番に実行されます。2 つのリクエスト間の間隔は、指定された時間より短くすることはできません。
2.4.1、ウォームアップ
しきい値は通常、マイクロサービスが引き受けることができる最大 QPS です。ただし、サービスが開始されたばかりのときは、すべてのリソースが初期化されていません (コールド スタート)。QPS が直接最大値まで実行されると、サービスが停止する可能性があります。瞬時に下がります。
ウォームアップは、ウォームアップ モードとも呼ばれ、サービスのコールド スタートに対する解決策です。リクエストしきい値の初期値は maxThreshold / coldFactor で、指定された期間が経過すると、maxThreshold 値まで徐々に増加します。coldFactor のデフォルト値は 3 です。
たとえば、QPS の maxThreshold を 10 に設定し、ウォームアップ時間を 5 秒に設定した場合、最初のしきい値は 10 / 3 (つまり 3) で、5 秒後に徐々に 10 に増加します。
ケース
要件: リソース /order/{orderId} に電流制限を設定し、最大 QPS は 10 で、ウォームアップ効果を使用し、ウォームアップ時間は 5 秒です。
2.4.1.1. フロー制御ルールを設定します。
2.4.1.2、Jmeterテスト
QPSは10です。
開始したばかりのときは、ほとんどのリクエストが失敗し、成功したリクエストは 3 つだけでした。これは、QPS が 3 に制限されていることを示しています。時間の経過とともに、成功率はますます高くなっています。
2.4.2. 列に並んで待つ
フェイルファーストおよびウォームアップは、新しいリクエストを拒否し、リクエストが QPS しきい値を超えると例外をスローします。
キューに入れて待機することは、すべてのリクエストをキューに入れ、しきい値で許可された時間間隔に従って順番に実行することです。後続のリクエストは、前の実行が完了するまで待機する必要があり、リクエストの予想待機時間が最大時間を超える場合は拒否されます。
動作原理
例: QPS = 5 は、キュー内のリクエストが 200 ミリ秒ごとに処理されることを意味し、タイムアウト = 2000 は、予想待ち時間が2000 ミリ秒を超えるリクエストが拒否され、例外がスローされることを意味します。
それで、予想される待ち時間はどのくらいですか?
たとえば、200 ミリ秒ごとに 1 つのリクエストが実行されるため、一度に 12 のリクエストが送信されます。
- 6 番目のリクエストの予想待機時間= 200 * (6 - 1) = 1000ms
- 12 番目のリクエストの予想待機時間 = 200 * (12-1) = 2200ms
最初の 1 秒間に 10 個のリクエストが同時に受信されますが、2 番目の秒間に受信されるリクエストは 1 つだけです。このときの QPS 曲線は次のようになります。
フロー制御にキュー モードを使用する場合、すべての受信リクエストはキューに入れられ、200 ミリ秒の固定間隔で実行される必要があり、QPS は非常にスムーズになります。
滑らかな QPS 曲線はサーバーにとってよりフレンドリーです。
ケース
要件: リソース /order/{orderId} の現在の制限を設定し、最大 QPS は 10 で、キューイングのフロー制御効果を使用し、タイムアウト期間を 5 秒に設定します。
2.4.2.1. フロー制御ルールの追加
2.4.2.2、Jmeterテスト
QPSは15で、設定した10を上回りました。
以前の高速障害およびウォームアップ モードの場合、超過リクエストはエラーを直接報告する必要があります。
しかし、キュー モードの結果を見てみましょう。すべてパスしました。
次に、センチネルに移動して、リアルタイム モニタリングの QPS 曲線を表示します。
QPS は非常にスムーズで、一貫して 10 に維持されますが、超過したリクエストは拒否されず、キューに入れられます。そのため、応答時間(待ち時間)はますます長くなります。
キューがいっぱいになると、一部のリクエストは失敗します。
2.4.3. 概要
フロー制御効果とは何ですか?
-
フェイルファースト: QPS がしきい値を超えた場合に新しいリクエストを拒否します
-
ウォームアップ: QPS がしきい値を超えると、新しいリクエストは拒否されます。QPS しきい値は徐々に増加し、コールド スタート時の同時実行性の高さによって引き起こされるサービスのダウンタイムを回避できます。
-
キューで待機: リクエストはキューに入り、しきい値で許可された時間間隔に従って順番に実行されます。リクエストの予想待機時間がタイムアウト時間よりも長い場合、リクエストは直接拒否されます。
2.5. ホットスポットパラメータの電流制限
以前の電流制限は、特定のリソースにアクセスするすべてのリクエストをカウントし、それが QPS しきい値を超えているかどうかを判断することでした。ホットスポットパラメータの電流制限は、同じパラメータ値を持つリクエストを個別にカウントし、QPSのしきい値を超えているかどうかを判断します。
2.5.1. グローバルパラメータの電流制限
たとえば、ID で製品をクエリするためのインターフェイスは次のとおりです。
/goods/{id} へのアクセス要求では、id パラメーターの値が変更され、ホットスポット パラメーターの電流制限により、パラメーター値に応じて QPS が個別にカウントされます。統計結果は次のとおりです。
id=1 のリクエストによってしきい値が制限される場合、id 値が 1 以外のリクエストは影響を受けません。
構成例:
代表値の意味は、ホット リソースのパラメータ 0 (最初のパラメータ) に関する統計を作成し、 1 秒あたりの同じパラメータ値に対するリクエストの数が5 を超えることはできないことです。
2.5.2. ホットスポットパラメータの電流制限
先ほどの設定では、製品をクエリするためのインターフェイスのすべての製品が同等に扱われ、QPS は 5 に制限されます。
実際の開発では、フラッシュセール商品などの人気商品もあるかもしれませんが、そのような商品の QPS 制限は他の商品とは異なり、より高いことが望まれます。次に、ホットスポット パラメータの電流制限の詳細オプションを設定する必要があります。
前の設定と組み合わせると、ここでの意味は、番号 0 のロング型パラメータの電流を制限することであり、次の 2 つの例外を除いて、同じパラメータの QPS は 5 秒あたり 5 を超えることはできません。
-
パラメータ値が 100 の場合、1 秒あたりに許可される QPS は 10 です。
-
パラメータ値が 101 の場合、1 秒あたりに許可される QPS は 15 です。
2.5.3. ケース
ケース要件: ホットスポット パラメーターの現在の制限をリソース /order/{orderId} に追加します。ルールは次のとおりです。
• デフォルトのホットスポット パラメータ ルールでは、1 秒あたりのリクエスト数は 2 を超えてはなりません。
• パラメータ 102 に例外を設定します。1 秒あたりのリクエスト数は 4 を超えてはなりません。
• パラメータ 103 に例外を設定します。1 秒あたりのリクエスト数は 10 を超えてはなりません。
注: ホットスポット パラメーターの現在の制限は、デフォルトの SpringMVC リソースでは無効です。リソースをマークするには @SentinelResource アノテーションを使用する必要があります。
2.5.3.1. リソースのマーキング
order-service の OrderController の /order/{orderId} リソースにアノテーションを追加します。
2.5.3.2. ホットスポットパラメータの電流制限ルール
このインターフェイスにアクセスすると、マークしたホット リソースが表示されることがわかります。
左側のメニューで[ホットスポット ルール]メニューをクリックします。
[追加] をクリックして、フォームに入力します。
2.5.3.3、Jmeterテスト
ここでのリクエストの QPS は 5 です。
3 つの http リクエストが含まれています。
共通パラメータ、QPS閾値は2
3. 分離とダウングレード
電流制限は予防策です。電流制限により、同時実行性の高さによって引き起こされるサービス障害を回避できますが、他の理由でサービスが失敗する可能性もあります。
これらの障害を特定の範囲内で制御し、雪崩を回避するには、スレッド分離(バルクウォール モード) とヒューズのダウングレード方法に依存する必要があります。
スレッド分離については前述しました: 呼び出し元がサービス プロバイダーを呼び出すと、呼び出し要求ごとに独立したスレッド プールが割り当てられます。障害が発生した場合、呼び出し元のすべてのリソースが使い果たされるのを避けるために、最大でもこのスレッド プール内のリソースが消費されます。 。
ヒューズダウングレード: サービスプロバイダーへの通話をカウントするために発信者側にサーキットブレーカーを追加するもので、通話の失敗率が高すぎるとサービスが切断され、サービスプロバイダーへのアクセスが許可されなくなります。
スレッドの分離であっても、ヒューズのダウングレードであっても、それがクライアント(呼び出し元) の保護であることがわかります。呼び出し元がリモート呼び出しを開始するときに、スレッド分離またはサービス融合を実行する必要があります。
また、マイクロサービスのリモート呼び出しはすべて Feign に基づいているため、Feign を Sentinel と統合し、Feign にスレッド分離とサービス ヒューズを実装する必要があります。
3.1、FeignClient は Sentinel を統合します
Spring Cloud では、マイクロサービス呼び出しはすべて Feign を通じて実装されるため、クライアント保護のために Feign と Sentinel を統合する必要があります。
3.1.1. 設定を変更してセンチネル機能を有効にする
OrderService の application.yml ファイルを変更して、Feign の Sentinel 機能を有効にします。
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
3.1.2. 書き込み失敗のダウングレードロジック
ビジネスの失敗後、エラーを直接報告することはできませんが、わかりやすいプロンプトまたはデフォルトの結果がユーザーに返される必要があります。これが失敗のダウングレード ロジックです。
障害後の FeignClient のダウングレード ロジックを作成する
① 方法 1: FallbackClass はリモート呼び出しの例外を処理できません
②方法 2: リモート呼び出しの例外を処理できる FallbackFactory を選択します。
ここでは、2 番目の方法の障害ダウングレード処理を示します。
ステップ 1 : feed-api プロジェクトでクラスを定義し、FallbackFactory を実装する
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
log.error("查询用户异常", throwable);
return new User();
}
};
}
}
ステップ 2 : Feeding-API プロジェクトの DefaultFeignConfiguration クラスに UserClientFallbackFactory を Bean として登録します。
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
ステップ 3 : feed-api プロジェクトの UserClient インターフェイスで UserClientFallbackFactory を使用します。
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
再起動後、注文クエリ サービスに一度アクセスし、センチネル コンソールを確認すると、新しいクラスター ポイントのリンクが表示されます。
3.1.3. 概要
Sentinel がサポートする Avalanche ソリューション:
- スレッド分離 (サイロ壁モード)
- サーキットブレーカーのダウングレード
Feign が Sentinel を統合する手順:
- application.yml で設定します: feign.sentienl.enable=true
- FeignClient用のFallbackFactoryを記述しBeanとして登録する
- FallbackFactory を FeignClient に構成する
3.2. スレッド分離 (バルクウォールモード)
3.2.1、スレッド分離の実装
スレッドの分離は次の 2 つの方法で実現できます。
-
スレッドプールの分離
-
セマフォ分離 (Sentinel はデフォルトで使用します)
図に示すように:
スレッドプール分離: 各サービスコールビジネスにスレッドプールを割り当て、スレッドプール自体を使用して分離効果を実現します
セマフォ分離: スレッド プールを作成する代わりに、カウンター モードを使用してビジネスで使用されるスレッド数を記録し、セマフォの上限に達すると、新しいリクエストを禁止します。
両方の長所と短所:
3.2.2、センチネルのスレッド分離
使用説明書:
スロットリング ルールを追加する場合、次の 2 つのしきい値タイプを選択できます。
-
QPS: クイック スタートで示されている、1 秒あたりのリクエストの数です。
-
スレッド数: このリソースで使用できる Tomcat スレッドの最大数です。つまり、スレッドの数を制限することで、スレッドの分離(バルクウォール モード) が実現されます。
ケース要件: order-service サービスの UserClient のクエリ ユーザー インターフェイスのフロー制御ルールを設定し、スレッド数が 2 を超えることはできません。次に、jemeter を使用してテストします。
3.2.2.1. 分離ルールの設定
偽インターフェイスの背後にあるフロー制御ボタンを選択します。
フォームに記入する:
3.2.2.2、Jmeterテスト
一度に 10 個のリクエストが発生した場合、同時スレッドの数が 2 を超える可能性が高く、超過したリクエストは以前に定義された障害劣化ロジックに従います。
3.2.3. 概要
スレッド分離の 2 つの手段は何ですか?
-
セマフォの分離
-
スレッドプールの分離
セマフォ分離の特徴は何ですか?
- カウンタモードに基づいており、シンプルでオーバーヘッドが低い
スレッド プール分離の特徴は何ですか?
- スレッド プール モードに基づいて追加のオーバーヘッドがありますが、分離制御はより強力です
3.3、ヒューズのダウングレード
ヒューズのダウングレードは、雪崩問題を解決する重要な手段です。その考え方は、サーキット ブレーカーがサービス コールの異常な割合と遅いリクエストの割合をカウントし、しきい値を超えるとサービスが中断されるというものです。つまり、サービスへのアクセス要求はすべて傍受され、サービスが復元されると、サーキット ブレーカーはサービスへのアクセス要求を解放します。
サーキット ブレーカーの制御の溶断と解放は、ステート マシンを通じて行われます。
ステート マシンは 3 つの状態で構成されます。
- クローズド: クローズ状態。サーキット ブレーカーはすべてのリクエストを解放し、例外と遅いリクエストの割合のカウントを開始します。閾値を超えるとオープン状態に切り替わります
- open: オープン状態では、サービス呼び出しは中断され、中断されたサービスへのアクセス要求は拒否され、フェイルファストして、ダウングレード ロジックに直接進みます。オープン状態で5秒後、半オープン状態になります。
- ハーフオープン: ハーフオープン状態ではリクエストが解放され、実行結果に応じて次の動作が判断されます。
- リクエストは成功しました。クローズ状態に切り替わります。
- リクエストが失敗しました: オープン状態に切り替えます
サーキットブレーカーのヒューズ戦略には、スローコール、異常比率、異常数の 3 種類があります。
3.3.1. 通話が遅い
遅い通話: サービス応答時間 (RT) が指定された時間より長いリクエストは、遅い通話リクエストとみなされます。指定された時間内に、リクエストの数が設定された最小数を超え、低速コールの割合が設定されたしきい値より大きい場合、サーキット ブレーカーがトリガーされます。
例:
解釈: RT が 500 ミリ秒を超えるコールは低速コールです。最後の 10,000 ミリ秒以内のリクエストをカウントします。リクエストの数が 10 を超え、低速コールの割合が 0.5 以上の場合、サーキット ブレーカーがトリガーされ、サーキットブレーカーは 5 秒間持続します。その後、ハーフオープン状態に入り、テスト用のリクエストをリリースします。
ケース
要件: UserClient のクエリ ユーザー インターフェイスのダウングレード ルールを設定します。低速呼び出しの RT しきい値は 50 ミリ秒、統計時間は 1 秒、リクエストの最小数は 5、失敗しきい値の比率は 0.4、ヒューズ期間は 5 です。
3.3.1.1. 低速通話の設定
user-service の /user/{id} インターフェースのサービスを変更します。スリープによる遅延時間をシミュレートします。
3.3.1.2. サーキットブレーカールールの設定
次に、偽インターフェイスのダウングレード ルールを設定します。50
ミリ秒を超えるリクエストは低速リクエストとみなされます。
3.3.1.3. テスト
ブラウザで http://localhost:8088/order/101 にアクセスし、すぐに 5 回更新すると、次の情報が見つかります。
サーキット ブレーカーがトリガーされ、リクエスト期間が 5ms に短縮され、すぐに失敗し、劣化したロジックに従い、null が返されました。
3.3.2. 異常な割合と異常な数
異常割合または異常数:指定した期間内のコール数をカウントし、コール数が指定したリクエスト数を超え、かつ異常の割合が設定した割合のしきい値に達した場合(または指定した異常数を超えた場合)、サーキットブレーカーが作動します。引き金になった。
たとえば、通常とは異なるスケール設定:
解釈: 過去 1000 ミリ秒以内のリクエストをカウントし、リクエスト数が 10 を超え、異常率が 0.4 以上の場合、サーキット ブレーカーが作動します。
例外数の設定:
解釈: 最後の 1000 ミリ秒以内のリクエストをカウントします。リクエストの数が 10 を超え、例外の割合が 2 以上の場合、サーキット ブレーカーがトリガーされます。
ケース
要件: UserClient のクエリ ユーザー インターフェイスのダウングレード ルールを設定します。統計時間は 1 秒、リクエストの最小数は 5、失敗しきい値比率は 0.4、ヒューズ期間は 5 秒です。
3.3.2.1. 例外リクエストの設定
まず、user-service のインターフェイス /user/{id} のビジネスを変更します。手動で例外をスローして、異常な比率のヒューズをトリガーします。
つまり、ID が 2 の場合、例外がトリガーされます。
3.3.2.2. サーキットブレーカールールの設定
次に、偽インターフェイスのダウングレード ルールを設定します。
ルール:
5 つのリクエストで、例外比率が 0.4 を超える限り、つまり 2 つ以上の例外がある限り、サーキット ブレーカーがトリガーされます。
3.3.2.3. テスト
ブラウザで素早くアクセス: http://localhost:8088/order/102、素早く 5 回更新し、ヒューズをトリガーします。
3.3.3. 概要
Sentinel サーキット ブレーカーのダウングレードの戦略は何ですか?
-
低速通話率:指定時間を超える通話を低速通話とし、単位時間内の低速通話の割合をカウントし、閾値を超えるとブローします
-
異常比率: 統計単位期間内の異常通話の比率。しきい値を超えるとヒューズが切断されます。
-
異常回数:単位時間内の異常通話回数をカウントし、閾値を超えるとブローします
4. 認可ルール
認可ルールは、要求元のソースを判断および制御できます。
4.1. 認可ルール
4.1.1. 基本ルール
認可ルールは呼び出し元を制御でき、ホワイト リストとブラック リストの 2 つの方法があります。
-
ホワイトリスト: 発信元がホワイトリストに含まれている発信者はアクセスを許可されます。
-
ブラックリスト: 発信元がブラックリストに含まれている発信者はアクセスを許可されません
左側のメニューの「認可」をクリックして、認可ルールを表示します。
-
リソース名: /order/{orderId} などの保護されたリソースです。
-
フロー制御アプリケーション: ソースのリストです。
- ホワイト リストがチェックされている場合、リスト内のソースへのアクセスが許可されます。
- ブラックリストがチェックされている場合、リスト内のソースへのアクセスは禁止されます。
例:
ゲートウェイから order-service へのリクエストは許可しますが、ブラウザーが order-service にアクセスすることは許可しません。その場合、ゲートウェイのソース名 (オリジン) をホワイト リストに入力する必要があります。
4.1.2. 原点の取得方法
Sentinel は、RequestOriginParser インターフェイスの parseOrigin を通じてリクエストのソースを取得します。
public interface RequestOriginParser {
/**
* 从请求request对象中获取origin,获取方式自定义
*/
String parseOrigin(HttpServletRequest request);
}
このメソッドの機能は、リクエスト オブジェクトからリクエスターの元の値を取得し、それを返すことです。
デフォルトでは、リクエスタの送信元に関係なく、sentinel は常にデフォルト値を返します。これは、すべてのリクエストのソースが同じデフォルト値であるとみなされることを意味します。
したがって、異なるリクエストが異なるオリジンを返せるように、このインターフェースの実装をカスタマイズする必要があります。
たとえば、order-service サービスでは、RequestOriginParser の実装クラスを定義します。
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 1.获取请求头
String origin = request.getHeader("origin");
// 2.非空判断
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}
リクエストヘッダーから元の値を取得しようとします。
4.1.3. ゲートウェイにリクエストヘッダーを追加する
リクエストの送信元を取得する方法は、リクエスト ヘッダーから送信元の値を取得することなので、ゲートウェイからマイクロサービスにルーティングされるすべてのリクエストに送信元ヘッダーを持たせる必要があります。
これは、以前に学習した GatewayFilter、AddRequestHeaderGatewayFilter を使用して実現する必要があります。
ゲートウェイ サービスの application.yml を変更し、defaultFilter を追加します。
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,gateway
routes:
# ...略
このようにして、ゲートウェイからルーティングされるすべてのリクエストには、値ゲートウェイを含むオリジン ヘッダーが含まれます。他の場所からマイクロサービスに到着するリクエストには、このヘッダーがありません。
4.1.4. 認可ルールの設定
次に、元の値がゲートウェイであるリクエストを許可する認可ルールを追加します。
構成は次のとおりです。
ここで、ゲートウェイを直接スキップして、order-service サービスにアクセスします。
ゲートウェイ経由でアクセスします。
4.2. カスタムの異常な結果
デフォルトでは、電流制限、ダウングレード、または認可インターセプトが発生すると、呼び出し元に例外がスローされます。異常な結果は流量制限(電流制限)です。これは十分に友好的ではなく、電流制限なのか、ダウングレードなのか、それとも許可された傍受なのかを知ることは不可能です。
4.2.1、例外の種類
また、例外が発生したときに返される結果をカスタマイズしたい場合は、BlockExceptionHandler インターフェイスを実装する必要があります。
public interface BlockExceptionHandler {
/**
* 处理请求被限流、降级、授权拦截时抛出的异常:BlockException
*/
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}
このメソッドには 3 つのパラメータがあります。
- HttpServletRequest リクエスト:リクエスト对象
- HttpServletResponse 応答:応答オブジェクト
- BlockException e: センチネルによってインターセプトされたときにスローされる例外
ここの BlockException には、いくつかの異なるサブクラスが含まれています。
異常な | 説明する |
---|---|
フロー例外 | 電流制限例外 |
ParamFlowException | 異常なホットスポットパラメータの電流制限 |
劣化例外 | ダウングレード例外 |
権限例外 | 認可ルールの例外 |
システムブロック例外 | システムルール例外 |
4.2.2、カスタム例外処理
次に、order-service でカスタム例外処理クラスを定義します。
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;
if (e instanceof FlowException) {
msg = "请求被限流了";
} else if (e instanceof ParamFlowException) {
msg = "请求被热点参数限流";
} else if (e instanceof DegradeException) {
msg = "请求被降级了";
} else if (e instanceof AuthorityException) {
msg = "没有权限访问";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}
テストを再開すると、さまざまなシナリオで、さまざまな例外メッセージが返されます。
制限:
傍受を許可する場合:
5. ルールの永続性
現在、センチネルのすべてのルールはメモリに保存されており、再起動するとすべてのルールが失われます。運用環境では、損失を避けるためにこれらのルールの永続性を確保する必要があります。
5.1、ルール管理モード
ルールを永続化できるかどうかは、ルール管理モードによって異なります。Sentinel は、次の 3 つのルール管理モードをサポートしています。
- オリジナル モード: Sentinel のデフォルト モード。ルールはメモリに保存され、サービスを再起動するとサービスは失われます。
- プルモード
- プッシュモード
5.1.1、プルモード
プル モード: コンソールは構成されたルールを Sentinel クライアントにプッシュし、クライアントは構成されたルールをローカル ファイルまたはデータベースに保存します。将来的には、ローカル ファイルまたはデータベースに定期的にクエリを実行して、ローカル ルールを更新する予定です。
5.1.2、プッシュモード
プッシュ モード: コンソールは、Nacos などのリモート構成センターに構成ルールをプッシュします。Sentinel クライアントは Nacos を監視し、設定変更のプッシュ メッセージを取得し、ローカル設定の更新を完了します。
5.2. プッシュモードの実現
5.2.1. order-service サービスの変更
Nacos のセンチネル ルール設定をリッスンするように OrderService を変更します。
具体的な手順は次のとおりです。
5.2.1.1. 依存関係の導入
order-service で nacos の依存関係を監視するためにセンチネルを導入します。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
5.2.1.2. nacos アドレスの設定
nacos アドレスを構成し、order-service の application.yml ファイルで構成情報をモニターします。
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: localhost:8848 # nacos地址
dataId: orderservice-flow-rules
groupId: SENTINEL_GROUP
rule-type: flow # 还可以是:degrade、authority、param-flow
5.2.2. センチネルダッシュボードのソースコードを変更する
SentinelDashboard はデフォルトでは nacos 永続性をサポートしていないため、ソース コードを変更する必要があります。
5.2.2.1、センチネルソースパッケージを解凍します。
5.2.2.2. nacos 依存関係を変更する
Sentinel-dashboard ソース コードの pom ファイルでは、nacos はテストのデフォルト スコープに依存しており、テスト中にのみ使用できます。ここでは削除する必要があります。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<scope>test</scope>
</dependency>
Sentinel-datasource-nacos が依存するスコープを削除します。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
5.2.2.3、nacos サポートを追加
Sentinel-dashboard のテスト パッケージには nacos のサポートが記述されているので、これを main にコピーする必要があります。
5.2.2.4. nacosアドレスの変更
次に、テスト コード内の NacosConfig クラスも変更する必要があります。次に、
その中の nacos アドレスを変更して、application.properties 内の構成を読み取るようにします。
nacos アドレス設定を Sentinel-dashboard の application.properties に追加します。
nacos.addr=localhost:8848
5.2.2.5. nacos データソースの設定
さらに、com.alibaba.csp.sentinel.dashboard.controller.v2 パッケージの下の FlowControllerV2 クラスを変更する必要もあります。
追加した Nacos データ ソースを有効にします。
5.2.2.6、フロントエンドページを変更する
次に、フロントエンド ページを変更し、nacos をサポートするメニューを追加します。
src/main/webapp/resources/app/scripts/directives/sidebar/ ディレクトリにあるsidebar.html ファイルを変更し、コメントのこの部分を開き、その中のテキストを変更します
。
5.2.2.7、プロジェクトを再コンパイルしてパッケージ化する
IDEA で Maven プラグインを実行し、変更した Sentinel-Dashboard をコンパイルしてパッケージ化します。
5.2.2.8、開始
起動方法は公式と同じです。
java -jar sentinel-dashboard.jar
nacos アドレスを変更する場合は、パラメーターを追加する必要があります。
java -jar -Dnacos.addr=localhost:8848 sentinel-dashboard.jar
6. 理論的根拠
6.1. 分散トランザクションの問題
6.1.1. 現地事情
ローカル トランザクション、つまり従来のスタンドアロン トランザクション。従来のデータベース トランザクションでは、次の 4 つの原則を満たす必要があります。
6.1.2. 分散トランザクション
分散トランザクションとは、次のような単一のサービスまたは単一のデータベース アーキテクチャの下で生成されないトランザクションを指します。
- データソース間の分散トランザクション
- サービス間での分散トランザクション
- 概況
データベースの水平分割とサービスの垂直分割の後、通常、ビジネス オペレーションを完了するには複数のデータベースとサービスにまたがる必要があります。たとえば、電子商取引業界における注文支払いのより一般的なケースには、次のような動作が含まれます。
- 新しい注文を作成する
- 商品在庫の控除
- ユーザーアカウント残高から金額を差し引く
上記を実行するには、3 つの異なるマイクロサービスと 3 つの異なるデータベースにアクセスする必要があります。
注文の作成、在庫の差し引き、およびアカウントの差し引きは、各サービスとデータベース内のローカル トランザクションであり、ACID 原則を保証できます。
しかし、これら3つを「ビジネス」として捉えた場合、「ビジネス」の原子性を満たすためには、すべての操作が成功するかすべて失敗するかのどちらかであり、部分的に成功、部分的に失敗するという現象は許されない、これが分散システムです。ビジネス。
現時点ではACIDを満たすことが難しく、分散トランザクションで解決すべき課題となっている
6.1.3. 分散トランザクションの問題をデモンストレーションする
分散トランザクションの問題を示すためにケースを使用します。
1) Seata_demo という名前のデータベースを作成し、SQL ファイルをインポートします。
2) 新しいマイクロサービスを作成します。
の:
Seata-demo: 親プロジェクト。プロジェクトの依存関係の管理を担当します。
- account-service: アカウント サービス。ユーザーの資金口座の管理を担当します。残高を差し引くインターフェースを提供する
- storage-service: 商品在庫の管理を担当する在庫サービス。在庫を差し引くためのインターフェースを提供する
- order-service: 注文サービス。注文の管理を担当します。注文を作成するときは、account-service と storage-service を呼び出す必要があります。
3) nacos とすべてのマイクロサービスを開始します
4) 注文関数をテストし、Post リクエストを送信します。
リクエストは次のとおりです。
curl --location --request POST 'http://localhost:8082/order?userId=user202103032042012&commodityCode=100202003032041&count=20&money=200'
テストの結果、在庫が不足している場合、残高が差し引かれているとロールバックされず、分散トランザクションの問題が発生することがわかりました。
6.2、CAP定理
1998 年、カリフォルニア大学のコンピューター科学者エリック ブリュワーは、分散システムには 3 つの指標があると提案しました。
- 一貫性
- 可用性
- パーティショントレランス(パーティションフォールトトレランス)
それらの最初の文字は C、A、P です。
エリック・ブリューワー氏は、これら3つの指標を同時に達成することはできないと述べた。この結論は CAP 定理と呼ばれます。
6.2.1. 一貫性
一貫性: ユーザーが分散システム内のノードにアクセスする場合、取得されるデータは一貫していなければなりません。
たとえば、現在は 2 つのノードが含まれており、初期データは一貫しています。
いずれかのノードのデータを変更すると、2 つのノードのデータに違いが生じます。
一貫性を維持するには、node01 から node02 へのデータ同期を達成する必要があります。
6.2.2. 可用性
可用性 (可用性): クラスター内の正常なノードにアクセスするユーザーは、タイムアウトや拒否ではなく応答を取得できる必要があります。
図に示すように、3 つのノードを持つクラスターがあり、応答を取得するのに間に合うようにそのうちの 1 つにアクセスできます。
ネットワーク障害またはその他の理由により一部のノードにアクセスできない場合は、そのノードが利用できないことを意味します。
6.2.3、パーティションのフォールトトレランス
パーティション: ネットワーク障害またはその他の理由により、分散システム内の一部のノードが他のノードとの接続を失い、独立したパーティションを形成します。
トレランス (フォールトトレランス) : クラスタ内でパーティションが発生した場合、システム全体が外部サービスを提供し続ける必要があります。
6.2.4. 矛盾
分散システムでは、システム間のネットワークが 100% 健全であることは保証できず、必ず障害が発生し、サービスを外部で保証する必要があります。したがって、パーティショントレランスは避けられません。
ノードが新しいデータ変更を受信すると、問題が発生します。
この時点で一貫性を確保したい場合は、ネットワークが回復するまで待つ必要があります。データの同期が完了すると、クラスタ全体が外部にサービスを提供します。サービスはブロックされ、使用できなくなります。
現時点で可用性を確保したい場合は、ネットワークが回復するまで待つことはできません。そうすると、node01、node02、node03 の間でデータの不整合が発生します。
つまり、P が出現しなければならない場合、A と C のうちの 1 つしか実現できません。
6.2.5. まとめ
CAP定理の内容を簡単に説明してください。
- 分散システムのノードはネットワーク経由で接続されているため、パーティションの問題が発生するはずです (P)
- パーティションが発生すると、システムの整合性(C)と可用性(A)を同時に満たすことができなくなります
考察: elasticsearch クラスターは CP ですか、それとも AP ですか?
- ES クラスターが分割されると、障害のあるノードがクラスターから削除され、データの一貫性を確保するためにデータ フラグメントが他のノードに再分散されます。したがって、可用性が低く、一貫性が高く、CP に属します。
6.3、BASE理論
BASE 理論は CAP に対する解決策であり、次の 3 つのアイデアが含まれています。
- 基本的に利用可能 (基本的に利用可能) : 分散システムに障害が発生した場合、可用性の一部を失うことが許可されます。つまり、コアが利用可能であることを保証します。
- **ソフトステート (ソフトステート): **一定期間においては、一時的な不整合状態などの中間状態が許可されます。
- 最終的に整合性: 強い整合性は保証できませんが、ソフト状態が終了した後、データの整合性は最終的に達成されます。
分散トランザクションの最大の問題は、各サブトランザクションの一貫性であるため、CAP 定理と BASE 理論から学ぶことができます。
-
AP モード: 各サブトランザクションは個別に実行および送信されるため、結果の不一致が許容され、最終的な整合性を達成するためにデータを復元するための修正措置を講じます。
-
CP モード: 各サブトランザクションは、実行後に互いに待機し、同時にコミットし、同時にロールバックして、強力なコンセンサスに達します。ただし、トランザクションの待機プロセス中は、弱い可用性の状態になります。
6.4. 分散トランザクションを解決するためのアイデア
分散トランザクションを解決するには、各サブシステムが互いのトランザクション状態を認識し、一貫した状態を保つ必要があるため、各トランザクション参加者(サブシステムトランザクション)を調整するトランザクションコーディネータが必要です。
ここでのサブシステム トランザクションはブランチ トランザクションと呼ばれ、関連するブランチ トランザクションはまとめてグローバル トランザクションと呼ばれます。
ただし、どのモードであっても、サブシステムのトランザクション間で相互に通信し、トランザクションのステータスを調整する必要があります。つまり、トランザクション コーディネーター(TC)が必要です。
要約:
BASE 理論の 3 つの考え方を簡単に説明します。
- 基本的に利用可能
- 柔らかい状態
- 最終的には一貫性のある
分散トランザクションを解決するためのアイデアとモデル:
- グローバルトランザクション: 分散トランザクション全体
- ブランチトランザクション:分散トランザクションに含まれる各サブシステムのトランザクション
- 最終的な整合性の考え方: 各ブランチ トランザクションは個別に実行および送信され、不整合がある場合はデータを復元する方法を見つけます。
- 強力な一貫性のアイデア: 各ブランチ トランザクションの実行後にビジネスを送信せず、お互いの結果を待ちます。その後、一律にコミットまたはロールバックします
7. シータと初めて知る
Seata は、2019 年 1 月に Ant Financial と Alibaba が共同でオープンソース化した分散トランザクション ソリューションです。高性能で使いやすい分散トランザクション サービスを提供することに尽力し、ユーザーにワンストップの分散ソリューションを構築します。
公式 Web サイトのアドレス: http://seata.io/。ドキュメントとポッドキャストで多くの使用方法とソース コード分析が提供されます。
7.1、Seata のアーキテクチャ
Seata トランザクション管理には 3 つの重要な役割があります。
-
TC (トランザクション コーディネーター) - **トランザクション コーディネーター: **グローバル トランザクションとブランチ トランザクションのステータスを維持し、グローバル トランザクションのコミットまたはロールバックを調整します。
-
TM (トランザクション マネージャー) - **トランザクション マネージャー:** グローバル トランザクションの範囲の定義、グローバル トランザクションの開始、グローバル トランザクションのコミットまたはロールバックを行います。
-
RM (リソース マネージャー) - **リソース マネージャー:** は、ブランチ トランザクションのリソースを管理し、TC と通信してブランチ トランザクションを登録し、ブランチ トランザクションのステータスを報告し、ブランチ トランザクションをコミットまたはロールバックします。
全体的な構造を図に示します。
Seata は、上記のアーキテクチャに基づいて 4 つの異なる分散トランザクション ソリューションを提供します。
- XA モード: 強力な一貫性を備えた段階的トランザクション モード。一定の可用性は犠牲になりますが、ビジネスの侵入はありません。
- TCC モード: ビジネス侵入を伴う、結果的に整合性のある段階的トランザクション モード
- AT モード: 結果的に整合性のある段階的トランザクション モード、ビジネス侵入なし、および Seata のデフォルト モード
- SAGA モード: ビジネス侵入を伴うロング トランザクション モード
どのようなソリューションであっても、取引のコーディネーターであるTCとは切っても切れない関係にあります。
7.2. TC サービスのデプロイ
7.2.1. Seata の TC サーバーのデプロイ
7.2.1.1. ダウンロード
まず最初に、seata-server パッケージをダウンロードする必要があります。アドレスはhttps://seata.io/zh-cn/blog/download.htmlです。
7.2.1.2. 解凍
zip パッケージを中国語以外のディレクトリに解凍します。ディレクトリ構造は次のとおりです。
7.2.1.3. 設定の変更
conf ディレクトリ内の registry.conf ファイルを変更します。
registry {
# tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
type = "nacos"
nacos {
# seata tc 服务注册到 nacos的服务名称,可以自定义
application = "seata-tc-server"
serverAddr = "127.0.0.1:8848"
group = "DEFAULT_GROUP"
namespace = ""
cluster = "SH"
username = "nacos"
password = "nacos"
}
}
config {
# 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
type = "nacos"
# 配置nacos地址等信息
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
7.2.1.4、nacos に設定を追加
特に注意してください。TC サービスのクラスターが構成を共有できるようにするために、統合構成センターとして nacos を選択しました。したがって、サーバー構成ファイル SeataServer.properties を nacos で構成する必要があります。
形式は次のとおりです。
設定内容は以下の通りです。
# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
7.2.1.5. データベーステーブルの作成
特別な注意: tc サービスが分散トランザクションを管理する場合、トランザクション関連のデータをデータベースに記録する必要があり、これらのテーブルを事前に作成する必要があります。
Seata という名前の新しいデータベースを作成します
これらのテーブルは主に、グローバル トランザクション、ブランチ トランザクション、およびグローバル ロック情報を記録します。
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- 分支事务表
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` tinyint(4) NULL DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime(6) NULL DEFAULT NULL,
`gmt_modified` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- 全局事务表
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`timeout` int(11) NULL DEFAULT NULL,
`begin_time` bigint(20) NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
7.2.1.6. TC サービスの開始
bin ディレクトリに入り、その中で Seata-server.bat を実行します。
起動が成功すると、seata-server が nacos 登録センターに登録されているはずです。
ブラウザを開いて nacos アドレス http://localhost:8848 にアクセスし、サービス リスト ページに入ると、seata-tc-server の情報を確認できます。
7.3. Seata とのマイクロサービス統合
7.3.1. 依存関係の導入
まず、order-service に依存関係を導入します。
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本较低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<!--seata starter 采用1.4.2版本-->
<version>${seata.version}</version>
</dependency>
7.3.2. TC アドレスの設定
order-service の application.yml で、TC サービス情報を構成し、登録センター nacos を通じてサービス名と組み合わせた TC アドレスを取得します。
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
namespace: "" # namespace,默认为空
group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
application: seata-tc-server # seata服务名称
username: nacos
password: nacos
tx-service-group: seata-demo # 事务组名称
service:
vgroup-mapping: # 事务组与cluster的映射关系
seata-demo: SH
マイクロサービスはこれらの構成に基づいて TC のアドレスをどのように見つけますか?
Nacos に登録されたマイクロサービスには、特定のインスタンスを決定するために 4 つの情報が必要であることがわかっています。
- 名前空間: 名前空間
- グループ: グループ化
- アプリケーション: サービス名
- クラスター: クラスター名
上記の 4 つの情報は、yaml ファイルで確認できます。
名前空間は空です。これはデフォルトのパブリックです。
これらを組み合わせると、TC サービス情報は public@DEFAULT_GROUP@seata-tc-server@SH となり、TC サービス クラスターを決定できます。次に、Nacos に移動して、対応するインスタンス情報を取得します。
8、実践練習
8.1、XAモード
XA 仕様は、X/Open 組織によって定義された分散トランザクション処理 (DTP、分散トランザクション処理) 標準です。XA 仕様は、グローバル TM とローカル RM 間のインターフェイスについて説明しています。ほとんどすべての主流データベースが XA 仕様をサポートしています。
8.1.1. 2 フェーズコミット
XA は仕様であり、現在主流のデータベースはこの仕様を実装しており、実装の原則は 2 フェーズ コミットに基づいています。
通常の状況:
異常事態:
フェーズ 1:
- トランザクション コーディネーターは、各トランザクション参加者にローカル トランザクションを実行するように通知します。
- ローカルトランザクションの実行完了後、トランザクションコーディネーターにトランザクションの実行状況を報告しますが、このときトランザクションはコミットされず、データベースロックを保持し続けます。
フェーズ 2:
- トランザクションコーディネーターは第一段階のレポートに基づいて次のステップを判断します
- すべてのフェーズが成功した場合、すべてのトランザクション参加者に通知し、トランザクションをコミットします。
- フェーズのいずれかの参加者が失敗した場合は、すべてのトランザクション参加者にトランザクションをロールバックするように通知します。
8.1.2 Seata の XA モデル
Seata は、元の XA モードをカプセル化して変換し、独自のトランザクション モデルに適応させるだけです。基本的なアーキテクチャを図に示します。
RM の最初の段階の作業:
①支店取引をTCに登録する
②ブランチ業務SQLを実行するがサブミットしない
③実行状況をTCに報告
TC の第 2 フェーズの作業:
-
TCは各ブランチのトランザクション実行状況を検出
a. すべて成功した場合は、すべての RM にトランザクションをコミットするように通知します。
b. 障害が発生した場合は、すべての RM にトランザクションをロールバックするように通知します。
RM の第 2 フェーズの作業:
- TC 命令の受信、トランザクションのコミットまたはロールバック
8.1.3 利点と欠点
XA モードの利点は何ですか?
- トランザクションの強力な一貫性は ACID 原則を満たします。
- 一般的に使用されるデータベースがサポートされており、実装が簡単で、コードの侵入がありません。
XA モードの欠点は何ですか?
- データベース リソースは第 1 段階でロックされ、第 2 段階の終了後にのみ解放される必要があるため、パフォーマンスが低下します。
- リレーショナル データベースに依存してトランザクションを実現する
8.1.4. XAモードの実現
Seata のスターターは XA モードの自動組み立てを完了しており、実装は非常に簡単です。
1) application.yml ファイル (トランザクションに参加している各マイクロサービス) を変更し、XA モードを有効にします。
seata:
data-source-proxy-mode: XA
2) @GlobalTransactional アノテーションをグローバル トランザクションを開始するエントリ メソッドに追加します。
この場合、それは OrderServiceImpl の create メソッドです。
3) サービスを再起動してテストします
order-service を再起動して再度テストすると、何があっても 3 つのマイクロサービスが正常にロールバックできることがわかります。
8.2、ATモード
AT モードも段階的コミット トランザクション モデルですが、XA モデルの長いリソース ロック期間を補います。
8.2.1、シータのATモデル
基本的なフローチャート:
フェーズ 1 RM 作業:
- ブランチトランザクションを登録する
- アンドゥログの記録(データスナップショット)
- ビジネスSQLを実行して送信する
- 取引ステータスを報告する
フェーズ 2 提出時の RM の作業:
- アンドゥログを削除するだけです
フェーズ 2 のロールバック中の RM の作業:
- アンドゥログに従ってアップデート前のデータに戻す
8.2.2. プロセスのレビュー
ATモードの原理を実際のビジネスで整理してみましょう。
たとえば、ユーザー残高を記録する別のデータベース テーブルが存在します。
ID | お金 |
---|---|
1 | 100 |
ブランチ サービスの 1 つによって実行される SQL は次のとおりです。
update tb_account set money = money - 10 where id = 1
AT モードでは、現在のブランチ トランザクションの実行フローは次のとおりです。
フェーズ 1:
1) TM がグローバル トランザクションを開始し、TC に登録します。
2) TM はブランチ トランザクションを呼び出します
3) ブランチトランザクションはビジネス SQL を実行する準備ができています
4) RM はビジネス SQL をインターセプトし、where 条件に従って元のデータをクエリし、スナップショットを形成します。
{
"id": 1, "money": 100
}
5) RM はビジネス SQL を実行し、ローカル トランザクションを送信し、データベース ロックを解放します。現時点ではmoney = 90
6) RM はローカルトランザクションステータスを TC に報告します
フェーズ 2:
1) TM はトランザクションが終了したことを TC に通知します
2) TC は支店取引ステータスを確認します
a) すべて成功した場合は、スナップショットをすぐに削除します
b) ブランチ トランザクションが失敗した場合は、ロールバックする必要があります。スナップショット データを読み取り ( {"id": 1, "money": 100}
)、スナップショットをデータベースに復元します。この時点で、データベースは再び 100 に復元されます。
フローチャート:
8.2.3 ATとXAの違い
AT モードと XA モードの最大の違いを簡単に説明しますか?
- XA モードは最初の段階でトランザクションをコミットせず、リソースをロックします。AT モードは、リソースをロックせずに最初の段階で直接コミットします。
- XA モードはデータベース メカニズムに依存してロールバックを実現しますが、AT モードはデータ スナップショットを使用してデータ ロールバックを実現します。
- XA モードでは強い一貫性、AT モードでは最終的な一貫性
8.2.4. ダーティライトの問題
複数のスレッドが AT モードで分散トランザクションに同時にアクセスすると、図に示すようにダーティ ライトの問題が発生する可能性があります。
解決策は、グローバル ロックの概念を導入することです。DB ロックを解放する前に、まずグローバル ロックを取得してください。現在のデータを同時に操作する別のトランザクションを避けてください。
8.2.5. 利点と欠点
ATモードのメリット:
- トランザクションの直接送信を 1 つのステージで完了し、データベース リソースを解放し、パフォーマンスを向上させます。
- グローバル ロックを使用して読み取りと書き込みの分離を実現する
- コードの侵入はなく、フレームワークが自動的にロールバックとコミットを完了します。
ATモードのデメリット:
- 2 つのフェーズはソフト状態に属し、最終的な整合性に属します。
- フレームワークのスナップショット機能はパフォーマンスに影響しますが、XA モードよりもはるかに優れています
8.2.6. ATモードの実装
AT モードでのスナップショットの生成やロールバックなどのアクションは、コードの侵入なしでフレームワークによって自動的に完了するため、実装は非常に簡単です。
ただし、AT モードでは、グローバル ロックを記録するためのテーブルと、データ スナップショット undo_log を記録するための別のテーブルが必要です。
1) データベーステーブルをインポートし、グローバルロックを記録します。
2) application.yml ファイルを変更し、トランザクション モードを AT モードに変更します。
seata:
data-source-proxy-mode: AT # 默认就是AT
3) サービスを再起動してテストします
8.3、TCCモード
TCC モードは AT モードとよく似ており、各ステージは独立したトランザクションですが、違いは、TCC が手動コーディングを通じてデータ回復を実装することです。次の 3 つのメソッドを実装する必要があります。
-
試してみてください: リソースの検出と予約。
-
確認: リソース操作ビジネスを完了します。試行が成功し、確認も成功する必要があります。
-
キャンセル: 予約されたリソースが解放されます。これは、try の逆の操作として理解できます。
8.3.1. プロセス分析
たとえば、ユーザー残高を差し引くビジネス。口座 A の元の残高を 100 とすると、残高から 30 元を差し引く必要があります。
- ステージ 1 (試行) : 残高が十分であるかどうかを確認し、十分な場合は凍結金額を 30 元増額し、利用可能な残高から 30 元を差し引きます。
初期残高:
残高は十分なので、凍結することができます。
この時点では、合計金額 = 凍結数量 + 利用可能数量となり、数量は 100 のまま変わりません。トランザクションは、他のトランザクションを待たずに直接コミットされます。
- フェーズ 2 (確認) : 提出 (確認) する場合は、凍結された金額から 30 が差し引かれます。
送信できることを確認しますが、利用可能な金額はすでに差し引かれているため、ここで凍結された金額をクリアするだけです。
この時点で、合計金額 = 凍結金額 + 利用可能金額 = 0 + 70 = 70 元
- フェーズ 2 (キャンセル) : ロールバック (キャンセル) する場合は、凍結された金額が 30 差し引かれ、利用可能な残高が 30 増加します。
ロールバックが必要な場合は、凍結された量を解放し、使用可能な量を復元する必要があります。
8.3.2 Seata の TCC モデル
Seata の TCC モデルは、次の図に示すように、以前のトランザクション アーキテクチャを引き続き継承しています。
8.3.3 利点と欠点
TCC モードの各段階では何を行うのでしょうか?
- 試してみましょう: リソースの確認と予約
- 確認:業務の実行と提出
- キャンセル: 予約されたリソースの解放
TCCの利点は何ですか?
- ダイレクト コミット トランザクションを 1 つのステージで完了し、データベース リソースを解放し、優れたパフォーマンスを実現します。
- ATモデルと比較してスナップショット生成不要、グローバルロック不要でパフォーマンス最強
- データベース トランザクションには依存しませんが、非トランザクション データベースに使用できる補償操作に依存します。
TCCのデメリットは何ですか?
- コード侵入があり、try、confirm、cancelインターフェースを手動で記述するのは面倒すぎる
- ソフト状態、トランザクションは最終的に一貫性がある
- 確認やキャンセルの失敗を考慮して冪等な処理を行う必要がある
8.3.4、トランザクションの一時停止と空のロールバック
8.3.4.1、空のロールバック
ブランチ トランザクションの try フェーズがブロックされると、グローバル トランザクションがタイムアウトになり、2 番目のフェーズのキャンセル操作がトリガーされる可能性があります。try オペレーションが実行されない場合は、先に cancel オペレーションが実行されますが、このときキャンセルはロールバックできず、空のロールバックとなります。
図に示すように:
キャンセル操作を実行する場合は、try が実行されたかどうかを判断し、実行されていない場合は空にロールバックする必要があります。
8.3.4.2. サービスの一時停止
空にロールバックされたビジネスの場合、以前にブロックされた try 操作が再開され、try が実行され続けると確認もキャンセルもできなくなり、トランザクションは常に中間状態になります。サスペンション。
try オペレーションを実行する際には、キャンセルが実行されたかどうかを判断し、キャンセルが実行された場合は、中断を回避するために空ロールバック後の try オペレーションを禁止する必要があります。
8.3.5. TCCモードの実現
空のロールバックとビジネスの一時停止の問題を解決するには、現在のトランザクションのステータスを記録する必要があります。トライ中かキャンセル中か。
8.3.5.1、思考分析
ここでテーブルを定義します。
CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) NOT NULL,
`user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
`freeze_money` int(11) unsigned DEFAULT '0' COMMENT '冻结金额',
`state` int(1) DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
の:
- xid: グローバル トランザクション ID です。
- ize_money: ユーザーの凍結金額を記録するために使用されます。
- state: トランザクションステータスを記録するために使用されます
このとき、どのように事業を始めるべきでしょうか?
- ビジネスを試してみる:
- 凍結された金額と取引ステータスを account_freeze テーブルに記録します。
- 勘定科目表で利用可能な金額を差し引く
- 用件の確認
- xidに従ってaccount_freezeテーブルの凍結されたレコードを削除します。
- 営業を中止する
- account_freeze テーブルを変更します。凍結金額は 0、状態は 2 です。
- アカウントテーブルを変更して利用可能な金額を復元します
- ロールバックが空かどうかを判断するにはどうすればよいですか?
- キャンセル ビジネスでは、xid に従って account_freeze をクエリします。null の場合は、試行がまだ行われていないことを意味し、空のロールバックが必要です。
- 営業停止を回避するには?
- try ビジネスでは、xid に従って account_freeze をクエリし、すでに存在する場合は、Cancel が実行されたことを証明し、try ビジネスの実行を拒否します。
次に、アカウント サービスを変換し、TCC を使用して残高控除機能を実装します。
8.3.5.2、TCCインターフェースの宣言
TCC の Try、confirm、および Cancel メソッドはすべて、アノテーションに基づいてインターフェイスで宣言する必要があります。
account-service プロジェクトのcn.angyan.account.service
パッケージに新しいインターフェイスを作成し、TCC の 3 つのインターフェイスを宣言します。
@LocalTCC
public interface AccountTCCService {
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money")int money);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
3) 実装クラスを書く
account-service サービスのパッケージの下に新しいクラスを作成してcn.angyan.account.service.impl
、TCC サービスを実装します。
@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
@Transactional
public void deduct(String userId, int money) {
// 0.获取事务id
String xid = RootContext.getXID();
// 1.扣减可用余额
accountMapper.deduct(userId, money);
// 2.记录冻结金额,事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freeze.setXid(xid);
freezeMapper.insert(freeze);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
// 1.获取事务id
String xid = ctx.getXid();
// 2.根据id删除冻结记录
int count = freezeMapper.deleteById(xid);
return count == 1;
}
@Override
public boolean cancel(BusinessActionContext ctx) {
// 0.查询冻结记录
String xid = ctx.getXid();
AccountFreeze freeze = freezeMapper.selectById(xid);
// 1.恢复可用余额
accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
// 2.将冻结金额清零,状态改为CANCEL
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count == 1;
}
}
8.4、SAGAモード
Saga モードは Seata の今後のオープンソースのロング トランザクション ソリューションであり、主に Ant Financial が提供する予定です。
その理論的基礎は、 1987 年に Hector & Kenneth によって出版された論文Sagasです。
佐賀の Seata 公式 Web サイトガイド: https://seata.io/zh-cn/docs/user/saga.html
8.4.1 原則
Saga モードでは、分散トランザクションに複数の参加者が存在し、各参加者はリバーサル補償サービスであり、ユーザーはビジネス シナリオに従ってフォワード操作とリバース ロールバック操作を実装する必要があります。
分散トランザクションの実行中は、各参加者の転送操作が順番に実行され、すべての転送操作が正常に実行されると、分散トランザクションはコミットされます。いずれかの順方向操作が失敗した場合、分散トランザクションは戻って前の参加者の逆ロールバック操作を実行し、送信された参加者をロールバックして、分散トランザクションを初期状態に戻します。
佐賀も 2 つのステージに分かれています。
- フェーズ 1: ローカル トランザクションを直接送信する
- フェーズ2: 成功したら何もせず、失敗したら補償ビジネスを書いてロールバックする
4.4.2. 利点と欠点
アドバンテージ:
- トランザクション参加者は、イベント駆動型の高スループットに基づいて非同期呼び出しを実装できます。
- トランザクションを 1 つのステージで直接送信、ロックなし、優れたパフォーマンス
- TCCで3つのステージを書かなくても実装は簡単
欠点:
- ソフト状態の持続期間は不確実であり、適時性は低い
- ロックなし、トランザクション分離なし、ダーティ書き込みなし
8.5. 4 つのモードの比較
4 つの実装を次の観点から比較します。
- 一貫性: トランザクションの一貫性は保証できますか? 強い整合性か、それとも結果整合性か?
- 分離: トランザクションはどの程度分離されていますか?
- コード侵入: ビジネス コードを変更する必要がありますか?
- パフォーマンス: パフォーマンスの低下はありますか?
- シナリオ: 一般的なビジネス シナリオ
図に示すように:
9、高可用性
9.1. 高可用性クラスター構造
TC サービス クラスターの構築は非常に簡単で、複数の TC サービスを開始して nacos に登録するだけです。
しかし、クラスタは 100% のセキュリティを保証することはできず、クラスタが配置されているコンピュータ室に障害が発生した場合はどうなるでしょうか。したがって、要件が高い場合は、通常、異なる場所にある複数のコンピューター室による災害復旧が行われます。
たとえば、1 つの TC クラスターが上海にあり、別の TC クラスターが杭州にあるとします。
マイクロサービスは、トランザクション グループ (tx-service-group) と TC クラスター間のマッピング関係に基づいて、どの TC クラスターを使用する必要があるかを見つけます。SH クラスターに障害が発生した場合、vgroup-mapping のマッピング関係を HZ に変更するだけで済みます。その後、すべてのマイクロサービスが HZ の TC クラスターに切り替えられます。
9.2、高可用性クラスターを実現するため
9.2.1、リモート災害復旧をシミュレートする TC クラスター
2 つの Seata TC サービス ノードを開始することが計画されています。
ノード名 | IPアドレス | ポート番号 | クラスター名 |
---|---|---|---|
設定 | 127.0.0.1 | 8091 | SH |
シータ2 | 127.0.0.1 | 8092 | ヘルツ |
以前にseataサービスを開始したことがあります。ポートは8091、クラスター名はSHです。
次に、seata ディレクトリをコピーし、seata2 という名前を付けます。
Seata2/conf/registry.conf を次のように変更します。
registry {
# tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
type = "nacos"
nacos {
# seata tc 服务注册到 nacos的服务名称,可以自定义
application = "seata-tc-server"
serverAddr = "127.0.0.1:8848"
group = "DEFAULT_GROUP"
namespace = ""
cluster = "HZ"
username = "nacos"
password = "nacos"
}
}
config {
# 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
type = "nacos"
# 配置nacos地址等信息
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
Seata2/bin ディレクトリに入り、次のコマンドを実行します。
seata-server.bat -p 8092
nacos コンソールを開いてサービス リストを表示します。
9.2.2. nacos へのトランザクショングループマッピングの設定
次に、tx-service-group とクラスター間のマッピング関係を nacos 構成センターに構成する必要があります。
新しい構成を作成します。
構成の内容は次のとおりです。
# 事务组映射关系
service.vgroupMapping.seata-demo=SH
service.enableDegrade=false
service.disableGlobalTransaction=false
# 与TC服务的通信配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
# RM配置
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
# TM配置
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
# undo日志配置
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
client.log.exceptionRate=100
9.2.3. マイクロサービスが nacos 設定を読み取る
次に、各マイクロサービスの application.yml ファイルを変更して、マイクロサービスが nacos の client.properties ファイルを読み取れるようにする必要があります。
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
group: SEATA_GROUP
data-id: client.properties
マイクロサービスを再起動します。マイクロサービスが tc の SH クラスターに接続されているか、tc の HZ クラスターに接続されているかは、nacos の client.properties によって決まります。