redis-分散ロック

2978 ワード

Javaでは、synchronizedでロックを追加することも、lockのlockメソッドでロックを追加することも、unlockメソッドでロックを解除することもできます.しかし、複数のプロセスやサーバーをほめている場合、このようなロックの仕方は使えません.他のクライアントの人にロックされているかどうかを知らせることができません.だからredis、zookeeper、etcdなどで実現することができます.redisにもlockのような楽観的なロックがあり、redis-商品取引にもWATCHの使用が示されているが、keyの内容が十分に多い場合、監視の頻繁な変化はかえって性能の低下を招く.JAvaのmapには、putIfAbsentメソッドがあり、対応するkeyがすでに付与されている場合、付与を継続できないことを意味します.redisでは、setnx,SET if Not eXistsと同様の方法もありますが、対応するkeyに値がある場合は、値を割り当て続けることができないので、分散ロックとして使用することができます.
//   key
static String lockName = "lock:";
static int cnt = 1000;
static CountDownLatch countDownLatch = new CountDownLatch(cnt);

@Test
public void testLock() throws InterruptedException {
    JedisUtils.del(lockName);
    //     ,         
    int lockTime = 1;
    //       ,           
    long timeOut = 2500;
    for (int i = 0; i < cnt; i++) {
        new Thread(new LockThread(i, lockTime, timeOut)).start();
        countDownLatch.countDown();
    }
    TimeUnit.SECONDS.sleep(3);
}

static class LockThread implements Runnable {
    int lockTime;
    long timeOut;
    int idx;

    public LockThread(int idx, int lockTime, long timeOut) {
        this.idx = idx;
        this.lockTime = lockTime;
        this.timeOut = timeOut;
    }

    @Override
    public void run() {
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String lock = lock(lockName, lockTime, timeOut);
        if (StringUtils.isNotEmpty(lock)) {
            System.out.println(idx + ":     ");
            //JedisUtils.del(lockName);
        }
    }
}

private static String lock(String lockName, int lockTime, long timeOut) {
    long end = System.currentTimeMillis() + timeOut;
    String value = UUID.randomUUID().toString();
    while (System.currentTimeMillis() < end) {
        long setnx = JedisUtils.setnx(lockName, value);
        // 1     ,  
        if (setnx == 1) {
            JedisUtils.expire(lockName, lockTime);
            return value;
        }
        try {
            //       100      
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    return null;
}

上のsetnxとexpireは原子的ではありません.変更できます.
  • lua言語を通じて.
  • の新しいバージョンでは、SET key value [EX seconds] [PX milliseconds] [NX|XX]というコマンドを使用できます.

  • 通常、上記の分散ロックは実行できますが、次のいくつかの問題があります.
  • は、失効時間を設定し、失効時間内に実行が完了しないと、他のプロセスがロックを取得し、同じ時間に複数のプロセスが同じロックを取得する.
  • 失効時間内にロックを取得したプロセスがクラッシュした場合、ロックは解放されず、他のプロセスは失効の期限切れを待っています.
  • redisサーバが1つしかない場合、サーバがクラッシュして正常に動作しません.Redisがマスタスレーブである場合、Aプロセスはロックを取得し、マスタサーバデータがスレーブサーバに同期していないときにクラッシュし、Bプロセスは既にマスタサーバとなっている元スレーブサーバを読み出す場合、ロックがないためまたロックを取得し、同じ時間に複数のプロセスが同じロックを取得する.