5分鐘了解分布式鎖解決方案

要弄清楚分布式鎖,先來(lái)了解什么是分布式鎖冯丙?

同一個(gè)進(jìn)程內(nèi)的鎖: 讓線程按照指定的順序去執(zhí)行方法或者代碼塊肉瓦,保證線程原子的進(jìn)行

不同進(jìn)程: jvm層面的鎖就不管用了,那么可以利用第三方的一個(gè)組件胃惜,來(lái)獲取鎖泞莉,未獲取到鎖,則阻塞當(dāng)前想要運(yùn)行的線程船殉。

場(chǎng)景1:

A: 銀行存款200

讀取到200鲫趁,存入100,得到 200+100=300


B: 同一賬號(hào) 

讀取到200利虫,取出100挨厚,得到 200-100 = 100

最終賬號(hào)里面應(yīng)該是200不變,A得到300糠惫,B得到200疫剃,A-B 數(shù)據(jù)都出現(xiàn)不一致

所以我們可以使用分布式鎖,讓A硼讽,B兩個(gè)進(jìn)程順序進(jìn)行巢价,這樣B讀取賬號(hào)的時(shí)候就讀到300,然后順序進(jìn)行最終數(shù)據(jù)是一致的。

場(chǎng)景2:

現(xiàn)在任務(wù)池里面有一堆任務(wù)蹄溉,A,B都需要從任務(wù)池里面獲取任務(wù)您炉,當(dāng)獲取到任務(wù)的時(shí)候柒爵,把獲取的任務(wù)從任務(wù)池里面刪除,如果不作同步處理赚爵,那么A,B就可能獲取到同一個(gè)任務(wù)

我們可以使用分布式鎖棉胀,保證獲取任務(wù)順序進(jìn)行。

為什么zk能實(shí)現(xiàn)分布式鎖

Zookeeper是什么冀膝?

其實(shí)zookeeper可以理解為一個(gè)類(lèi)似liunx的文件結(jié)構(gòu)唁奢,里面的核心數(shù)據(jù)結(jié)構(gòu)是node節(jié)點(diǎn),然后可以存放少量數(shù)據(jù)窝剖。

如下圖:

zk文件結(jié)構(gòu)

zk的node節(jié)點(diǎn)有四種屬性

node創(chuàng)建模式 中文解釋 說(shuō)明
PERSISTENT 持久節(jié)點(diǎn) znode不會(huì)自動(dòng)刪除麻掸,即使客戶端斷開(kāi)連接
PERSISTENT_SEQUENTIAL 持久順序節(jié)點(diǎn) znode不會(huì)自動(dòng)刪除,并且名字會(huì)自動(dòng)遞增
EPHEMERAL 臨時(shí)節(jié)點(diǎn) znode自動(dòng)刪除赐纱,在客戶端斷開(kāi)連接
EPHEMERAL_SEQUENTIAL 臨時(shí)有序節(jié)點(diǎn) znode自動(dòng)刪除脊奋,并且名字會(huì)自動(dòng)遞增

可見(jiàn)zk的node特性,當(dāng)我們創(chuàng)建節(jié)點(diǎn)的時(shí)候疙描,可以指定創(chuàng)建模式诚隙,當(dāng)指定EPHEMERAL_SEQUENTIAL的節(jié)點(diǎn),會(huì)生成一個(gè)有序的節(jié)點(diǎn)起胰,當(dāng)我們獲取鎖的時(shí)候久又,可以判斷,當(dāng)前節(jié)點(diǎn)是否為最小節(jié)點(diǎn)效五,如果是地消,那么就認(rèn)為獲取鎖成功,如果不是畏妖,那么我們監(jiān)聽(tīng)比當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)犯建,當(dāng)前一個(gè)節(jié)點(diǎn)放棄鎖的時(shí)候,zk通過(guò)watcher機(jī)制瓜客,會(huì)通知客戶端适瓦,這樣當(dāng)前節(jié)點(diǎn)就可以順利獲取鎖了。當(dāng)解鎖的時(shí)候谱仪,delete掉當(dāng)前節(jié)點(diǎn)就好了玻熙。

