SpringBoot 分散トランザクションのベスト エフォート通知

SpringBoot 分散トランザクションのベスト エフォート通知

[[393657]]

環境: springboot.2.4.9 + RabbitMQ3.7.4

ベストエフォート通知とは何ですか?

これは充電のケースです

対話プロセス:

1. アカウント システムが再チャージ システム インターフェイスを呼び出します。

2. 再チャージシステムが支払いを完了すると、アカウントシステムに再チャージ結果通知が送信されます。通知が失敗した場合、再充電システムは戦略に従って通知を繰り返します。

3. アカウントシステムは再チャージ結果通知を受信し、再チャージステータスを変更します。

4. アカウント システムが通知を受信しない場合、再チャージ システムのインターフェイスを積極的に呼び出して再チャージの結果を照会します。

上記の例から、ベスト エフォート通知スキームの目標をまとめることができます。つまり、通知の発信者は、特定のメカニズムを通じてビジネス処理の結果を受信者に通知するために最善の努力をします。具体的には以下が含まれます:

1. 特定のメッセージ繰り返し通知メカニズムがあります。通知の受信者が通知を受け取っていない可能性があるため、メッセージを繰り返すための特定のメカニズムが必要です。

2. メッセージ校正メカニズム。最善の努力にもかかわらず受信者に通知されない場合、または受信者がメッセージを消費した後に再度消費する必要がある場合、受信者は要求を満たすために通知側にメッセージ情報を積極的に問い合わせることができます。

ベストエフォート通知と信頼性の高いメッセージ一貫性の違いは何ですか?

1. さまざまなソリューションのアイデア: 信頼性の高いメッセージの一貫性。通知の発信者は、メッセージが送信され、通知の受信者に届くことを確認する必要があります。メッセージの信頼性は主に通知の発信者によって保証されます。ベスト エフォート通知: 通知の発信者は、通知の受信者にビジネス処理の結果を通知するために最善の努力を払いますが、メッセージが受信されない可能性があります。この場合、通知の受信者は、ビジネス処理の結果を照会するために、イニシエーターのインターフェースを積極的に呼び出す必要があります。通知の信頼性は通知の受信者によって異なります。

2. 両者のビジネスアプリケーションシナリオは異なります。信頼性の高いメッセージの一貫性は、トランザクション プロセスのトランザクション一貫性に重点を置き、非同期方式でトランザクションを完了します。ベスト エフォート通知は、トランザクション後の通知、つまりトランザクション結果を確実に通知することに重点を置いています。

3. さまざまな技術的ソリューション 信頼性の高いメッセージの一貫性を実現するには、送信から受信までのメッセージの一貫性、つまりメッセージの送受信を解決する必要があります。ベストエフォート通知では、送信から受信までのメッセージの一貫性を保証することはできませんが、メッセージ受信の信頼性メカニズムのみを提供します。信頼できるメカニズムは、メッセージの受信者に通知するために最大限の努力をすることです。受信者がメッセージを受信できない場合、受信者は消費について積極的に問い合わせます。

RabbitMQ によるベストエフォート通知

RabbitMQ に関する関連記事: 「SpringBoot RabbitMQ メッセージの信頼性の高い送受信」、「RabbitMQ メッセージ確認メカニズムの確認」。

プロジェクト構造

2 つのサブモジュール: users-mananger (アカウント モジュール)、pay-manager (支払いモジュール)

頼る

  1. <依存関係>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-jpa</artifactId>
  4. </依存関係>
  5. <依存関係>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. </依存関係>
  9. <依存関係>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-amqp</artifactId>
  12. </依存関係>
  13. <依存関係>
  14. <グループID>mysql</グループID>
  15. <artifactId>mysql-コネクタ-java</artifactId>
  16. <scope>ランタイム</scope>
  17. </依存関係>

サブモジュール pay-manager

設定ファイル

  1. サーバ:
  2. ポート: 8080
  3. ---  
  4. 春:
  5. ウサギさん:
  6. ホスト: ローカルホスト
  7. ポート: 5672
  8. ユーザー名: ゲスト
  9. パスワード: ゲスト
  10. 仮想ホスト: /
  11. 発行者確認タイプ: 相関
  12. 発行者戻り値: true  
  13. リスナー:
  14. 単純:
  15. 同時実行数: 5
  16. 最大同時実行数: 10
  17. プリフェッチ: 5
  18. 承認モード: 手動
  19. リトライ:
  20. 有効: true  
  21. 初期間隔: 3000
  22. 最大試行回数: 3
  23. デフォルトの再キュー拒否: false  

