Redisは分散ロック(setnx、getset、incr)を実現し、タイムアウト状況をどのように処理するか(一)

9777 ワード

一、setnxによって1、setnx key valueが実現され、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() {
            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、get keyはkeyに対応するvalue値を取得し、そのkeyが存在しない場合は0を返します.
public String get(final String key) {
        this.checkIsInMulti();
        return (String)this.execute(new SmartJedis.Action() {
            public String doAction(Jedis jedis) {
                return jedis.get(key);
            }
        }, SmartJedis.RW.R, key);
    }

3、getset key value keyの古い値を取得し、新しいvalueを入れる
public static String getset(final String key, final String value) {
        return getShardedJedisClient().execute(new ShardedJedisAction() {
            @Override
            public String doAction(ShardedJedis shardedJedis) {
                return shardedJedis.getSet(key, value);
            }
        });
    }

ここで、まず携帯電話の3要素検証の列を挙げます:(Aチャネルシステム、業務Bシステム、外部メーカーCシステム)(1)B業務システムはAチャネルシステムを呼び出して、入ってきた携帯電話、身分証明書、番号の3要素が一致しているかどうかを検証します.(2)Aチャネルシステムは外部メーカーCシステムを再起動する.(3)Aチャネルシステムは結果をBトラフィックシステムに返す.この3つのプロセスのうち,(2)プロセスは,外部メーカーの呼び出し時に課金が必要である.B業務システムの同時生産量が高い場合、100件の同じ三要素検査があり、同じ三要素であるため、Aルートは一度メーカーを呼び出すだけで結果を知ることができる.では、Aチャネルシステムはどのようにして100件の要求をすべて外部メーカーCシステムにアクセスさせないように制御しますか?明ちゃんは方案1を提出した:Aシステムの中で、100個のスレッドが同時に要求すると、redisを行う.setnx("LOCK_KEY_phone&idNo&name","demo")では、最初のスレッドが先にロックを取得し、他のスレッドが待機するようになり、thread(0)処理が終了するとthread(0)はdelete("LOCK_KEY_phone&idNo&name")を行い、ロックを外し、thread(i)はget("LOCK_KEY_phone&idNo&name")を行い、前の処理が完了したことを示し、このとき、私たちは前の記録を調べることができます.
RedisUtils.setnx("LOCK_KEY_phone&idNo&name","demo");
JSONObject result = A.request(B);
AssetUtils.notNull(result,ResponseCodeEnum.Success,"    ");
ResultDmo resultDmo = (ResultDmo)BeanUtils.maptoBean(result);
resultDao.insert(resultDmo);
if(result!=0){
  //              ,    (           )
}else{
  //            ,      
  resultDao.select("  ");
}

マクロ氏は、「明ちゃんの考えは厳密ではない」と話した.100件のスレッドの一部がタイムアウトしたり、システムがダウンタイムしたりしたとき、ロックは常に一部のスレッドに所有され、デッドロック状態になることが分かった.キャッシュキーにタイムアウト時間を設定する必要があります.例えば:200 ms
RedisUtils.setnx("LOCK_KEY_phone&idNo&name","demo",200);

この場合、外部メーカーCシステムの業務処理時間が200 ms程度であると大まかに判断する、
========================================================================================================================
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){
//      
RedisUtils.delete("LOCK_KEY_phone&idNo&name");
}

(B)このような状況は厳密ではない:aがsetnxロックを取得すると、aスレッドがクラッシュまたはタイムアウトし、b、cスレッドがoldに同時にgetされ、タイムアウトを判断し、bスレッドdelete aスレッドのロックが発生し、setnx後;cスレッドはまた、bスレッドのロックdelete、setnxをロックする.この状況ではスレッドを完全にロックできません.
(B)シナリオのアップグレード-->(C)シナリオ:
aがsetnxロックを取得すると、aスレッドがクラッシュまたはタイムアウトし、bスレッドgetset、oldを取得してタイムアウトを判断し、cスレッドgetset、oldを取得し(この値はbがセットされたばかり)、タイムアウトしていないと判断し、cは待機を継続する.bスレッドdelete aスレッドのロック、setnx後.この状況は安全だ.
注意すべき点:①簡単にgetとgetsetを混用しないで、筆者はgetsetを単独で使用したほうがいいと思います.1つの場合、a、b、c、3つのスレッド、a、bは同時にgetし、aはすぐにoldを返し、突然cが来て、bの前にgetsetして、ロックを削除して、bのgetはnilを返すしかありません.このとき、タイムスタンプに基づいて比較します:a.get!=(a.set) b.get ! = (b.set)このようにa,bはロックを取得していないが,aは実際にロックを取得している.②複数サーバ時間の同期の問題.
まとめ:ロックタイムアウトはどのように処理するか、getset方式でタイムスタンプ差を判断する方式で、同時にgetsetよりもタイムアウトし、同時にsetnxに行くことが多い.いつももっと早くsetnxに行くことがあります.
===============================================================================================================================
二、incrプリエンプトリソースによって1を実現し、incrはkeyに格納されたデジタル値を1つ増加する.キーが存在しない場合、キーの値はまず0に初期化され、INCR操作が実行されます.値にエラーのタイプが含まれている場合、または文字列タイプの値が数値として表示されない場合は、エラーが返されます.
public static Long incr(final String key) {
        return shardedClient.execute(new ShardedJedisAction() {
            @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) {
            //     >1,         
            throw new AppException(ResponseCode.FAIL.getCode(), "    ");
        }
=========================================================================================================================================

 Long startTime = System.currentTimeMillis();
 JSONObject result = A.request(B);
 Long endTime = System.currentTimeMillis();
 Long time = endTime - startTime;
  //        incr key    ,          
  if (time > 200) {
      //  ID,      
      String key = "LOCK_KEY_phone&idNo&name" + source;
      RedisUtils.incr(key);
      int total = Integer.valueOf(RedisUtils.get(key));
      //     10 ,    (       )
      AssertUtils.isTrue(total < 10, ResponseCode.FAIL, "  " + source + "    ");
  }

ここにはカウンタのタイムアウト時間が200 ms設定されており、リクエストがタイムアウトすると大量のスレッドが同時にアクセスし、筆者のところには10件が同時に来て、アラームが起動します.人為的にルートを調べる.setnxとは異なり、あるスレッドがタイムアウトし、setnxの方式は手動で判断し、ロックをかけ、大量のスレッドが入ることを防止する必要がある(ここでは輪訓で実現できる).incrの方式はタイムアウトして、大量のスレッドが入ってきて、私は処理しませんが、ここのtime>200は誤差があります.