如何利用zk實(shí)現(xiàn)分布式鎖

前面的理論知識(shí),那么我們就可以用代碼實(shí)現(xiàn)

package com.skrein.zk;

import lombok.extern.slf4j.Slf4j;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @Author: hujiansong
 * @Date: 2019/5/16 17:29
 * @since: 1.8
 */
@Slf4j
public class DistributedLock implements Lock {

    private static final String LOCK_PATH = "/LOCK";

    private static final String ZOOKEEPER_IP_PORT = "localhost:2181";

    private ZkClient client = new ZkClient(ZOOKEEPER_IP_PORT, 4000, 4000, new SerializableSerializer());

    private String currentPath;

    private String lastPath;

    private CountDownLatch countDownLatch;

    public DistributedLock() {
        // 新建鎖的時(shí)候疯攒,判斷父節(jié)點(diǎn)是否存在
        if (!client.exists(LOCK_PATH)) {
            client.createPersistent(LOCK_PATH);
        }
    }

    public void lock() {
        if (!tryLock()) {
            waitForLock();
            lock();
        } else {
            log.info("獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn){}", currentPath);
        }
    }

    private void waitForLock() {

        // 監(jiān)聽(tīng)lastPath
        IZkDataListener listener = new IZkDataListener() {
            public void handleDataChange(String s, Object o) throws Exception {

            }

            public void handleDataDeleted(String s) throws Exception {
                log.info("前置節(jié)點(diǎn)已經(jīng)刪除,path={}", s);
                if(countDownLatch!=null){
                    // 前置節(jié)點(diǎn)刪除嗦随,通知喚醒去獲取鎖
                    countDownLatch.countDown();
                }
            }
        };
        // 監(jiān)聽(tīng)前置節(jié)點(diǎn)
        client.subscribeDataChanges(lastPath, listener);
        // 判斷前置節(jié)點(diǎn)是否存在
        if (this.client.exists(lastPath)) {
            countDownLatch = new CountDownLatch(1);
            try {
                // 前置節(jié)點(diǎn)存在,那么就阻塞等待
                countDownLatch.await();
            } catch (InterruptedException e) {
                log.error("CountDownLatch await Exception", e);
            }
        }
        // 前一個(gè)節(jié)點(diǎn)的listener解綁,因?yàn)橐呀?jīng)刪除完了
        client.unsubscribeDataChanges(lastPath, listener);

    }

    public void lockInterruptibly() throws InterruptedException {

    }

    public boolean tryLock() {
        // 先創(chuàng)建一個(gè)臨時(shí)有序節(jié)點(diǎn)
        if (currentPath == null) {
            currentPath = client.createEphemeralSequential(LOCK_PATH + "/", "lock");
            log.info("當(dāng)前鎖路徑{}", currentPath);
        }
        // 獲取所有子節(jié)點(diǎn)
        List<String> children = client.getChildren(LOCK_PATH);
        Collections.sort(children);
        // 當(dāng)前最小就認(rèn)為獲取鎖成功枚尼,直接返回true
        if (currentPath.equals(LOCK_PATH + "/" + children.get(0))) {
            return true;
        }
        // 如果第一個(gè)不是,那么拿到currentPath 前一個(gè) path
        int i = Collections.binarySearch(children, currentPath.substring(6));
        try {
            lastPath = LOCK_PATH + "/" + children.get(i - 1);
        } catch (Exception e) {
            System.out.println(currentPath + "" + children);
        }
        return false;
    }

    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    public void unlock() {
        // 刪除當(dāng)前節(jié)點(diǎn)
        log.info("釋放鎖{}",currentPath);
        this.client.delete(currentPath);
    }

    public Condition newCondition() {
        return null;
    }
}

分布式鎖使用


package com.skrein.zk;

import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;

/**
 * @Author: hujiansong
 * @Date: 2019/5/16 17:53
 * @since: 1.8
 */
@Slf4j
public class OrderService implements Runnable {

