Java はさまざまなロックを提供していますが、なぜ分散ロックが必要なのでしょうか?

Java はさまざまなロックを提供していますが、なぜ分散ロックが必要なのでしょうか?

[[357636]]

現在、単一プロジェクト構造は基本的に残っておらず、分散クラスターやマイクロサービスがほとんどです。サーバーが複数あるため。資源共有の問題は避けられません。リソース共有であるため、同時実行の問題は避けられません。これらの問題に対して、Redis は分散ロックという優れたソリューションも提供します。この記事では、分散ロックがなぜ必要なのかというトピックについて主に説明します。

少し前に、グループ内の仲間が、分散ロックでほとんどの運用上の問題を解決できるのであれば、Java が提供するロックは何の役に立つのかと質問しました。分散ロックを直接使用したほうが良いのではないでしょうか?私はこの質問についてよく考え、最初は同様の答えがあるかどうかオンラインで調べました。それから私はそれについて考えました。この問題を解決するには、本質から分析する必要があります。

よし、車に乗って出発しよう。

1. はじめに

これは分散ロックであるため、サーバーが 1 つだけではなく、複数存在する可能性があります。例を使って段階的に説明します。あるウェブサイトにフラッシュセール商品があり、まだ 100 個残っているとします。すると、陝西省、江蘇省、チベットなどの人々がこのイベントを見て、狂ったように買い始めるのです。このフラッシュセール商品の数量値が Redis データベースに保存されていると仮定します。

ただし、異なる地域のユーザーはフラッシュセールに異なるサーバーを使用します。これにより、クラスター アクセス メソッドが形成されます。

Redis を統合するために Springboot を使用します。

2. プロジェクトの準備

(1)POM依存関係を追加する

  1. <依存関係>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </依存関係>
  5. <依存関係>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-test</artifactId>
  8. <scope>テスト</scope>
  9. </依存関係>
  10. <依存関係>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-data-redis</artifactId>
  13. </依存関係>
  14. <依存関係>
  15. <グループ ID>org.apache.commons</グループ ID>
  16. <artifactId>コモンズプール2</artifactId>
  17. </依存関係>

(2)属性設定を追加する

  1. # Redis データベース インデックス (デフォルトは 0)
  2. spring.redis.データベース= 0
  3. # Redis サーバーのアドレス
  4. spring.redis.host=ローカルホスト
  5. # Redis サーバー接続ポート
  6. spring.redis.port=6379
  7. # Redis サーバー接続パスワード (デフォルトでは空)
  8. spring.redis.パスワード=
  9. # 接続プール内の最大接続数(負の値は制限なしを意味します)デフォルトは 8 です
  10. spring.redis.lettuce.pool.max -アクティブ=8
  11. # 接続プールの最大ブロッキング待機時間(負の値は制限なしを意味します)デフォルトは -1 です
  12. spring.redis.lettuce.pool.max -wait =-1
  13. # 接続プール内のアイドル接続の最大数。デフォルトは8です
  14. spring.redis.lettuce.pool.max -idle =8
  15. # 接続プール内のアイドル接続の最小数。デフォルトは0です
  16. spring.redis.lettuce.pool です。最小アイドル=0

(3)新しい設定パッケージを作成し、RedisConfigクラスを作成する

  1. @構成
  2. パブリッククラスRedisConfig {
  3. @ビーン
  4. パブリックRedisTemplate<String, Serializable >
  5. redisTemplate(レタス接続ファクトリー接続ファクトリー) {
  6. RedisTemplate<String, Serializable > redisTemplate = new RedisTemplate<>();
  7. redisTemplate.setKeySerializer(新しい StringRedisSerializer());
  8. redisTemplate.setValueSerializer(新しい GenericJackson2JsonRedisSerializer());
  9. redisTemplate.setConnectionFactory(接続ファクトリー);
  10. redisTemplateを返します
  11. }
  12. }

(4)新しいコントローラーを作成し、Mycontrollerクラスを作成します。

  1. @レストコントローラ
  2. パブリッククラスMyController{
  3. オートワイヤード
  4. プライベート StringRedisTemplate stringRedisTemplate;
  5. @GetMapping( "/テスト" )
  6. パブリック文字列deduceGoods(){
  7. int商品 = Integer .parseInt(stringRedisTemplate.opsForValue().get( "商品" ));
  8. 実商品= 商品数-1;
  9. もし商品>0の場合{
  10. stringRedisTemplate.opsForValue() です。 ( "商品" 、実商品 + "" )を設定します
  11. 戻る  「商品は正常に購入されましたが、まだ次の商品が残っています: " + realGoods + "個" ;
  12. }それ以外{
  13. 戻る  「商品は売り切れました。次回のイベントにようこそ」 ;
  14. }
  15. }
  16. }

