redis - 分佈式鎖

在java中,咱們能夠用synchronized進行加鎖,也能夠用lock的lock方法加鎖和unlock方法釋放鎖。可是在多個進程或者誇服務器的狀況下,這種加鎖的方式就沒辦法用了,由於他沒辦法讓其餘客戶端的人知道是否被加鎖了。因此咱們能夠用redis、zookeeper、etcd等來實現。redis也有相似於lock的樂觀鎖,在redis - 商品交易中也展現了WATCH的使用,可是當key裏的內容足夠多時,監控頻繁的變化反而致使性能的降低。
java的map,有putIfAbsent方法,意思是若是對應的key已經賦值了,則不能繼續賦值。redis中,也有相似的方法,setnx,SET if Not eXists,也是若是對應的key有值了,則不能繼續賦值,因此咱們能夠用他這個方法做爲分佈式鎖。java

// 鎖的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;
}

正常狀況下,上面的分佈式鎖能夠運行的,可是有如下幾個問題:redis

  1. 設置了失效時間,若是在失效時間內沒執行完,則其餘進程就會獲取到鎖,致使同一時間有多個進程獲取同一個鎖。
  2. 若是在失效時間內獲取到鎖的進程已經崩潰,鎖依然得不到釋放,致使其餘進程依然在等待失效過時。
  3. 若是隻有一個redis服務器,那服務器崩潰則不能正常運行。若是redis作主從, A進程獲取鎖,且在主服務器數據未同步到從服務器的時候崩潰,B進程讀取已經變成主服務器的原從服務器時,由於沒有鎖又獲取到了鎖,致使同一時間有多個進程獲取同一個鎖。
相關文章
相關標籤/搜索