    private static AtomicLong ATOMICLONG = new AtomicLong(1);

    private static CountDownLatch COUNTDOWNLATCH = new CountDownLatch(10);

    private Lock lock = new DistributedLock();

    public void run() {
        try {
            COUNTDOWNLATCH.await();
        } catch (InterruptedException e) {
            log.error("CountDownLatch error", e);
        }
        String orderCode = null;
        lock.lock();
        try {
            // do biz
            orderCode = getOrderCode();
        } finally {
            lock.unlock();
        }
       

    }

    public String getOrderCode() {
        Date now = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.format(now) + ATOMICLONG.getAndIncrement();
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            // 按照線程數(shù)迭代實(shí)例化線程
            new Thread(new OrderService()).start();
            // 創(chuàng)建一個(gè)線程贴浙,倒計(jì)數(shù)器減1
            COUNTDOWNLATCH.countDown();
        }
    }

}

日志打印:

2019-05-17 10:53:14,542  INFO Thread-1 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000150
2019-05-17 10:53:14,542  INFO Thread-3 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000153
2019-05-17 10:53:14,542  INFO Thread-5 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000151
2019-05-17 10:53:14,543  INFO Thread-9 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000159
2019-05-17 10:53:14,543  INFO Thread-15 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000154
2019-05-17 10:53:14,543  INFO Thread-13 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000156
2019-05-17 10:53:14,543  INFO Thread-7 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000155
2019-05-17 10:53:14,544  INFO Thread-17 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000157
2019-05-17 10:53:14,543  INFO Thread-11 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000152
2019-05-17 10:53:14,544  INFO Thread-19 (com.skrein.zk.DistributedLock.tryLock:87) - 當(dāng)前鎖路徑/LOCK/0000000158
2019-05-17 10:53:14,547  INFO Thread-1 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000150
2019-05-17 10:53:14,560  INFO Thread-1 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000150
2019-05-17 10:53:14,609  INFO Thread-5 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000151
2019-05-17 10:53:14,609  INFO Thread-5 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000151
2019-05-17 10:53:14,643  INFO Thread-11 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000152
2019-05-17 10:53:14,644  INFO Thread-11 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000152
2019-05-17 10:53:14,673  INFO Thread-3 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000153
2019-05-17 10:53:14,673  INFO Thread-3 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000153
2019-05-17 10:53:14,724  INFO Thread-15 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000154
2019-05-17 10:53:14,724  INFO Thread-15 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000154
2019-05-17 10:53:14,759  INFO Thread-7 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000155
2019-05-17 10:53:14,759  INFO Thread-7 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000155
2019-05-17 10:53:14,800  INFO Thread-13 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000156
2019-05-17 10:53:14,800  INFO Thread-13 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000156
2019-05-17 10:53:14,833  INFO Thread-17 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000157
2019-05-17 10:53:14,833  INFO Thread-17 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000157
2019-05-17 10:53:14,872  INFO Thread-19 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000158
2019-05-17 10:53:14,872  INFO Thread-19 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000158
2019-05-17 10:53:14,906  INFO Thread-9 (com.skrein.zk.DistributedLock.lock:46) - 獲取鎖成功,當(dāng)前鎖節(jié)點(diǎn)/LOCK/0000000159
2019-05-17 10:53:14,906  INFO Thread-9 (com.skrein.zk.DistributedLock.unlock:111) - 釋放鎖/LOCK/0000000159

可以看到10個(gè)線程,依次獲取鎖署恍,解綁鎖崎溃。

為什么Redis能實(shí)現(xiàn)分布式鎖

Redis的SetNx命令當(dāng)key存在的時(shí)候,返回null

為了保證鎖的可用性,也就是當(dāng)一個(gè)獲取鎖的線程掛掉了盯质,后續(xù)線程也能正確的獲取鎖袁串,那么需要利用key的expired屬性來(lái)保證

因?yàn)槭欠植际芥i,所以需要一個(gè)requestId來(lái)保障是哪個(gè)請(qǐng)求獲取的鎖呼巷,所以value可以是標(biāo)識(shí)客戶端的id

