これは良いことです: Redisの分散ロックの深い理解につながります

これは良いことです: Redisの分散ロックの深い理解につながります

[[375372]]

分散ロックとは何ですか?

Redis に関して言えば、最初に思い浮かぶ機能はデータをキャッシュする機能です。さらに、Redis は単一プロセスと高パフォーマンスの特性を備えているため、分散ロックによく使用されます。

ロックはプログラム内の同期ツールであり、共有リソースに一度に 1 つのスレッドのみがアクセスできるようにするものであることは誰もが知っています。私たちは皆、よく使用する synchronized や Lock などの Java のロックに精通しています。ただし、Java ロックは単一のマシンでのみ有効であり、分散クラスター環境では有効ではありません。このとき、分散ロックを使用する必要があります。

分散ロックは、その名前が示すように、分散プロジェクト開発で使用されるロックです。分散システム間の共有リソースへの同期アクセスを制御するために使用できます。一般的に、分散ロックは次の特性を満たす必要があります。

1. 相互排他: 常に、同じデータに対して分散ロックを取得できるのは 1 つのアプリケーションだけです。

2. 高可用性: 分散シナリオでは、少数のサーバーのダウンタイムが通常の使用に影響することはありません。この場合、分散ロックを提供するサービスをクラスターにデプロイする必要があります。

3. ロックのタイムアウトを防止する: クライアントが積極的にロックを解除しない場合、サーバーは一定時間後に自動的にロックを解除し、クライアントがクラッシュしたりネットワークにアクセスできなくなったりしたときにデッドロックを防止します。

4. 排他性: ロックとロック解除は同じサーバーによって実行される必要があります。つまり、ロック保持者だけがロックを解除できます。追加したロックを他の人がロック解除することはできません。

業界には分散ロック効果を実現できるツールが数多くありますが、その操作はロック、ロック解除、ロックタイムアウトの防止だけです。

この記事は Redis 分散ロックに関するものなので、Redis の知識ポイントに基づいて拡張するのは自然なことです。

ロックを実装するコマンド

まず、Redis コマンドをいくつか紹介します。

1. SETNX、使用法はSETNXキー値

SETNX は「SET if Not eXists」の略語です。設定が成功した場合は 1 を返し、それ以外の場合は 0 を返します。


setnx の使用法

キーロックの値を「Java」に設定した後、別の値に設定すると失敗することがわかります。見た目はシンプルでロックを独占しているように見えますが、鍵に有効期限がないという致命的な問題があります。この方法では、キーを手動で削除するか、ロックを取得した後に有効期限を設定しない限り、他のスレッドがロックを取得することはありません。

この場合、キーに有効期限を追加し、ロックを取得するときにスレッドが直接 2 つの手順を実行できるようにすることができます。

  1. SETNXキー1
  2. 期限切れキー秒数

このソリューションにも問題があります。ロックの取得と有効期限の設定が 2 つのステップに分割されており、これらはアトミック操作ではないためです。ロックの取得は成功しても、時間の設定に失敗する可能性があります。それは時間の無駄ではないでしょうか?

しかし、心配しないでください。Redisの担当者はすでにこの問題を検討しているので、次のコマンドが導入されています。

2. SETEX、使用法 SETEX キーの秒数の値

値 value をキーに関連付け、キーの有効期間を秒数に設定します。キーがすでに存在する場合、SETEX コマンドは古い値を上書きします。

このコマンドは次の 2 つのコマンドに似ています。

  1. セット キー
  2. EXPIREキー秒数 # 有効期限を設定する

これら 2 つのステップはアトミックであり、同時に完了します。


setexの使用法

3. PSETEX、使用法 PSETEX キー ミリ秒値

このコマンドは SETEX コマンドに似ていますが、SETEX コマンドのように秒単位ではなくミリ秒単位でキーの有効期間を設定します。

ただし、Redis バージョン 2.6.12 以降では、SET コマンドでパラメータを使用して、SETNX、SETEX、および PSETEX コマンドと同じ効果を実現できます。

例えば、このコマンド

  1. セット キー値 NX EX 秒

NX および EX パラメータを追加すると、その効果は SETEX と同等になり、これは Redis でロックを取得する最も一般的な方法でもあります。

