キャッシュまたはzookeeperベースの分散ロック実装

5539 ワード

バッファロック
 我们常常将缓存作为分布式锁的解决方案,但是却不能单纯的判断某个 key 是否存在 来作为锁的获得依据,因为无论是 exists 和 get 命名都不是线程安全的,都无法保证只有一个线程可以获得锁,存在线程争抢,複数のスレッドが同時にロックを取得する場合があります(古典的なRedis「読み書き」の問題).
incrキャッシュロック
@Component
public class LockClient {

    private StringRedisTemplate stringRedisTemplate;

    private ValueOperations valueOperations;

    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.valueOperations = stringRedisTemplate.opsForValue();
    }

    public void lockIncr() {
        Long lockIncr = valueOperations.increment("lockIncr", 1);
        //       
        if (lockIncr == 1) {
            //     
        }
    }
}    
  • incr:指定キーに対応する値をインクリメントし、keyに対応する値が存在しない場合は、まずkeyの値を0に設定し、incr操作を実行してインクリメントの値を返します.
  • というロックの実現原理は主にincrコマンドの原子性を利用し,同じ時間に1つのスレッドだけがこのコマンドを操作する.
  • というロックの実現方式は、結果データを気にしない.ビジネスコードに実行できるのは、一意のスレッドのみであることを保証します.

  • setnxキャッシュロック
     上のロック実装方式では、リソースを分離し、唯一のスレッドだけがリソースを取得して操作を実行できることを保証しました.しかし、リソースが唯一のスレッドで実行されていない場合は?複数のスレッドが競合している場合は?
        public void lockSetnx() {
            String lock = "lockSetnx";
            long millis = System.currentTimeMillis();
            long timeout = millis + 3000L + 1;
            try {
                while (true) {
                    boolean setnx = valueOperations.setIfAbsent(lock, timeout + "");
                    if (setnx == true) {
                        break;
                    }
                    String oldTimeout = valueOperations.get(lock);
                    //                ,           。
                    //   p1、p2        ,         。p1、p2      getSet   。
                    //    p1       ,   p1               (          ),       。
                    //    p2       ,   p2        p1 set     (          ),       。
                    String oldValue = valueOperations.getAndSet(lock, timeout + "");
                    if (millis > Long.valueOf(oldTimeout) && millis > Long.valueOf(oldValue)) {
                        break;
                    }
                    //    100   ,     
                    Thread.sleep(100);
                }
    
                //       
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (millis < timeout) {
                    stringRedisTemplate.delete(lock);
                }
            }
    
        }
  • setnx:最初のスレッドだけが成功し、trueを返し、残りのスレッドは失敗し、falseを返します.
  • getSet:keyの古い値を返し、新しい値をsetします.
  • 細かく見ると、setnxコマンドで分散ロックが実現できるようですが、なぜgetSetに名前を付けるのでしょうか.getSetコマンドは、クライアントの異常なダウンタイムを解決し、ロックが正常に解放されていない場合に、期限切れの時間と組み合わせてスレッドの安全を保証するためです.公式サイトの紹介を見て、この問題を詳しく説明することができます.

  • zookeeperロック
    zookeeperは、生まれつきの分散協調ツールであり、分散ロック、分散カウンタ、分散キューなど、さまざまな分散の難題を解決するために生まれてきた.
    zookeeper分散ロックは、自分で実現すれば、大体の実現方式は以下の通りです.
    フェアロック:
  • zookeeperの指定ノード(locks)の下で一時順序ノードnode_を作成するn ;
  • locksの下にあるすべてのサブノードchildrenを取得します.
  • サブノードは、ノードの自己増分シーケンス番号に従って小さい順から大きい順に並べ替えられる.
  • は、本ノードが第1のサブノードであるか否かを判断し、もしそうであればロックを取得する.そうでない場合は、そのノードよりも小さいノードの削除イベントをリスニングします.
  • リスニングイベントが有効になると、ロックが取得されるまで、第2のステップに戻って再判断する.

  • アンフェアロック
  • zookeeperのノード(lock)に一時ノードznodeを作成します.
  • の作成に成功したことは、このロックが取得されたことを示す.他のクライアントがロックを作成するのに失敗し、このロックのリスニングのみを登録できます.
  • 他のクライアントは、このロックが解放される(znodeノードが削除される)ことを傍受し、ロック(ノードの作成)を試み、第2のステップを継続する.

  • 幸いなことに、zookeeper recipesクライアントは、さまざまな分散ロックを実現しています.
  • InterProcessMutex
  • InterProcessSemaphoreMutex(再入排他ロック不可)
  • InterProcessReadWriteLock(分散型読み書きロック)
  • InterProcessSemaphore(共有信号量-最大並列数設定)
  • zookeeper recipesロックの簡単な使用:
            
                org.apache.zookeeper
                zookeeper
                3.4.14
            
            
                org.apache.curator
                curator-framework
                4.0.1
            
            
                org.apache.curator
                curator-recipes
                4.0.1
            
        public InterProcessMutex interProcessMutex(String lockPath) {
            CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeper, new ExponentialBackoffRetry(1000, 3));
            //       ,       
            client.usingNamespace(namespace);
            client.start();
            return new InterProcessMutex(client, lockPath);
        }
        public void lockUse() {
            InterProcessMutex interProcessMutex = interProcessMutex("/lockpath");
            try {
                //    
                if (interProcessMutex.acquire(100, TimeUnit.MILLISECONDS)) {
                    //       
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //    
                try {
                    interProcessMutex.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
  • zookeeperの紹介の全面的な文章をお勧めします.https://www.cnblogs.com/shamo89/p/9800925.html

  • 比較
  • キャッシュ分散ロックは、ポーリング方式でロックを試みなければならず、性能に浪費が大きい.zookeeper分散ロックは、リスニングによって通知またはタイムアウトを待つことができ、ロックが解放された場合、使用者に通知すればよい.
  • キャッシュがロックを取得したクライアントがダウンタイムした場合、ロックは解放されず、他の方法でしか解決できない(上のgetSet判断).zookeeperでは、一時的なznodeが作成されているため、クライアントが切れるとznodeがなくなり、自動的にロックが解除されます.