所以思路是:獲取鎖的時(shí)候通過(guò)sexNx來(lái)嘗試獲取鎖囱修,如果設(shè)置成功,那么獲取鎖成功,如果設(shè)置失敗王悍,那么需要輪詢?cè)搆ey是否存在或者ttl(time to live)該key的存活時(shí)間破镰,來(lái)重新去獲取鎖,直到獲取鎖成功压储。

解鎖過(guò)程:get(key) == requestId ? del(key)
先判斷key返回的結(jié)果是否是當(dāng)前客戶端啤咽,如果是,那么刪除該key

相信處理過(guò)高并發(fā)的同學(xué)渠脉,一定能發(fā)現(xiàn)問(wèn)題宇整,這個(gè)操作并不是原子性的。也就是說(shuō)有可能執(zhí)行完get(key) == requestId,由于當(dāng)前線程執(zhí)行業(yè)務(wù)時(shí)間過(guò)長(zhǎng)芋膘,超過(guò)了該key的過(guò)期時(shí)間鎖已經(jīng)廢棄掉了鳞青,其他線程就獲取了鎖,然后接下來(lái)執(zhí)行del(key)为朋,就會(huì)把其他線程的鎖給刪除掉臂拓。

好在Redis給我們提供了lua腳本,可以將多個(gè)操作合并為一個(gè)原子操作习寸。所以解鎖操作使用lua腳本的方式進(jìn)行胶惰。

Redis實(shí)現(xiàn)分布式鎖過(guò)程

@Slf4j
public class RedisDistributedLock {

    private Jedis client = new Jedis("localhost");

    private static final String REDIS_LOCK_KEY = "DISTRIBUTE::LOCK::";

    /**
     * 鎖的過(guò)期時(shí)間 ms
     */
    private static final Long LOCK_EXPIRED_TIME = 60 * 1000L;


    /**
     * 請(qǐng)求鎖的id,標(biāo)識(shí)是哪個(gè)客戶端獲取的鎖
     */
    private String reqLockId;


    public RedisDistributedLock() {
        // 使用 uuid + 當(dāng)前時(shí)間戳 來(lái)生成鎖的ID
        this.reqLockId = UUID.randomUUID().toString() + System.currentTimeMillis();
    }

    public void lock(String key) throws InterruptedException {
        if (!tryLock(key)) {
            waitForLock(key);
            lock(key);
        } else {
            log.info("獲取鎖成功!");
        }

    }

    private void waitForLock(final String key) throws InterruptedException {
        // 開(kāi)啟一個(gè)線程去輪詢r(jià)edis的key過(guò)期時(shí)間
        for (; ; ) {
            String lockKey = REDIS_LOCK_KEY + key;
            Long ttl = client.ttl(lockKey);
            // key不存在或者已經(jīng)過(guò)期霞溪,然后break掉重新去獲取鎖
            if (!client.exists(lockKey) || ttl.intValue() <= 0) {
                log.info("當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:{}", ttl);
                break;
            }
        }
    }

    // 獲取鎖操作很簡(jiǎn)單孵滞,直接setnx操作即可
    public boolean tryLock(String key) {
        String tryLock = client.set(REDIS_LOCK_KEY + key, reqLockId, SetParams.setParams().nx().px(LOCK_EXPIRED_TIME));
        return tryLock != null && tryLock.equals("OK");
    }


    // 解鎖采用eval lua腳本方式執(zhí)行
    public boolean unlock(String key) {
        // 判斷key的value是否為RequestId,如果是,那么刪除key
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object eval = client.eval(script, Collections.singletonList(REDIS_LOCK_KEY + key), Collections.singletonList(reqLockId));
        return "OK".equals(eval);
    }


    public static void main(String[] args) throws InterruptedException {
        String orderId = "123";
        for (int i = 0; i < 10; i++) {
            // 開(kāi)啟10個(gè)線程模擬并發(fā)
            new Thread(new OrderService(orderId)).start();
        }
    }