ロックを解除する方法

ロックを解除するコマンドは簡単で、キーを直接削除するだけです。しかし、前述したように、分散ロックはロック保持者自身によって解放される必要があるため、まず現在ロックを解放しているスレッドが保持者であることを確認してから削除する必要があります。このようにすると、2 つのステップになり、原子性に違反しているように見えます。私たちは何をすべきでしょうか?

慌てる必要はありません。Lua スクリプトを使用して、次のように 2 つのステップを組み立てることができます。

  1. redis.call( "get" 、KEYS[1]) == ARGV[1]の場合
  2. それから 
  3. redis.call( "del" ,KEYS[1])を返す
  4. それ以外 
  5. 0を返す
  6. 終わり 

KEYS[1]は現在のキーの名前であり、ARGV[1]は現在のスレッドのID(またはそれが属するスレッドを識別できる他の固定でない値)になります。これにより、期限切れのロックを保持しているスレッドや他のスレッドが誤って既存のロックを削除することを防ぐことができます。

コードの実装

原理を理解した後は、手動でコードを記述して、Redis 分散ロックの機能を実装できます。この記事の目的は、分散ロックの書き方を教えることではなく、主に原理を説明することなので、擬似コードで実装しました。

最初は、ロックとロック解除の基本的なメソッドを含む redis ロック ツール クラスです。

  1. パブリッククラスRedisLockUtil {
  2.  
  3. プライベート文字列 LOCK_KEY = "redis_lock" ;
  4.  
  5. //キー保持時間、5ms
  6. プライベートロングEXPIRE_TIME = 5;
  7.  
  8. // 待機タイムアウト、1秒
  9. プライベートロングTIME_OUT = 1000;
  10.  
  11. // redis コマンドパラメータ。nx および px のコマンドセットと同等
  12. プライベートSetParams params = SetParams.setParams().nx().px(EXPIRE_TIME);
  13.  
  14. // ローカルの Redis クライアントに接続された Redis 接続プール
  15. JedisPool jedisPool = new JedisPool( "127.0.0.1" , 6379);
  16.  
  17. /**
  18. * ロック
  19. *
  20. * @パラメータID
  21. * スレッドID、または現在のスレッドを識別できるその他の一意のフィールド
  22. * @戻る 
  23. */
  24. パブリックブールロック(文字列id) {
  25. ロングスタート = System.currentTimeMillis();
  26. ジェディス jedis = jedisPool.getResource();
  27. 試す {
  28. のために(;;) {
  29. // SETコマンドはOKを返し、ロックが正常に取得されたことを証明します
  30. 文字列ロック = jedis.set (LOCK_KEY, id, params);
  31. if ( "OK" .equals(lock)) {
  32. 戻る 真実;
  33. }
  34. // それ以外の場合はループして待機します。 TIME_OUT 内にロックが取得されない場合、取得は失敗します。
  35. long l = System.currentTimeMillis() - 開始;
  36. (l >= TIME_OUT)の場合{
  37. 戻る 間違い;
  38. }
  39. 試す {
  40. // しばらくスリープします。そうしないとループが繰り返し失敗します。
  41. スレッド.sleep(100);
  42. } キャッチ (InterruptedException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. ついに
  47. jedis.close ();
  48. }
  49. }
  50.  
  51. /**
  52. * ロック解除
  53. *
  54. * @パラメータID
  55. * スレッドID、または現在のスレッドを識別できるその他の一意のフィールド
  56. * @戻る 
  57. */
  58. パブリックブールロック解除(文字列id) {
  59. ジェディス jedis = jedisPool.getResource();
  60. //キーのluaスクリプトを削除する
  61. 文字列スクリプト = "if redis.call('get',KEYS[1]) == ARGV[1] then" + " return redis.call('del',KEYS[1]) " + "else"  
  62. + " 0 を返す " + "終了" ;
  63. 試す {
  64. 文字列結果 =
  65. jedis.eval(スクリプト、Collections.singletonList(LOCK_KEY)、Collections.singletonList(id)).toString();
  66. 戻る  "1" .equals(結果);
  67. ついに
  68. jedis.close ();
  69. }
  70. }
  71. }

特定のコード関数のコメントが明確に記述されているので、効果をテストするためのデモ クラスを作成できます。

  1. パブリッククラスRedisLockTest {
  2. プライベート静的RedisLockUtil デモ = 新しい RedisLockUtil();
  3. プライベートスタティック 整数NUM = 101;
  4.  
  5. 公共 静的void main(String[] args) {
  6. ( int i = 0; i < 100; i++)の場合{
  7. 新しいスレッド(() -> {
  8. 文字列 id = Thread.currentThread().getId() + "" ;
  9. ブール値 isLock = demo.lock(id);
  10. 試す {
  11. // ロックを取得したら、共有パラメータを減算します
  12. if (isLock) {
  13. --;  
  14. System.out.println (数値) ;
  15. }
  16. ついに
  17. // ロックを解除し、最後に必ずロックを入れてください
  18. デモのIDをロック解除します。
  19. }
  20. })。始める();
  21. }
  22. }
  23. }

同時実行をシミュレートするために 100 個のスレッドを作成し、実行後の結果は次のようになります。


コード実行結果

ロックの効果が得られ、スレッドの安全性が保証されていることがわかります。

もちろん、上記のコードは単に効果を実現するだけであり、機能は明らかに不完全です。健全な分散ロックには考慮すべき点が多く、実際の設計はそれほど簡単ではありません。

私たちの目標は、原則を学び、理解することだけです。産業グレードの分散ロック ツールを手作業で作成するのは非現実的であり、不必要です。同様のオープンソース ツール (Redisson) は多数あり、原理も似ており、業界の同業者によって長い間テストされているため、そのまま使用できます。

機能は実現されていますが、実際には設計の観点から見ると、このような分散ロックには大きな欠陥があり、それがこの記事の焦点でもあります。

分散ロックの欠点

1. クライアントの長期ブロックはロックの失敗につながる

クライアント 1 はロックを取得しますが、ネットワークの問題または GC により長時間ブロックされ、ビジネス プログラムが実行される前にロックが期限切れになります。このとき、クライアント 2 も正常にロックを取得できるため、スレッド セーフティの問題が発生する可能性があります。


クライアントが長時間ブロックされました

では、このような異常をどうやって防ぐのでしょうか?最初に解決策について話すのではなく、他の欠陥を紹介してから議論しましょう。

2. Redis サーバーのクロックドリフト問題

Redis サーバーのマシン クロックが進むと、キーは早期にタイムアウトになります。たとえば、クライアント 1 がロックを取得した後、キーは 12:02 に期限切れになりますが、Redis サーバー自体のクロックはクライアントよりも 2 分進んでいるため、キーは 12:00 に無効になります。このとき、クライアント 1 がロックを解除していない場合、複数のクライアントが同時に同じロックを保持する可能性があります。

3. 単一ポイントインスタンスのセキュリティ問題

Redis がシングルマスター モードの場合、このマシンがダウンすると、すべてのクライアントがロックを取得できなくなります。可用性を向上させるために、マスターにスレーブを追加することができます。ただし、Redis のマスターとスレーブの同期は非同期であるため、クライアント 1 がロックを設定した後に、マスターがハングアップし、スレーブがマスターに昇格する可能性があります。非同期レプリケーションの特性により、クライアント 1 によって設定されたロックは失われます。この時点で、クライアント 2 もロックを正常に設定できるため、クライアント 1 とクライアント 2 が同時にロックを取得することになります。

Redis の単一点問題を解決するために、Redis の作者は RedLock アルゴリズムを提案しました。

RedLockアルゴリズム

このアルゴリズムを実装するための前提は、Redis を複数のノードにデプロイする必要があり、これにより単一点障害を効果的に防ぐことができるということです。具体的な実装アイデアは次のとおりです。

1. 現在のタイムスタンプ(ミリ秒)を取得します。

2. まず、キーの有効期間 (TTL) を設定します。この時間以降は自動的に解除されます。次に、クライアントは同じキーと値を使用してすべての Redis インスタンスを設定しようとします。 Redis インスタンスに接続するたびに、TTL よりもはるかに短いタイムアウトを設定します。これは、閉じられた Redis サービスを長時間待つことを避けるためです。そして、次の Redis インスタンスを取得してみます。

たとえば、TTL (有効期限) が 5 秒の場合、ロックを取得するためのタイムアウトを 50 ミリ秒に設定できます。したがって、50 ミリ秒以内にロックを取得できない場合は、ロックは放棄され、次のロックが試行されます。

3. クライアントは、最初のステップの時間と Redis サーバーのクロック ドリフト エラーを差し引いた、取得可能なすべてのロックを取得します。この時間差が TTL 時間より短く、ロックを正常に設定したインスタンスの数が >= N/2 + 1 (N は Redis インスタンスの数) の場合、ロックは正常に追加されます。

たとえば、TTL が 5 秒の場合、Redis に接続してすべてのロックを取得するのに 2 秒かかり、その後クロック ドリフト (エラーが約 1 秒であると仮定) を差し引くと、ロックの実際の有効時間は 2 秒だけになります。

4. 何らかの理由でクライアントがロックを取得できない場合、すべての Redis インスタンスのロック解除を開始します。

このアルゴリズムによれば、5 つの Redis インスタンスがあると仮定すると、クライアントは 3 つ以上からロックを取得すれば成功したとみなされます。フローチャートのデモンストレーションはおそらく次のようになります。


キーの有効期間

さて、アルゴリズムが導入されました。設計の観点から見ると、RedLock アルゴリズムのアイデアは主に Redis の単一点障害の問題を効果的に防止することであることは間違いありません。さらに、TTL の設計時にはサーバー クロックのドリフトのエラーも考慮されるため、分散ロックのセキュリティが大幅に向上します。

しかし、これは本当にそうなのでしょうか?個人的には効果は平均的だと感じています。

まず、RedLock アルゴリズムでは、ロックの有効時間が Redis インスタンスへの接続時間から減算されることがわかります。ネットワークの問題によりこのプロセスに時間がかかりすぎると、ロックに残される有効時間が大幅に短縮されます。クライアントが共有リソースにアクセスできる時間は短く、プログラム処理中にロックが期限切れになる可能性が非常に高くなります。さらに、ロックの有効時間をサーバーのクロックドリフトから減算する必要がありますが、どのくらい減算すればよいのでしょうか?この値が正しく設定されていない場合、問題が発生する可能性が高くなります。

2 つ目のポイントは、このアルゴリズムでは Redis の単一点障害の問題を防ぐために複数のノードの使用を考慮していますが、ノードがクラッシュして再起動すると、複数のクライアントが同時にロックを取得する可能性があるということです。

5つのRedisノードA、B、C、D、Eがあり、クライアント1と2がそれぞれをロックしていると仮定します。

  1. クライアント 1 は A、B、C を正常にロックし、ロックを正常に取得しました (ただし、D と E はロックされませんでした)。
  2. ノード C のマスターがクラッシュしたため、ロックがスレーブに同期されませんでした。スレーブがマスターにアップグレードされた後、クライアント 1 によって追加されたロックは失われました。
  3. この時点でクライアント 2 がロックを取得し、C、D、E をロックします。ロックは正常に取得されます。

この方法では、クライアント 1 とクライアント 2 は同時にロックを取得しますが、プログラムの潜在的なセキュリティ リスクは依然として存在します。さらに、これらのノードの 1 つで時間のずれが発生すると、ロックのセキュリティ問題も発生する可能性があります。

したがって、複数のインスタンスの展開によって可用性と信頼性は向上しますが、RedLock は Redis の単一点障害の隠れた危険性を完全に解決するものではなく、クロック ドリフトや長期のクライアント ブロッキングによって発生するロック タイムアウトの問題も解決しません。ロックのセキュリティリスクは依然として存在します。

結論は

さらに、ロックの絶対的な安全性をどのように確保できるのかと疑問に思う人もいるかもしれません。

これに対して私が言えるのは、ケーキを食べて、ケーキも残しておくことはできないということだけです。分散ロック ツールとして Redis を使用する主な理由は、Redis 自体が非常に効率的で単一プロセスであり、高い同時実行性でも十分なパフォーマンスを保証できるためです。しかし、多くの場合、パフォーマンスとセキュリティを十分に考慮することはできません。ロックのセキュリティを確保する必要がある場合は、db や zookeeper などの他のミドルウェアを使用して制御できます。これらのツールはロックのセキュリティを非常によく保証できますが、パフォーマンスは不十分としか言えません。そうでなければ、ずっと前に誰もが使用していたはずです。

一般的に、Redis を使用して共有リソースを制御し、高いデータ セキュリティ要件が求められる場合、究極の保証ソリューションは、ビジネス データに対してべき等制御を実行することです。この方法では、複数のクライアントがロックを取得しても、データの一貫性には影響しません。もちろん、すべてのシナリオがこれに適しているわけではありません。具体的な選択は読者自身が行う必要があります。結局のところ、完璧な技術というものは存在せず、最適なものだけが最良なのです。

<<:  もう混乱しないでください。クラウドネイティブをこのように理解できるかもしれない

>>:  新世代のハイブリッド クラウド管理の発表: クラウド ジャーニーの成功または失敗はこれに左右されるでしょうか?

推薦する

役に立つ情報: Catalyst-Seattle/10G ポート/4T トラフィック開始

Catalyst、この製品は5年前から存在しており(ドメイン名の更新は2017年に終了しました)、パ...

空白ページを 404 ページにするにはどのようなデザインが必要ですか?

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますウェブサイ...

ウェブサイトの重みの効果的な集約の成功と偶然ではない最適化

体重は、ウェブマスターにとって愛するものでもあり、嫌うものでもあります。体重が急激に増えたときは喜ば...

ウェブマスターネットワークからの毎日のレポート:百度モバイルがランキングを上げるウェブサイトの価格を暴露

ニュース:百度の1,000元のクラウドフォンが公開:中国聯通と提携し今月中旬に発売予定5月10日早朝...

百度サイトのホームページが1位にならない理由

著者のSEOブログは6月18日にサイトのホームページの一番上にありませんでした。ダウングレードされた...

#BlackFriday# INXY: 専用サーバー 30% オフ、CDN 20% オフ ($4/T)、クラウド ストレージ 25% オフ、VPS 20% オフ

11月28日から12月2日まで、INXYはスーパープロモーションを提供しています。主な内容は次のとお...

安価なストレージスペース: interserver、月額 10 ドル、4 TB のストレージ/10 TB のトラフィック、ニューヨーク データ センター、米国

インターサーバー(1999年~)は、米国ニューヨークのデータセンターでストレージスペースサービスを提...

Baidu のハイパーリンク アルゴリズムのアップグレード後に Web サイトの外部リンク構築を改善する方法

Baidu Webmaster Platform に「ハイパーリンク不正アルゴリズムのアップグレード...

ユーザーを引き付けるデザイン: 明確なモバイルアプリフォーム

[編集者注] この記事は@elya妞の個人ブログから転載したものです。より多くの設計者がモバイル ア...

[更新版] AS4809、AS9929、AS58807、AS4837、3つのネットワーク直接接続を備えた、米国最速のVPSの推奨

最速の米国 VPS、高速の米国 VPS、高速の米国 VPS、最速の米国 VPS... 米国 VPS ...

WeChat ブラウザのアップグレードは H5 ゲームにとって大きなメリットです!

要約:今回、H5 の価値が真に実現されるかどうかは、多くの大手企業が H5 の先進的な機能を利用して...

3年間で30回の戦い:知板ブランド総支配人劉宗明と彼のマーケティングチーム

月収10万元の起業の夢を実現するミニプログラム起業支援プラン同じ日に2つの会議。景気低迷を背景に、知...

自分を分析する: 優秀で尊敬されるSEO担当者になる

正直に言うと、私はほぼ1年間失業しています。そして、まだ失業中の人たちにとって、私がまだ SEO に...

SEO最適化の手順をご存知ですか?

私は Vino SEO チームのリーダー、Bingze です。私の一般的なオンライン名は、zhch1...

「斉魯地」で成長企業のビジネスイノベーションが開花

GDPは8兆円を超えており、間違いなく経済大国です。新旧の成長原動力の転換の過程において、彼らは活力...