redisの分散ロック

3708 ワード

前言
くだらないことは言わないで、例えば1つの操作はユーザーの状態を修正して、状態を修正するにはまずユーザーの状態を読み出して、メモリの中で修正して、変更してから保存する必要があります.このような操作が同時に行われると,読み出しと保存状態の2つの操作は原子ではないため,同時問題が発生する.(Wiki解釈:原子操作とは、スレッドスケジューリングメカニズムによって中断されない操作を指す.この操作が開始されると、終了まで実行され、途中でcontext switchスレッドが切り替えられない.)
この場合、プログラムの同時実行を制限するために分散ロックを使用します.Redis分布式ロックは非常に広く使用されており、面接の重要なポイントの一つであり、多くの学生がこの知識を知っており、分布式ロックの原理も大体知っているが、具体的には細部の使用が完全に正確ではないことが多い.
redis分布式ロックの概念
分散ロックが本質的に実現される目標はRedisの中に「茅の穴」を占めることであり,他のプロセスも占拠しようとすると,そこにしゃがんでいる人がいることに気づき,あきらめるか後で試してみるしかなかった.
ピットは一般的にsetnx(set if not exists)命令を使用し、1つのクライアントのみがピットを占有することを許可する.先に占拠して、使い終わったら、delコマンドを呼び出して茅坑を解放します.
くりを一つあげる

> setnx lockCodehole true
OK
... do something critical ...
> del lockCodehole
(integer) 1...

しかし、論理実行中に異常が発生した場合、delコマンドが呼び出されない可能性があり、デッドロックに陥り、ロックが解放されないという問題があります.
そこで私たちはロックを手に入れた後、ロックに期限切れの時間を加えて、例えば5 sを加えて、中間に異常が発生しても5秒後にロックが自動的に解放されることを保証することができます.
> setnx lockCodehole true
OK
> expire lockCodehole 5
... do something critical ...
> del lockCodehole
(integer) 1

しかし、以上の論理には問題がある.setnxとexpireの間でサーバープロセスが突然停止した場合、機械が電源を落としたり、人為的に殺されたりしたため、expireが実行されず、デッドロックになる可能性があります.この問題の根源はsetnxとexpireが原子命令ではなく2つの命令であることにある.この2つの命令が一緒に実行できれば問題はありません.Redis事務で解決したいかもしれません.しかし、ここでは、expireはsetnxの実行結果に依存するため、setnxがロックを奪っていない場合、expireは実行すべきではない.トランザクションにはif-else分岐ロジックがなく、トランザクションの特徴は一気に実行するか、すべて実行するか、1つも実行しないかです.この問題を解決するために、Redisオープンソースコミュニティには、この問題を解決するために使用される分散ロックのlibraryがたくさん現れています.実現方法は極めて複雑で、シロユーザーは一般的に大きな精力を費やして理解することができる.分散ロックを使用する必要がある場合は、Jedisまたはredis-pyだけではいけないことを意味し、分散ロックのlibraryを導入する必要があります.
 
この乱れを治めるために,Redis 2.8バージョンでは著者らはset命令の拡張パラメータを加え,setnxとexpire命令を一緒に実行できるようにし,分散ロックの乱れを徹底的に解決した.これからはすべてのサードパーティ分散ロックlibraryが休めます.
> set lockCodehole true ex 5 nx
OK
... do something critical ...
> del lockCodehole

上の命令はsetnxとexpireを組み合わせた原子命令であり,分布ロックの奥義である.
リエントロック可能
くだらないことは言わないでデモに直接行こう
public class RedisWithReentrantLock {

  private ThreadLocal> lockers = new ThreadLocal<>();

  private Jedis jedis;

  public RedisWithReentrantLock(Jedis jedis) {
    this.jedis = jedis;
  }

  private boolean _lock(String key) {
    return jedis.set(key, "", "nx", "ex", 5L) != null;
  }

  private void _unlock(String key) {
    jedis.del(key);
  }

  private Map currentLockers() {
    Map refs = lockers.get();
    if (refs != null) {
      return refs;
    }
    lockers.set(new HashMap<>());
    return lockers.get();
  }

  public boolean lock(String key) {
    Map refs = currentLockers();
    Integer refCnt = refs.get(key);
    if (refCnt != null) {
      refs.put(key, refCnt + 1);
      return true;
    }
    boolean ok = this._lock(key);
    if (!ok) {
      return false;
    }
    refs.put(key, 1);
    return true;
  }

  public boolean unlock(String key) {
    Map refs = currentLockers();
    Integer refCnt = refs.get(key);
    if (refCnt == null) {
      return false;
    }
    refCnt -= 1;
    if (refCnt > 0) {
      refs.put(key, refCnt);
    } else {
      refs.remove(key);
      this._unlock(key);
    }
    return true;
  }

  public static void main(String[] args) {
    Jedis jedis = new Jedis();
    RedisWithReentrantLock redis = new RedisWithReentrantLock(jedis);
    System.out.println(redis.lock("codehole"));
    System.out.println(redis.lock("codehole"));
    System.out.println(redis.unlock("codehole"));
    System.out.println(redis.unlock("codehole"));
  }

}