プロジェクト開発では、分散トランザクションを処理する必要があることがよくあります。たとえば、データベースを複数のデータベースに分割すると、元々 1 つのデータベースで実行されていた操作が複数のデータベースにまたがる場合があります。システムがサービスに分割されると、1 つのシステム上の元の操作が複数のシステムにまたがる可能性があります。よく使用するキャッシュ (redis、memcache など) でも、分散トランザクションが関係する場合があります。キャッシュとデータベースは 2 つの異なるエンティティであるため、キャッシュとデータベース間のデータの一貫性をどのように確保するかも重要な考慮事項です。分散トランザクションとは、トランザクションによって処理されるリソースが分散システム内の異なるノードに配置されているトランザクションです。 スタンドアロン システムの場合、通常はデータベースを使用してローカル トランザクションを実装します。たとえば、次の JDBC コードはトランザクションを実装します。
分散システムでは複数のシステムで同じデータベースリンクを共有できないため、上記の処理方法をそのまま使用して分散トランザクションを実装することはできません。 以下では、私が実際の開発で使用した分散トランザクションの扱い方をいくつか紹介し、最後に分散トランザクションの関連理論を紹介してまとめます。 分散トランザクションを避ける 分散トランザクションは処理がより困難であるため、可能な限り回避する必要があります。例えば、顧客情報システムの場合、登録ユーザー数が多いため保存されるデータの量が多すぎるため、別々のデータベースとテーブルに保存されます。顧客情報モデルは、顧客基本情報テーブル、顧客ログインアカウントテーブル、顧客ログインパスワードテーブル、顧客連絡先情報テーブルなど、データベース内の複数のテーブルに対応する複数のサブモデルに分かれています。ログインアカウントテーブルと顧客基本情報テーブルの関係は次のようになっていると仮定します。 user_id と login_id は、それぞれ 2 つのテーブルの主キーです。 user_id は、2 つのテーブルを関連付ける login_info テーブルの外部キーとしても機能します。 user_id と login_id の値は、ユーザーが登録すると自動的に生成されます。 user_info テーブルと login_info テーブルは、それぞれ user_id と login_id を使用して、データベースとテーブルのシャーディング ルールを計算します。各モデルを 10 個のライブラリと 100 個のテーブルに保存するとします。つまり、user_info_00 から user_info_99 までの 100 個のテーブルがあり、そのうち user_info_00 から user_info_09 は最初のライブラリに属し、user_info_10 から user_info_19 は 2 番目のライブラリに属し、以下同様に続きます。 データベースとテーブルを分割した後、user_id と login_id の生成ルール (たとえば、数値文字列をランダムに生成するか、単純に増加するシーケンスを使用するか) を慎重に考慮しないと、同じユーザーの user_info 情報と login_info 情報が 2 つの異なるデータベースに保存され、分散トランザクションが発生する可能性があります。 この問題に直面した場合、最善の解決策は、分散トランザクションの発生を回避する方法を検討することです。ユーザーに関連するすべてのモデル データを 1 つのデータベースに保存する方法を見つければ、分散トランザクションを回避できます。各モデルデータのサブライブラリとサブテーブルのルーティングルールは、各テーブルの主キーID(user_id、login_idなど)によって決定されるため、各テーブルの主キー生成ルールをカスタマイズすれば、ユーザーのすべてのモデルデータが同じライブラリに格納されることが保証されます。次の ID 生成ルールを想定します。
このアイデアに基づいて、ユーザーが登録するときに user_id を生成し、user_id のサブライブラリとサブテーブルの場所をランダムに生成することができます。次に、他のモデル (login_id など) の主キー ID を生成するときに、このモデルの主キー ID のサブライブラリとサブテーブルの位置が user_id と同じである必要があります。注意すべきもう 1 つの点は、テーブルのクエリ条件が必ずしも主キー ID に限定される必要はないということです。他のクエリ条件列がある場合は、その列の生成ルールにも同じサブライブラリとサブテーブルの位置が含まれていることを確認する必要があります。そうしないと、その列をクエリに使用できません。 この方法では、ユーザーのすべてのモデル データが同じライブラリに保存されることが保証され、分散トランザクションの発生を効果的に回避できます。 取引補償 通常、高い同時実行性に対処する主な方法の 1 つは、分散キャッシュ (Redis など) を追加してクエリ パフォーマンスを向上させることです。分散キャッシュを追加した後のシステムによるデータのクエリのプロセスは次のとおりです。 つまり、まずキャッシュからデータをクエリしてみます。キャッシュがヒットした場合は、結果を直接返します。それ以外の場合は、DB からデータをクエリしてみてください。クエリ DB がヒットすると、データがキャッシュに追加され、次回クエリが実行されたときにキャッシュにヒットできるようになります。 データを更新する場合、通常は最初に DB のデータが更新され、DB への書き込みが成功した後にキャッシュのデータが更新されます。では、キャッシュと DB 間のデータの一貫性をどのように確保するかという疑問が生じます。キャッシュと DB は 2 つの異なるエンティティであるため、DB が正常に書き込まれた後にキャッシュが更新されます。キャッシュの更新が失敗した場合 (たとえば、ネットワーク ジッタによりキャッシュが一時的に利用できなくなる場合)、キャッシュと DB に不整合が発生します。このとき、上図のクエリ ロジックに従って、最初にキャッシュを検索すると、「ダーティ」データがクエリされ、ビジネスに重大な影響を及ぼします。これも典型的な分散トランザクションの問題です。キャッシュと DB は同時に正常に更新されるか、同時に更新に失敗します。この問題を解決するより良い方法は、トランザクション補償です。 DB にトランザクション補償テーブル transaction_log を作成できます。 transaction_log テーブルは、ビジネス データと同じデータベースに配置することも、別のデータベースに配置することもできます。データを更新する前に、更新するモデルデータをtransaction_logに記録します。たとえば、user_info テーブルのデータを更新すると、transaction_log に userId が記録されます。 transaction_log レコードが成功すると、ビジネス データ テーブル user_info の内容が更新され、最後にキャッシュ内の userInfo データが更新されます。キャッシュが正常に更新されたら、transaction_log テーブル内の対応するレコードを削除できます。 user_info テーブルが更新された後、ネットワーク ジッタまたはその他の理由によりキャッシュの更新が失敗したとします。この場合、transaction_log テーブル内の対応するレコードは引き続き存在し、トランザクションが完了しなかったことを示します。 アプリケーションは、transaction_log テーブル内のレコードを定期的にスキャンするスケジュールされたタスクを作成します (たとえば、2 秒ごとに 1 回)。条件を満たすレコードが見つかった場合、補正ロジックの実行が試行されます。たとえば、ユーザー情報を更新すると、DB 内の user_info テーブルは正常に更新されますが、キャッシュの更新は失敗します。スケジュールされたタスクは、transaction_log テーブル内の対応するレコードが削除されておらず、通常の待機時間が超過していることを検出したため、キャッシュを DB と整合させようとします (キャッシュ内の対応するデータを削除するか、userId に基づいて DB を再クエリしてキャッシュを補充することができます)。補正タスクが完了したら、transaction_log テーブル内の対応するレコードを削除できます。補正タスクが再度失敗した場合、transaction_log テーブル内のレコードは保持され、次のサイクルで再度実行されるまで待機します。 トランザクション補償により、トランザクションの最終的な一貫性が保証されます。つまり、事故が発生した場合、DB とキャッシュの不整合が発生する時間枠 (たとえば 2 秒) が発生しますが、最終的には両方のデータの整合性が保証されます。スケジュールされたタスク サイクルの設定に関しては、ビジネスの「ダーティ」データに対する感度とシステム負荷を考慮する必要があります。 トランザクションメッセージ 金融システムの場合、ユーザーが正常に登録した後にユーザーのアカウントを自動的に作成する必要があるとします。顧客情報はカスタマーセンターシステムで管理され、顧客アカウント情報は会計センターシステムで管理されます。ユーザーが正常に登録された場合、会計システムに顧客のアカウントが正常に作成されたことを確認する必要があります。これも明らかに分散トランザクションの問題です。 この問題に対処するには、前のセクションで紹介したトランザクション補償メカニズムを使用するのが当然です。ただし、登録と口座開設は同期して完了する必要はなく、ユーザーの登録成功イベントを感知する必要があるのは会計システムだけではありません (たとえば、マーケティング システムもユーザーの登録成功イベントを感知し、ユーザーにクーポンを送信する必要がある場合があります)。そのため、非同期通知にはメッセージ メカニズムを使用する方が適切です。すると、「ユーザーが正常に登録された場合、メッセージは正常に送信される必要がある」という疑問が生じます。 このシナリオに対処するには、トランザクション メッセージを使用できます。ただし、前提条件として、使用する MQ ミドルウェアが Alibaba の RocketMQ などのトランザクション メッセージをサポートしている必要があります。現在、市場に出回っている他の主流の MQ ミドルウェア (Kafka や RabbitMQ など) は、トランザクション メッセージをサポートしていません。 次のシーケンス図は、トランザクション メッセージの実行フローを示しています。
注意深い友人は、上図のステップ 5 で問題が発生してコミットが失敗した場合、メッセージ発行者とメッセージ サブスクライバー間のトランザクションに不整合が生じることに気付くでしょう。これを防ぐには、MQ タイムアウト コールバック メカニズムを追加します。 次のシーケンス図は、トランザクション メッセージのコミットが失敗した場合の実行フローを示しています。 MQ がパブリッシャーからコミット/ロールバック通知を長時間受信しない場合、MQ はパブリッシャー アプリケーションをコールバックして、ローカル トランザクションが正常に実行されたかどうか、およびコミットまたはロールバック前のメッセージであるかどうかを問い合わせます。パブリッシャーは、ローカル トランザクションが正常に実行されたかどうかを判断するために、対応するコールバックを提供する必要があります。 TCC 2 フェーズ コミット シナリオによっては、分散トランザクションに複数の参加者が関与し、各参加者が現在の状態に基づいてトランザクションに応答する必要がある場合があります。 電子商取引の Web サイトで、ユーザーが支払い時に複数の支払い方法から選択できるシナリオを想定します。たとえば、合計支払額が 100 元の場合、ユーザーは 10 元をポイントで支払い、90 元を口座残高で支払うことを選択できます。マーケティングシステムはユーザーのポイントを管理し、会計システムはアカウント残高を管理し、注文システムは注文ステータスの管理を担当します。
この分散トランザクション シナリオに対処するには、TCC 2 フェーズ コミット方式を使用できます。 TCC は、トランザクション全体を試行とコミット/キャンセルの 2 つのフェーズに分割します。 TCC プロセス全体には、トランザクション イニシエーター、トランザクション 参加者、トランザクション コーディネーターの 3 つの役割があります。上記の注文支払いを例にとると、TCC を使用してトランザクションを処理するプロセスは次のようになります。
ただし、このアプローチだけでは一貫性の問題が残ります。たとえば、第 2 フェーズのコミット中にクラッシュやネットワーク ジッターなどの異常な状況が発生した場合、トランザクションは「最終的に一貫性がない」状態になる可能性があります (参加者が try フェーズのみを実行し、第 2 フェーズを実行しなかった場合、または一部の参加者が第 2 フェーズのコミットに成功し、一部の参加者がコミットに失敗した場合)。この状況に対処するには、例外が発生したときにトランザクションを回復できるように、トランザクション ログを追加する必要があります。 トランザクション ログを記録するための信頼性の高いストレージとして DB を使用できます。ログには、トランザクション実行プロセスのコンテキスト、トランザクション実行ステータス、トランザクション参加者などの情報が含まれている必要があります。トランザクション ログは、トランザクション イニシエーターまたはトランザクション コーディネーターによって記録できます。 トランザクション ログは、マスター トランザクション ログとスレーブ トランザクション ログで構成できます。
トランザクション ログを使用すると、トランザクション ログを定期的にスキャンして、異常に中断されたトランザクションを見つけることができます。トランザクション ログに記録された情報に従って、残りの参加者はコミットまたはキャンセルをプッシュされ、分散トランザクション全体が「最終的な一貫性」を実現できるようになります。 コミット フェーズで例外が発生した場合のトランザクション補正ロジックは次のとおりです。 TCC 2 フェーズ コミットを実装する場合は、次の点に注意する必要があります。
要約する 従来のスタンドアロン トランザクションは、A (原子性)、C (一貫性)、I (独立性)、D (永続性) の 4 つの特性を満たす必要があり、固定されたトランザクションです。分散システムには複数のノードがあるため、4 つの ACID 仕様を完全に満たすことは非常に困難です。そこで、柔軟なトランザクション BASE 理論 (基本的な可用性、ソフト ステート、最終的な一貫性) が誕生しました。 スタンドアロン トランザクションと比較すると、分散トランザクションは A と D では厳密に保証されますが、C と I では制限をある程度緩和する必要があります (中間状態データの表示と最終的な一貫性の許可)。 |
5月3日に、オープンソースの要塞ホスト Jumpserver 1.3 バージョンが正式にリリースされ...
インターネットの普及により、私たちはますますインターネットに依存するようになっています。特にモバイル...
昨日、暇だったのでspeedykvmを推奨する記事「推奨:今年のベストKVM/SSD[raid10]...
9月3日のアップデートで、百度はウェブサイトのセキュリティリスク警告機能を正式に追加しました。ハッカ...
インターネット上のブログは、チャットや知識の探求のためのプラットフォームにもなりつつあります。有名な...
コアヒント:画像の著作権は中国の画像ビジネスモデルの基盤であり、世界を征服するための武器です。しかし...
多くの人が「クローズドループは誤った命題だ」と話すとき、多くの本当の業界関係者は「ははは」と反応する...
ウェブサイトのTDKの3つの要素(タイトル、説明、キーワード)は、できるだけ変更しない方が良い、そう...
オンライン マーケティングが成熟するにつれて、SEO テクノロジーも広く使用され、学習されるようにな...
[はじめに] 昨日はSEOとは何かについてお話ししました。BaiduはBaidu Web Searc...
Ouxun Cloud(すべての資格を備えた遼寧省の国内企業)は現在、大規模な秋のプロモーションを実...
ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス業界ではよく知られている...
今朝、コンピューターの電源を入れたとき、Kingsoft Internet Security が W...
UI 作業を簡素化し、運用および保守担当者により柔軟なリソース クエリ方法を提供するために、ZSta...
昨日、「JD.com の一般的なセカンダリ カテゴリ ページの SEO に関する簡単な分析 (パート...