Redis でのいくつかのロックの実現について語る

 

Redis のいくつかのロック実装

1. Redis ロックの分類

Redis で使用できるロック コマンド テーブルは INCR、SETNX、SET です。

2. 最初のロックコマンド INCR

この種のロックの考え方は、キーが存在しない場合、まずキーの値が 0 に初期化され、次に INCR 操作が実行されて 1 が追加されるというものです。 

次に、他のユーザーが INCR 操作を実行して 1 を追加したときに、返された数値が 1 より大きい場合、ロックが使用されていることを意味します。

  • 1. クライアント A は、ロックが取得されたことを示す値 1 のキーを取得するようにサーバーに要求します。
  • 2. クライアント B もサーバーに値 2 のキーを取得するよう要求します。これは、ロックの取得が失敗したことを示します。
  • 3. クライアント A がコードの実行を完了し、ロックを削除します。
  • 4. 一定時間待機した後、クライアント B は要求時にキーの値を取得し、ロックが正常に取得されたことを示します。
  • 5. クライアント B はコードを実行してロックを完了し、削除します。 

1

2

$redis->incr($key);

$redis->expire($key, $ttl); //设置生成时间为1秒

3. 2 番目のタイプのロック SETNX

この種のロックの考え方は、キーが存在しない場合、キーを値に設定するというものです。 

キーがすでに存在する場合、SETNX は何も行いません

  • 1. クライアント A がサーバーにキーの値の設定を要求し、設定が成功するとロックが成功したことになります。
  • 2. クライアント B もサーバーにキーの値を設定するよう要求します。応答が失敗した場合は、ロックが失敗したことを意味します。
  • 3. クライアント A がコードの実行を完了し、ロックを削除します。
  • 4. 一定時間待機した後、クライアント B がキー値の設定を要求し、設定が成功します。
  • 5. クライアント B はコードを実行してロックを完了し、削除します。 

1

2

$redis->setNX($key, $value);

$redis->expire($key, $ttl);

4. 3番目のロックSET

上記の 2 つの方法には問題があり、両方ともキーの有効期限を設定する必要があることがわかります。では、なぜキーに有効期限を設定するのでしょうか? リクエストの実行が何らかの理由で予期せず終了し、ロックは作成されたものの削除されなかった場合、ロックは常に存在するため、今後キャッシュが更新されることはありません。したがって、事故を防ぐためにロックに有効期限を追加する必要があります。 

ただし、Expire による設定はアトミックな操作ではありません。したがって、アトミック性を確保するためにトランザクションを使用することもできますが、まだいくつか問題があるため、公式は別のものを引用しています SET コマンド自体の使用には、バージョン 2.6.12 から有効期限を設定する機能が含まれています。

  • 1. クライアント A がサーバーにキーの値の設定を要求し、設定が成功するとロックが成功したことになります。
  • 2. クライアント B もサーバーにキーの値を設定するよう要求します。応答が失敗した場合は、ロックが失敗したことを意味します。
  • 3. クライアント A がコードの実行を完了し、ロックを削除します。
  • 4. 一定時間待機した後、クライアント B がキー値の設定を要求し、設定が成功します。
  • 5. クライアント B はコードを実行してロックを完了し、削除します。 

1

$redis->set($key, $value, array('nx', 'ex' => $ttl));  //ex表示秒

5. その他の問題

上記の手順でニーズは満たされましたが、まだ他の問題を考慮する必要がありますか? 

  • 1. Redis がロックの失敗を検出した場合はどうすればよいですか? 割り込み要求かループ要求か? 
  • 2. 循環リクエストの場合、そのうちの 1 つがロックを取得すると、他のリクエストがロックを取得したときにロックを取得するのは簡単ですか? 
  • 3. 事前にロックの有効期限が切れた後、クライアント A が実行を終了していないため、クライアント B がロックを取得します。このとき、クライアント A は実行を終了しますが、ロックを削除するときに、B のロックも削除されますか?

6. 解決策

  • 問題 1: 循環リクエストを使用し、ロックを取得するために循環リクエストを使用する 
  • 問題 2 の場合: 2 番目の問題の場合、ループがロックの取得を要求するとき、スリープ関数を追加し、ループの実行まで数ミリ秒待機します。 
  • 質問 3 の場合: ロックに保存されているキーはランダムです。この場合、キーを削除するたびに、保存されているキーの値が保存したものと同じかどうかを判断します。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

        do {  //针对问题1,使用循环

            $timeout = 10;

            $roomid = 10001;

            $key = 'room_lock';

            $value = 'room_'.$roomid;  //分配一个随机的值针对问题3

            $isLock = Redis::set($key, $value, 'ex', $timeout, 'nx');//ex 秒

            if ($isLock) {

                if (Redis::get($key) == $value) {  //防止提前过期,误删其它请求创建的锁

                    //执行内部代码

                    Redis::del($key);

                    continue;//执行成功删除key并跳出循环

                }

            } else {

                usleep(5000); //睡眠,降低抢锁频率,缓解redis压力,针对问题2

            }

        } while(!$isLock);

7. 別のロック

上記のロックは要件を完全に満たしていますが、公式では一連のロック アルゴリズムも提供しています。ここでは PHP を例に説明します。

1

2

3

4

5

6

7

8

9

10

11

12

13

    $servers = [

        ['127.0.0.1', 6379, 0.01],

        ['127.0.0.1', 6389, 0.01],

        ['127.0.0.1', 6399, 0.01],

    ];

  

    $redLock = new RedLock($servers);

  

    //加锁

    $lock = $redLock->lock('my_resource_name', 1000);

  

    //删除锁

    $redLock->unlock($lock)

上記は個人的な経験ですので、参考になれば幸いです。

転載元:マイクロリーディング   https://www.weidianyuedu.com

おすすめ

転載: blog.csdn.net/weixin_45707610/article/details/131868982