エンティティクラス

チャージ金額とアカウント情報を記録する

  1. @実在物
  2. @テーブル(名前= "t_pay_info" )
  3. パブリッククラス PayInfo はSerializable を実装します{
  4. @ID
  5. プライベートな Long ID;
  6. プライベート BigDecimal マネー;
  7. プライベート Long accountId ;
  8. }

DAOとサービス

  1. パブリックインターフェース PayInfoRepository は JpaRepository<PayInfo, Long> を拡張します {
  2. PayInfo findByOrderId(文字列 orderId);
  3. }
  1. @サービス
  2. パブリッククラス PayInfoService {
  3.      
  4. @リソース
  5. プライベート PayInfoRepository payInfoRepository ;
  6. @リソース
  7. プライベート RabbitTemplate rabbitTemplate ;
  8.      
  9. // データが保存された後にメッセージを送信します(メッセージは確認モードまたはトランザクションモードで送信できます)
  10. @トランザクション
  11. パブリックPayInfo savePayInfo(PayInfo payInfo) {
  12. payInfo.setId(System.currentTimeMillis());
  13. PayInfo 結果 = payInfoRepository.save(payInfo);
  14. 相関データ correlationData = new CorrelationData(UUID.randomUUID().toString().replaceAll( "-" , "" ));
  15. 試す {
  16. rabbitTemplate.convertAndSend( "pay-exchange" "pay.#" 、新しいObjectMapper().writeValueAsString(payInfo)、相関データ);
  17. } キャッチ (AmqpException | JsonProcessingException e) {
  18. e.printStackTrace();
  19. }
  20. 結果を返します
  21. }
  22.      
  23. パブリックPayInfo queryByOrderId(String orderId) {
  24. payInfoRepository.findByOrderId(orderId)を返します
  25. }
  26.      
  27. }

お支払いが完了したらメッセージを送信してください。

コントローラーインターフェース

  1. @レストコントローラ
  2. @RequestMapping( "/payInfos" )
  3. パブリッククラス PayInfoController {
  4. @リソース
  5. プライベート PayInfoService payInfoService ;
  6.      
  7. // 支払いインターフェース
  8. @PostMapping( "/pay" )
  9. パブリックオブジェクト支払い(@RequestBody PayInfo payInfo) {
  10. payInfoService.savePayInfo(payInfo);
  11. 戻る  「支払いが送信されました。結果を待っています」 ;
  12. }
  13.      
  14. @GetMapping( "/queryPay" )
  15. パブリックオブジェクトクエリペイ(文字列オーダーID) {
  16. payInfoService.queryByOrderId(orderId)を返します
  17. }
  18.      
  19. }

サブモジュール users-manager

アプリケーション構成

  1. サーバ:
  2. ポート: 8081
  3. ---  
  4. 春:
  5. ウサギさん:
  6. ホスト: ローカルホスト
  7. ポート: 5672
  8. ユーザー名: ゲスト
  9. パスワード: ゲスト
  10. 仮想ホスト: /
  11. 発行者確認タイプ: 相関
  12. 発行者戻り値: true  
  13. リスナー:
  14. 単純:
  15. 同時実行数: 5
  16. 最大同時実行数: 10
  17. プリフェッチ: 5
  18. 承認モード: 手動
  19. リトライ:
  20. 有効: true  
  21. 初期間隔: 3000
  22. 最大試行回数: 3
  23. デフォルトの再キュー拒否: false  

エンティティクラス

  1. @実在物
  2. @テーブル(名前= "t_users" )
  3. パブリッククラスUsers{
  4. @ID
  5. プライベートな Long ID;
  6. プライベート文字列;
  7. プライベート BigDecimal マネー;
  8. }

アカウント情報フォーム

  1. @実在物
  2. @テーブル(名前= "t_users_log" )
  3. パブリッククラスUsersLog{
  4. @ID
  5. プライベートな Long ID;
  6. プライベート文字列 orderId ;
  7. // 0: 支払い中、1: 支払い済み、2: キャンセル済み
  8. @(columnDefinition = "int default 0" )
  9. プライベート整数ステータス = 0 ;
  10. プライベート BigDecimal マネー;
  11. プライベートDate createTime ;
  12. }