非常にシンプルな統合チュートリアル。ポートは 8080 です。プロジェクトをコピーし、ポートを 8090 に変更し、負荷分散として nginx を使用してクラスターを構築します。これで環境が整理されました。以下の分析を始めましょう。

3. 分散ロックが必要なのはなぜですか?

フェーズ1: ネイティブアプローチの採用

ポート 8080 にアクセスするために複数のスレッドを使用します。ロックがないため、この時点で同時実行の問題が確実に発生します。したがって、この商品は共有リソースであり、複数のスレッドによってアクセスされるため、Java のさまざまなロックをすぐに思い浮かべることができますが、その中で最も有名なのは synchronized です。したがって、上記のコードを最適化したほうがよいでしょう。

フェーズ2: 同期ロックの使用

この時点でコードを変更します。

  1. @レストコントローラ
  2. パブリッククラスMyController{
  3. オートワイヤード
  4. プライベート StringRedisTemplate stringRedisTemplate;
  5. @GetMapping( "/テスト" )
  6. パブリック文字列deduceGoods(){
  7. 同期された(これ){
  8. int商品 = Integer .parseInt(stringRedisTemplate.opsForValue().get( "商品" ));
  9. 実商品= 商品数-1;
  10. もし商品>0の場合{
  11. stringRedisTemplate.opsForValue() です。 ( "商品" 、実商品 + "" )を設定します
  12. 戻る  「商品は正常に購入されましたが、まだ次のものが残っています: " + realGoods + "個" ;
  13. }それ以外{
  14. 戻る  「商品は売り切れました。次回のイベントにようこそ」 ;
  15. }
  16. }
  17.  
  18. }
  19. }

ご覧のとおり、現在、 synchronized キーワードを使用してロックを追加しているため、複数のスレッドが同時にアクセスするときにデータの不整合やその他の問題が発生しません。このアプローチは、単体構造では確かに役立ちます。現在、単一の構造を持つプロジェクトは非常に少なく、大部分はクラスター化されています。この時点で同期は機能しなくなります。なぜ同期が機能しないのですか?

フラッシュセール製品にアクセスするためにクラスター アプローチを使用します (nginx が負荷分散を行います)。データの不一致が表示されます。つまり、 synchronized キーワードのスコープは実際にはプロセスであり、このプロセスの下のすべてのスレッドをロックできます。ただし、マルチプロセスは不可能です。フラッシュセール商品の場合、この値は固定です。ただし、各地域にサーバーが存在する可能性があります。このように、異なる地域のサーバーは異なり、アドレスも異なり、プロセスも異なります。したがって、同期ではデータの一貫性を保証することはできません。

フェーズ3: 分散ロック

上記の synchronized キーワードでは、複数のプロセスのロック メカニズムを保証することはできません。この問題を解決するには、Redis 分散ロックを使用できます。では、コードをもう一度変更してみましょう。

  1. @レストコントローラ
  2. パブリッククラスMyController{
  3. オートワイヤード
  4. プライベート StringRedisTemplate stringRedisTemplate;
  5. @GetMapping( "/テスト" )
  6. パブリック文字列deduceGoods(){
  7. ブール値の結果 = stringRedisTemplate.opsForValue().setIfAbsent( "lock" , "Feng Dongdong" );
  8. if(!結果){
  9. 戻る  「他の人はそれを即座に殺しているので入ることができません」 ;
  10. }
  11. int商品 = Integer .parseInt(stringRedisTemplate.opsForValue().get( "商品" ));
  12. 実商品= 商品数-1;
  13. もし商品>0の場合{
  14. stringRedisTemplate.opsForValue() です。 ( "商品" 、実商品 + "" )を設定します
  15. システム。 out .println( "商品の購入に成功しました。残りのアイテムは: " + realGoods + "items" );
  16. }それ以外{
  17. システム。 out .println( "商品は売り切れました。次のイベントへようこそ" );
  18. }
  19. stringRedisTemplate.delete ( "lock" ) ;
  20. 戻る  "成功" ;
  21. }
  22. }

