1. ホーム
  2. データベース
  3. レディス

Redisによる分散ロック(setnx, getset, incr)の実装とタイムアウトの扱い方

2022-01-15 20:50:49

分散ロックをWebで検索すると、redisを使ったものが主流です。redisベースの分散ロックは、redisのシングルスレッド実行機構により、実行時のシーケンシャルな命令が保証されるため、開発者の設計コストを大きく削減できるメリットがあります。

I. setnxによる実装

1. setnx キー値

keyが存在しない場合のみ、keyの値をvalueに設定し、1を返す。与えられたkeyがすでに存在する場合、setnxは何もせず0を返す。

public static Boolean setnx(final String key, final String value, final long seconds) {
        return getShardedJedisClient().execute(new ShardedJedisAction<Boolean>() {
            public Boolean doAction(ShardedJedis shardedJedis) {
                Jedis jedis = (Jedis) shardedJedis.getShard(key);
                String result = jedis.set(key, value, "NX", "EX", seconds);
                return "OK".equalsIgnoreCase(result);
            }
        });
    }

2.キーを取得する

キーの値を取得します。キーが存在しない場合は、0を返します。

public String get(final String key) {
        this.checkIsInMulti();
        return (String)this.execute(new SmartJedis.Action<String>() {
            public String doAction(Jedis jedis) {
                return jedis.get(key);
            }
        }, SmartJedis.RW.R, key);
    }

3. ゲットセットキーバリュー

keyの古い値を取得し、新しい値を

public static String getset(final String key, final String value) {
        return getShardedJedisClient().execute(new ShardedJedisAction<String>() {
            @Override
            public String doAction(ShardedJedis shardedJedis) {
                return shardedJedis.getSet(key, value);
            }
        });
    }

ここまでで、まずは携帯電話認証の3つの要素のコラムをご覧ください。(Aチャネルシステム、ビジネスBシステム、外部ベンダーCシステム)。
(1) 業務Bシステムは、Aチャンネルシステムを呼び出し、着信した携帯電話、IDカード、番号の3要素が1つであることを確認する。
(2) 次にAチャンネルシステムは、外部ベンダーのCシステムを呼び出す。
(3) チャネルシステムAは、その結果を業務システムBに返す。
この3つのプロセスのうち、(2)のプロセスでは、外部ベンダーが呼び出されたときに課金されます。
B業務システムの並行性が高い場合、同じ3要素チェックが100個あり、同じ3要素なので、チャネルAは1回ベンダーに電話するだけで結果を知ることができます。では、Aチャンネルシステムは、100件の要求をすべて外部ベンダーのCシステムに行かせないように制御するにはどうしたらよいでしょうか。

シャオミンは選択肢1を提案する。

システムAでは
100スレッドが同時にリクエストしてきた場合、redis. setnx("LOCK_KEY_phone&idNo&name", "demo") とし、最初のスレッドが最初にロックを取得し、他のスレッドは待ち、スレッド(0)が処理を終えると、スレッド(0)は delete("LOCK_KEY_phone&idNo&name") とする。ロックが解除され、スレッド(i)がget("LOCK_KEY _phone&idNo&name") して0を取得すると、最後の1件が処理されたことになり、この時点で、最後のレコードに問い合わせに行くことができる。

RedisUtils.setnx("LOCK_KEY_phone&idNo&name","demo");
JSONObject result = A.request(B);
AssetUtils.notNull(result,ResponseCodeEnum.Success,"getResult");
ResultDmo resultDmo = (ResultDmo)BeanUtils.maptoBean(result);
resultDao.insert(resultDmo);
if(result!=0){
  // the last same request has not yet been processed, waiting for the rotation (how the specific rotation is not expanded here)
}else{
  //the last request is completed, check the library operation
  resultDao.select("parameters");
}

シャオホンは言った。シャオミンの考え方は厳密ではありません。

課題】100スレッドのうち、一部のスレッドがタイムアウトしたり、システムダウンなどの予期せぬ事態が発生した場合、常に一部のスレッドがロックを保持し、デッドロック状態が発生すること。
キャッシュ・キーにタイムアウトを設定する必要があります。例:200ms

RedisUtils.setnx("LOCK_KEY_phone&idNo&name","demo",200);