アカウント再チャージ記録テーブル(重複排除)

DAOとサービス

  1. パブリックインターフェースUsersRepositoryはJpaRepository<Users, Long>を拡張します。
  2. }
  3. パブリックインターフェースUsersLogRepositoryはJpaRepository<UsersLog, Long>を拡張します。
  4. ユーザーログ findByOrderId(文字列 orderId);
  5. }

サービスクラス

  1. @サービス
  2. パブリッククラスUsersService{
  3. @リソース
  4. プライベートUsersRepository usersRepository;
  5. @リソース
  6. プライベートUsersLogRepository usersLogRepository;
  7.      
  8. @トランザクション
  9. パブリックブール型 updateMoneyAndLogStatus(Long id, String orderId) {
  10. ユーザーログ usersLog = usersLogRepository.findByOrderId(orderId);
  11. (usersLog != null && 1 == usersLog.getStatus()) の場合 {
  12. 新しい RuntimeException( "paid" ) をスローします。
  13. }
  14. ユーザー users = usersRepository.findById(id).orElse( null );
  15. ユーザー == null場合
  16. 新しい RuntimeException( "アカウントが存在しません" ) をスローします。
  17. }
  18. users.setMoney(users.getMoney(). add (usersLog.getMoney()));
  19. usersRepository.save(ユーザー);
  20. ユーザーログのステータスを設定します(1);
  21. usersLog リポジトリを保存します。
  22. 戻る 真実;
  23. }
  24.      
  25. @トランザクション
  26. パブリックブール値 saveLog(UsersLog usersLog) {
  27. ユーザーログにIdを設定します。
  28. usersLog リポジトリを保存します。
  29. 戻る 真実;
  30. }
  31. }

メッセージ監視

  1. @成分
  2. パブリッククラス PayMessageListener {
  3.      
  4. プライベート静的最終ロガー logger = LoggerFactory.getLogger(PayMessageListener.class);
  5.      
  6. @リソース
  7. プライベートUsersService usersService;
  8.      
  9. @SuppressWarnings( "チェックなし" )
  10. @RabbitListener(キュー = { "ペイキュー" })
  11. @RabbitHandler
  12. パブリックvoid 受信(メッセージ メッセージ、チャネル チャネル) {
  13. 長い配信タグ = message.getMessageProperties().getDeliveryTag();
  14. バイト[] buf = null ;
  15. 試す {
  16. buf = message.getBody();
  17. logger.info( "受信したメッセージ: {}" , new String(buf, "UTF-8" )) ;
  18. Map<String, Object> 結果 = new JsonMapper().readValue(buf, Map.class);
  19. Long id = (( Integer ) result.get( "accountId" )) + 0L ;
  20. 文字列 orderId = (文字列) result.get( "orderId" ) ;
  21. usersService.updateMoneyAndLogStatus(id、orderId);
  22. チャネル.basicAck(配信タグ、 true );
  23. } キャッチ (例外 e) {
  24. logger.error( "メッセージ受信中に例外が発生しました: {}、例外メッセージ: {}" 、 e.getMessage()、 new String(buf、 Charset.forName( "UTF-8" ))) ;
  25. e.printStackTrace();
  26. 試す {
  27. // このような異常なメッセージは、手動で調査するためにデッドレターキューに入れる必要があります。
  28. チャネル.basicReject(配信タグ、 false );
  29. } キャッチ (IOException e1) {
  30. logger.error( "メッセージ再エントリキュー例外を拒否: {}" , e1.getMessage());
  31. e1.printStackTrace();
  32. }
  33. }
  34. }
  35. }