    static class OrderService implements Runnable {

        private String orderId;

        private RedisDistributedLock lock = new RedisDistributedLock();

        public OrderService(String orderId) {
            this.orderId = orderId;
        }

        public void run() {
            try {
                lock.lock(orderId);
                log.info("do biz....");
                Thread.sleep(5000);
                log.info("do biz finished!");
            } catch (InterruptedException e) {
                log.error("lock InterruptedException", e);
            } finally {
                lock.unlock(orderId);
            }
        }
    }


}
"C:\Program Files\Java\jdk1.8.0_161\bin\java.exe" -javaagent:C:\Users\hujiansong\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\191.7141.44\lib\idea_rt.jar=63968:C:\Users\hujiansong\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\191.7141.44\bin -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;C:\Users\hujiansong\Desktop\tmp\distributed-lock\target\classes;F:\DevTool\apache-maven-3.5.3\repo\org\projectlombok\lombok\1.18.6\lombok-1.18.6.jar;F:\DevTool\apache-maven-3.5.3\repo\org\slf4j\slf4j-api\1.7.26\slf4j-api-1.7.26.jar;F:\DevTool\apache-maven-3.5.3\repo\log4j\log4j\1.2.17\log4j-1.2.17.jar;F:\DevTool\apache-maven-3.5.3\repo\com\101tec\zkclient\0.10\zkclient-0.10.jar;F:\DevTool\apache-maven-3.5.3\repo\org\apache\zookeeper\zookeeper\3.4.8\zookeeper-3.4.8.jar;F:\DevTool\apache-maven-3.5.3\repo\org\slf4j\slf4j-log4j12\1.6.1\slf4j-log4j12-1.6.1.jar;F:\DevTool\apache-maven-3.5.3\repo\jline\jline\0.9.94\jline-0.9.94.jar;F:\DevTool\apache-maven-3.5.3\repo\io\netty\netty\3.7.0.Final\netty-3.7.0.Final.jar;F:\DevTool\apache-maven-3.5.3\repo\redis\clients\jedis\3.0.1\jedis-3.0.1.jar;F:\DevTool\apache-maven-3.5.3\repo\org\apache\commons\commons-pool2\2.4.3\commons-pool2-2.4.3.jar" com.skrein.redis.RedisDistributedLock
2019-05-17 17:11:14,007  INFO Thread-7 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:14,008  INFO Thread-7 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:19,009  INFO Thread-7 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:19,012  INFO Thread-8 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:19,012  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:19,013  INFO Thread-8 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:19,014  INFO Thread-8 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:19,012  INFO Thread-4 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:19,012  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:19,012  INFO Thread-9 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:19,012  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:19,012  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:19,012  INFO Thread-1 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:19,012  INFO Thread-6 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:24,014  INFO Thread-8 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:24,015  INFO Thread-6 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:24,016  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:24,016  INFO Thread-4 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:24,017  INFO Thread-6 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:24,017  INFO Thread-6 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:24,016  INFO Thread-1 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:24,016  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:24,016  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:24,015  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:24,017  INFO Thread-9 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:29,017  INFO Thread-6 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:29,017  INFO Thread-1 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:29,018  INFO Thread-4 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:29,017  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:29,018  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:29,017  INFO Thread-9 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:29,018  INFO Thread-1 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:29,018  INFO Thread-1 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:29,018  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:29,018  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:34,018  INFO Thread-1 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:34,019  INFO Thread-4 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:34,019  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:34,019  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:34,020  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:34,020  INFO Thread-9 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:34,020  INFO Thread-4 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:34,020  INFO Thread-4 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:34,020  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:39,020  INFO Thread-4 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:39,020  INFO Thread-9 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:39,020  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:39,021  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:39,021  INFO Thread-9 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:39,021  INFO Thread-9 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:39,020  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:39,021  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:44,021  INFO Thread-9 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:44,021  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:44,021  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:44,022  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:44,022  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:44,022  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:44,022  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:49,022  INFO Thread-3 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:49,023  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:49,023  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:49,023  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:49,023  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:49,023  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:54,023  INFO Thread-0 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:54,024  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:54,024  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:55
2019-05-17 17:11:54,024  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:54,024  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:11:59,024  INFO Thread-5 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!
2019-05-17 17:11:59,025  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.waitForLock:56) - 當(dāng)前鎖已經(jīng)過(guò)期過(guò)期時(shí)間為:-2
2019-05-17 17:11:59,026  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.lock:45) - 獲取鎖成功!
2019-05-17 17:11:59,026  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.run:96) - do biz....
2019-05-17 17:12:04,026  INFO Thread-2 (com.skrein.redis.RedisDistributedLock.run:98) - do biz finished!