このケースは、外部ベンダーのCシステムの業務処理時間がおおよそ200msであることを大まかに判断し

ネット上では別の見方もある(B)。

RedisUtils.setnx("LOCK_KEY_phone&idNo&name",currentTime,200);
Long old = RedisUtils.get("LOCK_KEY_phone&idNo&name");
Long new = System.currentTimeMillis();
Long time = new - old;
if(time>0){
//processing has timed out
RedisUtils.delete("LOCK_KEY_phone&idNo&name");
}

(B)このケースは厳密ではありません:取得setnxロック、スレッドクラッシュまたはタイムアウト、B、Cスレッド同時に古い、および判断タイムアウトに取得すると、Bスレッドは、スレッドのロックを削除し、setnx後に表示されることがあります、CスレッドはBスレッドのロック削除、およびsetnxます。

(シナリオ(B)→(C)のバージョンアップ版。

aがsetnxロックを取得すると、スレッドaはクラッシュまたはタイムアウト、スレッドbはgetset、old取得、タイムアウト判定、スレッドcはgetset、old取得(この時点ではbが先ほど設定した値)、タイムアウト判定せず、cは待機継続、bはスレッドのロックを削除してsetnx後。この状況は安全である。

注意点
/{br ①安易にgetとgetsetを混ぜない、getsetは単独で使うのが良いと思う。
そこに状況は、A、B、C、3つのスレッドは、A、B同時に取得し、すぐに古い返され、突然の前にgetset、およびロックを削除するには、Bにはまり来たCは、唯一のnilを返すことができます取得します。この時点では、その後、タイムスタンプの比較によると。
a.get ! = (a.set)
b.get ! = (b.set)
この方法では、aもbもロックを取得せず、実際にロックが取得されたことになります。
複数サーバータイムの同期問題。

ロックがタイムアウトした対処方法は、getsetの方法でタイムスタンプの違いを判断し、同時にgetsetがタイムアウトしている以上、setnxに行く間、常により速くなります。

第二に、incrは、達成するためにリソースをつかむ。

1.インクルード

key に格納されている数値の値を1つ増やす。key が存在しない場合、INCR 操作を実行する前に key の値は 0 に初期化される。値が誤った型を含んでいる場合、または文字列型の値が数値として表現できない場合は、エラーが返される。

public static Long incr(final String key) {
        return shardedClient.execute(new ShardedJedisAction<Long>() {
            @Override
            public Long doAction(ShardedJedis shardedJedis) {
                shardedJedis.expire(key, 200);
                return shardedJedis.incr(key);
            }
        });
    }

あるいは、上記の3つの要素の例

Long result = RedisUtils.incr("LOCK_KEY_phone&idNo&name");
        if (result > 1) {
            //If the counter > 1, the request has come in
            throw new AppException(ResponseCode.FAIL.getCode(), "operation frequent");
        }


 JSONObject result = A.request(B);
 Long endTime = System.currentTimeMillis();
 Long time = endTime - startTime;
  //If the processing time is greater than the key survival time of incr, that the request has timed out
  if (time > 200) {
      //global ID, count the number of timeouts
      String key = "LOCK_KEY_phone&idNo&name" + source;
      RedisUtils.incr(key);
      int total = Integer.valueOf(RedisUtils.get(key));
      //assert that if the timeout is 10 times, an alarm will be raised (the alarm will not be expanded next)
      AssertUtils.isTrue(total < 10, ResponseCode.FAIL, "call" + source + "channel timeout");
  }

ここではカウンターのタイムアウトを200msに設定していますが、もしリクエストがタイムアウトした場合、同時に多数のスレッドがアクセスすることになり、この作者は同時に10ペンのアクセスがあると、アラームを開始します。人工的な排除チャネル。setnxとの違いは、スレッドのタイムアウト、setnxの方法は、手動で決定する必要があり、その後(ここでは回転訓練によって達成することができる)入力するスレッドの多数を防ぐためにロックを追加することであり、incr方法タイムアウト、スレッドの多くは、私は処理を行うことはありません入って来て、ここでは時間> 200エラーです。

Redisが分散ロック(setnx, getset, incr)を実装し、タイムアウトに対処する方法について書かれた記事です。Redisのsetnx, getset, incrについては、BinaryDevelopの過去記事を検索するか、引き続き以下の関連記事を閲覧してください。