コントローラーインターフェース

  1. @レストコントローラ
  2. @RequestMapping( "/users" )
  3. パブリッククラスUsersController{
  4.      
  5. @リソース
  6. プライベート RestTemplate 残りのテンプレート ;
  7. @リソース
  8. プライベートUsersService usersService;
  9.      
  10. @PostMapping( "/pay" )
  11. パブリックオブジェクトpay(Long id, BigDecimal money)は例外をスローします{
  12. HttpHeaders ヘッダー = new HttpHeaders();
  13. headers.setContentType(MediaType.APPLICATION_JSON);
  14. 文字列 orderId = UUID.randomUUID().toString().replaceAll( "-" , "" );
  15. Map<String, String> パラメータ = new HashMap<>();
  16. params.put( "accountId" , String.valueOf(id));
  17. params.put( "orderId" 、orderId);
  18. params.put( "お金" 、money.toString());
  19.          
  20. ユーザーログ usersLog = 新しいユーザーログ() ;
  21. usersLog.setCreateTime(新しい日付());
  22. usersLog.setOrderId(注文ID);
  23. usersLog.setMoney(お金);
  24. ユーザーログのステータスを0に設定します。
  25. usersService.saveLog(usersLog);
  26. HttpEntity<String> requestEntity = new HttpEntity<String>(new ObjectMapper().writeValueAsString(params), headers);
  27. restTemplate.postForObject( "http://localhost:8080/payInfos/pay" 、 requestEntity、 String.class )を返します
  28. }
  29.      
  30. }

上記は2つのサブモジュールのコード全体です

テスト

初期データ

アカウントサブモジュールコンソール

支払いサブモジュールコンソール

データテーブルデータ

完了! ! !

<<:  エッジコンピューティングは業界のデジタル変革に貢献します

>>:  気をつけてください、あなたの声が盗まれます!テンセント朱雀研究所の最新研究結果が明らかに:音声セキュリティを過小評価すべきではない

推薦する

ソフトウェア定義電源はメリットがあるが、興味を持つ人はほとんどいない

電力システムを仮想化することで、企業は柔軟性を獲得し、メンテナンスを削減し、さらには余剰電力を販売し...

プロフェッショナル向けソーシャルネットワーク「Identified」が2,100万ドルを調達

海外メディアの報道によると、6月5日、プロフェッショナル向けソーシャルネットワーキングサイトIden...

SEO最適化で適切なキーワードを選択する方法

現実の生活では、多くのことを同時に持つことは不可能だと気づきます。たとえば、うらやましいほどの愛があ...

百度の外部リンク判定基準に準じて外部リンクを構築する考え方

昨日、百度ウェブマスタープラットフォームの李氏は「外部リンクの判断について」を発表しました。外部リン...

大手女性向けメイクアップポータルの代替ロングテール戦略の解釈

ウェブサイトの最適化中に、偶然、大手女性化粧品サイトを見たのですが、そのロングテールコンテンツの多く...

ウェブサイトの降格の兆候と、この現象の根本的な原因について話す

SEOERがサイトを運営していると、必ず「痛い」ことに遭遇します。それは「解放前の時代に戻った」状況...

クラウド2.0時代のクラウドコンピューティング大手間の競争は加速している

中国のクラウド コンピューティング市場は 10 年ぶりの低迷期を迎えている。クラウドコンピューティン...

クラウド アプリケーションのバックアップの選択: 遅れをとっているのは誰か?

一部の IT チームは、アプリケーションのバックアップをクラウドで実行することを選択します。ローカル...

インフルエンサーマーケティングを通じてウェブサイトの SEO を強化するにはどうすればよいでしょうか?

現在、多くの海外企業で最もよく使われているプロモーション戦略の一つが、インフルエンサーマーケティング...

5月第2週の国内ドメイン名解決プロバイダートップ10:DNSPodの市場シェアが低下

IDC Review Network (idcps.com) は 5 月 16 日に次のように報告し...

次世代ネットワークに向けて IPv6 テクノロジーを採用

有名な樽効果は、「バケツにどれだけの水が入るかは、最も短い木片によって決まる」ということを教えてくれ...

転職してクラウド コンピューティングに携わりたい場合、次の 4 つの職種のうちどれを選びますか。

国がインターネット+のトレンドを推進するにつれて、ますます多くの企業がインターネット開発の道を歩み始...

pq.hostingはどうですか? 10Gbps帯域幅のアイルランドVPSの簡単なレビュー

pq.hosting には、アイルランドのデータセンターを提供するアイルランドの VPS 事業を含め...

JDパブリッククラウド 張思聡: JDクラウドオブジェクトストレージ製品の成長の軌跡

[51CTO.comからのオリジナル記事]中国電子技術標準化研究所が主催し、51CTOが主催する「第...

特別なサイトレイアウトを構築するにはどうすればいいですか?

私たちがやりたいサイト内レイアウトは、一般的に言われているサイト内内部リンクレイアウトではなく、別の...