とても簡単です。文章を追加して判断するだけです。実際、setIfAbsent メソッドの機能は、redis の setnx と同じです。つまり、現在のキーがすでに存在する場合、操作は実行されず、false が返されます。現在のキーが存在しない場合は、操作できます。最後に、他のユーザーがインスタント キル操作を実行できるように、このキーを放すことを忘れないでください。

もちろん、これは単なる基本的な例です。実際、分散ロックを実装するには多くの手順があり、多くの落とし穴が指摘されていません。いくつか解決してみましょう:

フェーズ4: 分散ロックの最適化

(1)第一の落とし穴:フラッシュセール商品では例外が発生し、分散ロックが解除できない

  1.   パブリック文字列deduceGoods()は例外をスローします{
  2. ブール値の結果 = stringRedisTemplate.opsForValue().setIfAbsent( "lock" , "Feng Dongdong" );
  3. if(!結果){
  4. 戻る  「他の人はそれを即座に殺しているので入ることができません」 ;
  5. }
  6. 試す {
  7. int商品 = Integer .parseInt(stringRedisTemplate.opsForValue().get( "商品" ));
  8. 実商品= 商品数-1;
  9. もし商品>0の場合{
  10. stringRedisTemplate.opsForValue() です。 ( "商品" 、実商品 + "" )を設定します
  11. システム。 out .println( "商品の購入に成功しました。残りのアイテムは: " + realGoods + "items" );
  12. }それ以外{
  13. システム。 out .println( "商品は売り切れました。次のイベントへようこそ" );
  14. }
  15. }ついに {
  16. stringRedisTemplate.delete ( "lock" ) ;
  17. }
  18. 戻る  "成功" ;
  19. }

この時点では、try ステートメントと finally ステートメントを追加するだけです。ロックは最終的には削除する必要があります。

(2)2つ目の落とし穴:フラッシュセールに時間がかかり、他のユーザーが待てない

  1. パブリック文字列deduceGoods()は例外をスローします{
  2. stringRedisTemplate.expire( "lock" ,10, TimeUnit.MILLISECONDS);
  3. ブール値の結果 = stringRedisTemplate.opsForValue().setIfAbsent( "lock" , "Feng Dongdong" );
  4. if(!結果){
  5. 戻る  「他の人はそれを即座に殺しているので入ることができません」 ;
  6. }
  7. 試す {
  8. int商品 = Integer .parseInt(stringRedisTemplate.opsForValue().get( "商品" ));
  9. 実商品= 商品数-1;
  10. もし商品>0の場合{
  11. stringRedisTemplate.opsForValue() です。 ( "商品" 、実商品 + "" )を設定します
  12. システム。 out .println( "商品の購入に成功しました。残りのアイテムは: " + realGoods + "items" );
  13. }それ以外{
  14. システム。 out .println( "商品は売り切れました。次のイベントへようこそ" );
  15. }
  16. }ついに {
  17. stringRedisTemplate.delete ( "lock" ) ;
  18. }
  19. 戻る  "成功" ;
  20. }

これに有効期限を追加します。つまり、10 ミリ秒以内にフラッシュ セールが成功しない場合は、フラッシュ セールが失敗したことを意味し、次のユーザーが置き換えられます。

(3)3番目の落とし穴:同時実行性の高いシナリオでは、フラッシュセールが長くなりすぎてロックが永久に無効になる

先ほど設定したロックの有効期限は 10 ミリ秒です。ユーザーのフラッシュセール時間が 15 ミリ秒の場合、そのユーザーがフラッシュセールに成功する前に他のユーザーが参加する可能性があることを意味します。このような状況が頻繁に発生すると、製品の購入に成功するまでに多数のユーザーが流入する可能性があります。他のユーザーが事前にロックを解除したが、現在のユーザーはまだフラッシュセールに成功していない可能性があります。これは最終的にデータの不整合につながります。解決方法をご覧ください:

  1. パブリック文字列deduceGoods()は例外をスローします{
  2. 文字列ユーザー= UUID.randomUUID().toString();
  3. stringRedisTemplate.expire( "lock" ,10, TimeUnit.MILLISECONDS);
  4. ブール値の結果 = stringRedisTemplate.opsForValue().setIfAbsent( "lock" , user );
  5. if(!結果){
  6. 戻る  「他の人はそれを即座に殺しているので入ることができません」 ;
  7. }
  8. 試す {
  9. int商品 = Integer .parseInt(stringRedisTemplate.opsForValue().get( "商品" ));
  10. 実商品= 商品数-1;
  11. もし商品>0の場合{
  12. stringRedisTemplate.opsForValue() です。 ( "商品" 、実商品 + "" )を設定します
  13. システム。 out .println( "商品の購入に成功しました。残りのアイテムは: " + realGoods + "items" );
  14. }それ以外{
  15. システム。 out .println( "商品は売り切れました。次のイベントへようこそ" );
  16. }
  17. }ついに {
  18. if( user .equals(stringRedisTemplate.opsForValue().get( "lock" ))){
  19. stringRedisTemplate.delete ( "lock" ) ;
  20. }
  21. }
  22. 戻る  "成功" ;
  23. }