Process finished with exit code 0

可以看到鎖依次進(jìn)行獲取鸯匹,然后釋放坊饶。

總結(jié)

ZK 實(shí)現(xiàn)分布式鎖,主要利用zk的node創(chuàng)建模式以及通知機(jī)制殴蓬。

Redis實(shí)現(xiàn)分布式匿级,依賴SexNX命令,和解鎖的lua腳本保證原子性

鎖的可靠性主要包含如下4點(diǎn):

可靠性特征 解釋
鎖是互斥 相同的時(shí)間,只有一個(gè)客戶端可以獲取到鎖
NO死鎖 當(dāng)客戶端獲取到鎖痘绎,當(dāng)客戶端突然掛掉津函,接下來(lái)的客戶端也能正確獲取到鎖
容錯(cuò)性 Redis要保證可靠,即當(dāng)Redis集群大多數(shù)節(jié)點(diǎn)可用的時(shí)候孤页,鎖也是可用的
正確釋放鎖 A客戶端不會(huì)釋放掉B客戶端的鎖尔苦,鎖要被加鎖的人釋放

下面就來(lái)聊聊zk和Redis對(duì)可靠性的滿足:

--- ZK Redis
鎖是互斥 滿足,node唯一 滿足散庶,setNx
NO死鎖 滿足 session timeout機(jī)制 滿足,expired
容錯(cuò)性 依靠zk集群 依靠Redis集群
正確釋放鎖 依賴wather機(jī)制 輪詢凌净,靠代碼實(shí)現(xiàn)

聲明:以上是我學(xué)習(xí)分布式鎖的demo例子悲龟,沒(méi)有上過(guò)生產(chǎn)。如果上生產(chǎn)冰寻,任何問(wèn)題概不負(fù)責(zé)须教。

代碼倉(cāng)庫(kù)

完整代碼放入了我的github倉(cāng)庫(kù),歡迎star

代碼地址如下:

DistributedLock

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斩芭,一起剝皮案震驚了整個(gè)濱河市轻腺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌划乖,老刑警劉巖贬养,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異琴庵,居然都是意外死亡误算,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)迷殿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)儿礼,“玉大人,你說(shuō)我怎么就攤上這事庆寺∥梅颍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵懦尝,是天一觀的道長(zhǎng)知纷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)陵霉,這世上最難降的妖魔是什么屈扎? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮撩匕,結(jié)果婚禮上鹰晨,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好模蜡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布漠趁。 她就那樣靜靜地躺著,像睡著了一般忍疾。 火紅的嫁衣襯著肌膚如雪闯传。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天卤妒,我揣著相機(jī)與錄音甥绿,去河邊找鬼。 笑死则披,一個(gè)胖子當(dāng)著我的面吹牛共缕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播士复,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼图谷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了阱洪?” 一聲冷哼從身側(cè)響起便贵,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冗荸,沒(méi)想到半個(gè)月后承璃,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蚌本,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年绸硕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片魂毁。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡玻佩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出席楚,到底是詐尸還是另有隱情咬崔,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布烦秩,位于F島的核電站垮斯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏只祠。R本人自食惡果不足惜兜蠕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抛寝。 院中可真熱鬧熊杨,春花似錦曙旭、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至川陆,卻和暖如春剂习,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背较沪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工鳞绕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尸曼。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓们何,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親骡苞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子垂蜗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容