この記事はWeChatの公開アカウント「Program Pass」から転載したもので、著者は下記のXiaohei兄弟です。この記事を転載する場合は成旭通社の公式アカウントまでご連絡ください。 やり直すことは、再発明するよりも常に簡単です 現在、他社の実装システム(以下、旧システム)を自社のシステム(以下、新システム)に完全統合するプロジェクトに取り組んでいます。そのためには、相手側が実装した機能を自分のシステムにも完全に実装する必要があります。 旧システムのままの商店はまだ数多く存在します。既存の販売者のエクスペリエンスに影響を与えないようにするために、新しいシステムによって提供される外部インターフェースは、以前のものと一致している必要があります。最後に、システムが完全に切り替えられた後、機能は新しいシステムでのみ実行されるため、古いシステムのデータを新しいシステムに完全に移行する必要があります。 もちろん、このプロジェクトを始める前に、私はこれらを予想していました。このプロセスは難しいだろうと思っていましたが、それほど難しいとは思いませんでした。当初は半年以上も待ち時間があったので、まだ時間はたっぷりあると思っていましたが、今は少しずつ埋めていかなければならない大きな穴のように感じています。 ああ、あまり言うと涙が出そうなので、もう文句は言いません。次回は完成後に皆さんと私の本当の体験を振り返りたいと思います。 本文に戻りますが、前回の記事「Redis 分散ロック」では、Redis をベースにした分散ロックを実装しました。この分散ロックの基本的な機能に問題はありませんが、再入可能機能が欠けているため、この記事では、Brother Hei が再入可能な分散ロックを実装する方法を紹介します。 この記事では、以下の内容を取り上げます。
再入性 再入可能ロックについて言えば、まずは Wikipedia の再入性の説明を見てみましょう。 プログラムまたはサブルーチンが「いつでも中断され、その後オペレーティング システムが別のコードの実行をスケジュールし、そのコードがエラーなしでサブルーチンを呼び出す」場合、それは再入可能 (再入可能) と呼ばれます。つまり、サブルーチンの実行中に、実行スレッドがサブルーチンに入って再度実行し、設計上の期待を満たす結果を得ることができます。複数のスレッドを同時に実行する場合のスレッド セーフとは異なり、再入可能性は、単一のスレッドが実行中の場合、同じサブルーチンに再び入っても安全であることを強調しています。 スレッドがコードの一部を実行し、ロックを正常に取得して実行を続行すると、ロックされたコードに再び遭遇します。再入可能性により、スレッドは実行を継続できますが、再入可能性がない場合は、実行を継続する前にロックが解放されるのを待機し、再度ロックを正常に取得する必要があります。 Java コードを使用して再入可能性について説明します。
スレッド X がメソッド a でロックを取得した後、メソッド b の実行を継続すると仮定します。この時点で再入が不可能な場合、スレッドはロックが解放されるまで待機し、再度ロックを競合する必要があります。 ロックは明らかにスレッド X によって所有されていますが、ロックを取得する前に、スレッド X 自身がロックを解放するのを待つ必要があります。これはとても奇妙に見えます。自分を解放するよ〜 再入性により、この厄介な問題を解決できます。スレッドがロックを取得した後、将来ロック メソッドに遭遇すると、ロックの数を直接 1 増やしてからメソッド ロジックを実行します。ロックメソッドを終了した後、ロック回数が 1 減少します。ロック回数が 0 になると、ロックは完全に解除されます。 再入可能ロックの最大の特徴は、ロックが追加された回数を計算するカウント機能であることがわかります。したがって、分散環境で再入可能ロックを実装する必要がある場合は、ロックの数もカウントする必要があります。 分散再入可能ロックを実装する方法は 2 つあります。
まず、ThreadLocal の実装を見てみましょう。 ThreadLocalベースの実装 実装 Java の ThreadLocal を使用すると、各スレッドが独自のインスタンスのコピーを持つことができます。この機能を使用すると、スレッドが再入する回数をカウントできます。 次に、ThreadLocal グローバル変数 LOCKS とメモリ ストレージ Map インスタンス変数を定義します。
各スレッドは、ThreadLocal を通じて独自の Map インスタンスを取得できます。マップ内のキーにはロックの名前が格納され、値にはロックが再入力された回数が格納されます。 ロックコードは次のとおりです。
「ps: redisLock#tryLock は前回の記事で実装した分散ロックです。公開アカウントの外部リンクは直接飛べないので、「Program Master」に従って分散ロックに返信するとソースコードが手に入ります。 ロック メソッドは、まず現在のスレッドがすでにロックを所有しているかどうかを判断します。すでに所有している場合は、ロックの再入回数を直接 1 増やします。 ロックがまだ所有されていない場合は、Redis でロックを試みます。ロックが正常に取得された後、再入回数に 1 を加算します。 ロックを解除するコードは次のとおりです。
ロックを解除するときは、まず再入回数を決定します。 1 より大きい場合は、ロックがスレッドによって所有されていることを意味するため、ロック再入回数を 1 減らすだけです。 現在の再入可能回数が 1 以下の場合は、まず Map 内のロックに対応するキーを削除し、次に Redis 内のロックを解放します。 ここで注意すべきは、ロックがスレッドによって所有されておらず、直接ロック解除される場合、再入回数も 1 以下となり、今度は直接ロック解除できない可能性があるということです。 「ThreadLocal を使用する場合は、メモリ リーク、コンテキスト データ文字列の使用などの問題を防ぐために、内部ストレージ インスタンス変数を適切なタイミングでクリーンアップすることを忘れないでください。次回は、ThreadLocal を使用して記述された最近のバグについてお話ししましょう。 関連する質問 ThreadLocal を使用して再入回数をローカルに記録するのは非常にシンプルで効率的ですが、いくつか問題もあります。 有効期限の問題 上記のロック コードから、ロックを再入力すると、ローカル カウントのみが 1 増加することがわかります。これにより、業務の実行時間が長くなると、Redis の有効期限が切れてロックが解除される状況が発生する可能性があります。 再度ロックに入ると、ローカルにはまだデータが存在するため、ロックが保持されたままであると考えられますが、これは実際の状況と一致しません。 ローカルで有効期限を長くしたい場合は、ローカルと Redis の有効期限の一貫性も考慮する必要があり、コードが非常に複雑になります。 異なるスレッド/プロセスは再入可能 狭義では、再入は同じスレッドにのみ適用されますが、実際のビジネスでは、異なるアプリケーション スレッドが同じロックに再入できることが求められる場合があります。 ThreadLocal ソリューションは同じスレッドの再エントリのみを満たすことができますが、異なるスレッド/プロセス間の再エントリの問題を解決することはできません。 異なるスレッド/プロセスの再エントリの問題は、次の Redis ハッシュ ソリューションを使用して解決する必要があります。 Redis ハッシュベースの再入可能ロック 実装 ThreadLocal ソリューションでは、Map を使用してロックを再入力できる回数を記録します。また、Redis はキーと値のペアを格納できるデータ構造である Hash (ハッシュ テーブル) も提供します。したがって、Redis ハッシュを使用してロック再入回数を保存し、Lua スクリプトを使用してロジックを判断することができます。 ロックされた Lua スクリプトは次のとおりです。
「KEYS:[lock]、ARGV[1000、uuid]の場合 Lua 言語に慣れていなくても心配しないでください。上記のロジックは比較的単純です。 ロック コードは、まず Redis の exists コマンドを使用して、現在のロックが存在するかどうかを判断します。 ロックが存在しない場合は、hincrby を直接使用してキー uuid を持つロック ハッシュ テーブルを作成し、ハッシュ テーブル内のキー uuid を 0 に初期化してから、再度 1 を追加し、最後に有効期限を設定します。 現在のロックが存在する場合は、hexists を使用して、キー uuid が現在のロックに対応するハッシュ テーブルに存在するかどうかを判断します。存在する場合は、hincrby を使用して再度 1 を追加し、最後に有効期限を再度設定します。 最後に、上記の 2 つのロジックが一致しない場合は、直接戻ります。 ロックコードは次のとおりです。
「Spring-Boot 2.2.7.リリース Lua スクリプトのロック ロジックを理解していれば、Java コードの実装は非常に簡単です。 SpringBoot が提供する StringRedisTemplate を直接使用できます。 ロック解除された Lua スクリプトは次のとおりです。
まず、hexists を使用して、Redis ハッシュ テーブルに特定のフィールドが含まれているかどうかを判断します。 ロックに対応するハッシュ テーブルが存在しない場合、またはキー uuid がハッシュ テーブルに存在しない場合は、直接 nil が返されます。 存在する場合、現在のロックはそれによって保持されていることを意味します。まず、hincrby を使用して再入回数を 1 減らし、計算後に再入回数を決定します。 0 以下の場合は、del を使用してロックを削除します。 ロック解除の Java コードは次のとおりです。
ロック解除コードの実行方法はロックの場合と似ていますが、ロック解除の実行結果の戻り値の型が Long である点が異なります。ここでロックと同様にブール値が使用されていない理由は、ロック解除の Lua スクリプトでは、3 つの戻り値が次の意味を持つためです。
戻り値に Boolean が使用されている場合、Spring-data-redis は型変換中に null を false に変換します。これは論理的な判断に影響するため、戻り値の型は Long にする必要があります。 次のコードは JedisScriptReturnConverter からのものです。 関連する質問 Spring-data-redis の低バージョンの問題 Spring-Boot が接続クライアントとして Jedis を使用し、Redis Cluster クラスター モードを使用する場合は、spring-boot-starter-data-redis バージョン 2.1.9 以上を使用する必要があります。そうでない場合、実行中に次のエラーがスローされます。
現在のアプリケーションが spring-data-redis をアップグレードできない場合は、問題はありません。次の方法を使用すると、ネイティブ Jedis 接続を直接使用して Lua スクリプトを実行できます。 ロックコードを例に挙げます。
データ型変換の問題 Jedis ネイティブ接続を使用して Lua スクリプトを実行すると、データ型変換の落とし穴に遭遇する可能性があります。 Jedis#eval がオブジェクトを返すことがわかります。 Lua スクリプトの戻り値に基づいて、関連する変換を実行する必要があります。これには、Lua データ型を Redis データ型に変換することが含まれます。 次に、主に Lua データを Redis に変換するときに陥りやすいいくつかのルールについて説明します。 1. Lua の数値と Redis のデータ型変換 Lua の数値型は倍精度浮動小数点数ですが、Redis は整数型のみをサポートしているため、変換プロセスでは小数点以下の桁が破棄されます。 2. Lua ブール値と Redis 型変換 この変換はトラブルに巻き込まれやすいです。 Redis にはブール型がないため、Lua の true は Redis の整数 1 に変換されます。Lua では、false は整数に変換されず、null に変換されてクライアントに返されます。 3. Lua nil と Redis の型変換 Lua の nil は空の値とみなされ、Java の null と同等です。 Lua では、条件式に nil が現れると、false として扱われます。 したがって、Lua nil もクライアントに null を返します。 その他の変換ルールは比較的簡単です。以下を参照してください。 http://doc.redisfans.com/script/eval.html 要約する 再入可能な分散ロックの鍵は、ロックの再入をカウントすることです。この記事では主に 2 つの解決策を紹介します。 1 つは ThreadLocal 実装に基づいており、実装が簡単で、より効率的に実行されます。ただし、ロックの有効期限の問題に対処する場合、コードの実装はより複雑になります。 別のソリューションでは、Redis ハッシュ データ構造を使用して ThreadLocal の欠陥を解決しますが、コードの実装は少し難しく、Lua スクリプトといくつかの Redis コマンドに精通している必要があります。また、spring-data-redis などのツールを使用して Redis を操作する場合、意図せずさまざまな問題に遭遇する可能性があります。 ヘルプ https://www.sofastack.tech/blog/sofa-jraft-rheakv-distributedlock/ https://tech.meituan.com/2016/09/29/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html |
<<: Cloud Native Computing Foundationがテクノロジーレーダーを導入し、継続的デリバリーツールのFluxとHelmが広く採用される
>>: SaaS プロバイダーのセキュリティを確認するための 10 のプラン
Kafka は人気のあるメッセージ キュー ミドルウェアです。大量のデータをリアルタイムで処理でき、...
[51CTO.comより引用] 現在、サイバーセキュリティ市場は急速な発展期を迎えており、中国はサイ...
エッジコンピューティングサービスは、近年進化を遂げた新しい用語と言えます。エッジコンピューティングの...
コンテンツは王様、外部リンクは女王様です。この文はウェブマスター業界で最もよく知られています。多くの...
プロジェクト開発では、分散トランザクションを処理する必要があることがよくあります。たとえば、データベ...
クラウド コンピューティングは、データ駆動型開発をサポートするインフラストラクチャになりました。今日...
新型コロナウイルス感染症のパンデミックにより、多くの企業は予想よりも早く従来のテクノロジーの負担に対...
最近、Baidu ランキングの Web ページ スナップショットのほとんどに www がないことが判...
1G メモリを搭載した XEN VPS に月額 3 ユーロを支払うことについてどう思いますか?実は税...
現在、貿易摩擦が未解決であるにもかかわらず、大企業はデジタルビジネス変革をサポートするために新しいテ...
他の銀行が公式サイトの「プライベートドメイン」内に「淘宝のような」金融モールを開設しているのに比べ、...
昨日百度が発表した「サイト構文クエリの問題について」という発表を見て、多くの罪のない個人ウェブマスタ...
[[380215]] Spring Boot プロジェクトでは、複数のデータ ソースに接続するのが非...
外部リンクはオフサイト最適化効果を実現するための主な方法の1つと言えます。さらに、関連する外部リンク...
最近、ガートナーは「競争環境: OpenStack ディストリビューションおよびサポート サービス市...