つまり、ロックを削除するときに、それが現在のスレッドであるかどうかを判断します。そうであれば削除します。そうでない場合は削除しません。こうすることで、他のスレッドが侵入しても、ロックがランダムに削除され、混乱が生じることはありません。

さて、ここまでは分散ロックの理由について基本的に紹介しました。 Redisson は分散ロックにおいて素晴らしい仕事をしました。次の記事でも、Redisson に焦点を当てて、分散の実装方法とその背後にある原則を紹介します。

この記事はWeChat公式アカウント「Yugong Yaoyishan」から転載したものです。下のQRコードからフォローできます。この記事を転載する場合は、Yugongyaoyishanの公式アカウントまでご連絡ください。

<<:  企業はどのようにしてクラウド移行を段階的に進めることができるのでしょうか?

>>:  Ubuntu Server に Docker なしで Kubernetes をインストールするにはどうすればいいですか?

推薦する

Liziqi を「貪っている」のは誰ですか?

Liziqiは「消去」されています。 7月14日、李子奇は主要プラットフォームでビデオ「米、油、塩、...

タオバオは史上最も厳しい偽造品対策規則を制定しました。悪意を持って偽造品を販売する店舗は直ちに閉鎖されます。

【TechWeb Report】7月22日、Taobaoは偽造品販売に関する最新の管理規則を発表した...

ウェブサイトの掲載に影響を与える要因の簡単な分析(I)

自分のウェブサイトが含まれていない場合はどうすればいいですか? 重みが足りない場合は、フレンドリーリ...

推奨: AlphaRacks - 5.99 USD/4 コア/1 GB RAM/90 GB ハード ドライブ/3.5 TB トラフィック

Alpharacks は、DDOS 保護、OpenVZ ベースの VPS、サーバー レンタルを提供す...

分散アーキテクチャでは、従来のデータベースの運用と保守はどのような変化に直面するでしょうか?

[[319472]]分散アーキテクチャは近年最もホットなトピックかもしれません。この記事では、集中型...

ブランドマーケティングの根底にあるロジック

ポストトラフィック時代では、モバイルインターネットと消費者インターネットの配当は消え、コンテンツ電子...

クラウド アプリケーションをより効率的に開発するための 5 つのヒント

クラウド テクノロジーが IT 業界を席巻している今日、クラウド コンピューティングの出現後に会社が...

クラウドコンピューティングを使用するときは、群衆に追随しないでください

少し前に読んだレポートでは、その概要はさまざまな業界におけるクラウド コンピューティングの適用に関す...

VPSデータベース - 6ドル/月/openvz/kvm/ロシア/カナダ/カンザス

vpsdatabaseは2017年5月に設立され、競争力のあるVPS製品を提供しています。販売中の2...

hostyun の月額 18 元の香港 VPS (米国ネイティブ IP、100M 帯域幅、AMD 5950X) の簡単なレビュー

Hostyunは最近、香港のクラウドデータセンターに「[Hong Kong AMD-Puhui]」と...

SEO 外部リンク構築でよくある 8 つの一方的な実践

SEO 担当者は、ウェブサイトに最適化されたキーワードがキーワード検索ランキングで上位に表示されるよ...

マイクロソフト、中国でのMSNサービスを停止すると発表

マイクロソフトのインスタント メッセージング ソフトウェア Windows Live Messeng...

#香港VPS# gigsgigscloud-6.9USD/windows/512MB RAM/300GBトラフィック/100MBポート

gigsgigscloud は本日、香港データセンターで Windows VPS を正式に開始しまし...

SEMの医療SEOキーワード戦略はユーザーの検索体験に応えます

2 月 16 日に、「SEM の医療 PPC の、少ない労力で機能する高コンバージョン ランディング...

Weiboマーケティング:Weiboの人気検索を有効活用する方法

陳念が以前書いた「ソーシャル ネットワーキング プラットフォームと SEO が出会うとき」という記事...