1. 程式人生 > >分布式系統的面試題12

分布式系統的面試題12

面試 普通 lose value eat inter create 能開 runt

1、面試題

一般實現分布式鎖都有哪些方式?使用redis如何設計分布式鎖?使用zk來設計分布式鎖可以嗎?這兩種分布式鎖的實現方式哪種效率比較高?

2、面試官心裏分析

其實一般問問題,都是這麽問的,先問問你zk,然後其實是要過度的zk關聯的一些問題裏去,比如分布式鎖。因為在分布式系統開發中,分布式鎖的使用場景還是很常見的。

3、面試題剖析

1redis分布式鎖

技術分享圖片

官方叫做RedLock算法,是redis官方支持的分布式鎖算法。

這個分布式鎖有3個重要的考量點,互斥(只能有一個客戶端獲取鎖),不能死鎖,容錯(大部分redis節點或者這個鎖就可以加可以釋放)

第一個最普通的實現方式,如果就是在redis裏創建一個key算加鎖

SET my:lock 隨機值 NX PX 30000,這個命令就ok,這個的NX的意思就是只有key不存在的時候才會設置成功,PX 30000的意思是30秒後鎖自動釋放。別人創建的時候如果發現已經有了就不能加鎖了。

釋放鎖就是刪除key,但是一般可以用lua腳本刪除,判斷value一樣才刪除:

關於redis如何執行lua腳本,自行百度

if redis.call("get",KEYS[1]) == ARGV[1] then

return redis.call("del",KEYS[1])

else

    return
0 end

為啥要用隨機值呢?因為如果某個客戶端獲取到了鎖,但是阻塞了很長時間才執行完,此時可能已經自動釋放鎖了,此時可能別的客戶端已經獲取到了這個鎖,要是你這個時候直接刪除key的話會有問題,所以得用隨機值加上面的lua腳本來釋放鎖。

但是這樣是肯定不行的。因為如果是普通的redis單實例,那就是單點故障。或者是redis普通主從,那redis主從異步復制,如果主節點掛了,key還沒同步到從節點,此時從節點切換為主節點,別人就會拿到鎖。

第二個問題,RedLock算法

技術分享圖片

這個場景是假設有一個redis cluster,有5redis master實例。然後執行如下步驟獲取一把鎖:

1)獲取當前時間戳,單位是毫秒

2)跟上面類似,輪流嘗試在每個master節點上創建鎖,過期時間較短,一般就幾十毫秒

3)嘗試在大多數節點上建立一個鎖,比如5個節點就要求是3個節點(n / 2 +1

4)客戶端計算建立好鎖的時間,如果建立鎖的時間小於超時時間,就算建立成功了

5)要是鎖建立失敗了,那麽就依次刪除這個鎖

6)只要別人建立了一把分布式鎖,你就得不斷輪詢去嘗試獲取鎖

2zk分布式鎖

技術分享圖片

zk分布式鎖,其實可以做的比較簡單,就是某個節點嘗試創建臨時znode,此時創建成功了就獲取了這個鎖;這個時候別的客戶端來創建鎖會失敗,只能註冊個監聽器監聽這個鎖。釋放鎖就是刪除這個znode,一旦釋放掉就會通知客戶端,然後有一個等待著的客戶端就可以再次重新枷鎖。

/**

 * ZooKeeperSession

 * @author Administrator

 *

 */

public class ZooKeeperSession {

 

private static CountDownLatch connectedSemaphore = new CountDownLatch(1);

 

private ZooKeeper zookeeper;

private CountDownLatch latch;

 

public ZooKeeperSession() {

try {

this.zookeeper = new ZooKeeper(

"192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181",

50000,

new ZooKeeperWatcher());

try {

connectedSemaphore.await();

} catch(InterruptedException e) {

e.printStackTrace();

}

 

System.out.println("ZooKeeper session established......");

} catch (Exception e) {

e.printStackTrace();

}

}

 

/**

 * 獲取分布式鎖

 * @param productId

 */

public Boolean acquireDistributedLock(Long productId) {

String path = "/product-lock-" + productId;

 

try {

zookeeper.create(path, "".getBytes(),

Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

return true;

} catch (Exception e) {

while(true) {

try {

Stat stat = zk.exists(path, true); // 相當於是給node註冊一個監聽器,去看看這個監聽器是否存在

if(stat != null) {

this.latch = new CountDownLatch(1);

this.latch.await(waitTime, TimeUnit.MILLISECONDS);

this.latch = null;

}

zookeeper.create(path, "".getBytes(),

Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

return true;

} catch(Exception e) {

continue;

}

}

 

// 很不優雅,我呢就是給大家來演示這麽一個思路

// 比較通用的,我們公司裏我們自己封裝的基於zookeeper的分布式鎖,我們基於zookeeper的臨時順序節點去實現的,比較優雅的

}

return true;

}

 

/**

 * 釋放掉一個分布式鎖

 * @param productId

 */

public void releaseDistributedLock(Long productId) {

String path = "/product-lock-" + productId;

try {

zookeeper.delete(path, -1);

System.out.println("release the lock for product[id=" + productId + "]......");  

} catch (Exception e) {

e.printStackTrace();

}

}

 

/**

 * 建立zk session的watcher

 * @author Administrator

 *

 */

private class ZooKeeperWatcher implements Watcher {

 

public void process(WatchedEvent event) {

System.out.println("Receive watched event: " + event.getState());

 

if(KeeperState.SyncConnected == event.getState()) {

connectedSemaphore.countDown();

}

 

if(this.latch != null) {  

this.latch.countDown();  

}

}

 

}

 

/**

 * 封裝單例的靜態內部類

 * @author Administrator

 *

 */

private static class Singleton {

 

private static ZooKeeperSession instance;

 

static {

instance = new ZooKeeperSession();

}

 

public static ZooKeeperSession getInstance() {

return instance;

}

 

}

 

/**

 * 獲取單例

 * @return

 */

public static ZooKeeperSession getInstance() {

return Singleton.getInstance();

}

 

/**

 * 初始化單例的便捷方法

 */

public static void init() {

getInstance();

}

 

}

 

3redis分布式鎖和zk分布式鎖的對比

redis分布式鎖,其實需要自己不斷去嘗試獲取鎖,比較消耗性能

zk分布式鎖,獲取不到鎖,註冊個監聽器即可,不需要不斷主動嘗試獲取鎖,性能開銷較小

另外一點就是,如果是redis獲取鎖的那個客戶端bug了或者掛了,那麽只能等待超時時間之後才能釋放鎖;而zk的話,因為創建的是臨時znode,只要客戶端掛了,znode就沒了,此時就自動釋放鎖

redis分布式鎖大家每發現好麻煩嗎?遍歷上鎖,計算時間等等。。。zk的分布式鎖語義清晰實現簡單

所以先不分析太多的東西,就說這兩點,我個人實踐認為zk的分布式鎖比redis的分布式鎖牢靠、而且模型簡單易用。

public class ZooKeeperDistributedLock implements Watcher{
    
    private ZooKeeper zk;
    private String locksRoot= "/locks";
    private String productId;
    private String waitNode;
    private String lockNode;
    private CountDownLatch latch;
    private CountDownLatch connectedLatch = new CountDownLatch(1);
private int sessionTimeout = 30000; 

    public ZooKeeperDistributedLock(String productId){
        this.productId = productId;
         try {
       String address = "192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181";
            zk = new ZooKeeper(address, sessionTimeout, this);
            connectedLatch.await();
        } catch (IOException e) {
            throw new LockException(e);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }

    public void process(WatchedEvent event) {
        if(event.getState()==KeeperState.SyncConnected){
            connectedLatch.countDown();
            return;
        }

        if(this.latch != null) {  
            this.latch.countDown(); 
        }
    }

    public void acquireDistributedLock() {   
        try {
            if(this.tryLock()){
                return;
            }
            else{
                waitForLock(waitNode, sessionTimeout);
            }
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        } 
}

    public boolean tryLock() {
        try {
         // 傳入進去的locksRoot + “/” + productId
        // 假設productId代表了一個商品id,比如說1
        // locksRoot = locks
        // /locks/10000000000,/locks/10000000001,/locks/10000000002
            lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
   
            // 看看剛創建的節點是不是最小的節點
         // locks:10000000000,10000000001,10000000002
            List<String> locks = zk.getChildren(locksRoot, false);
            Collections.sort(locks);
    
            if(lockNode.equals(locksRoot+"/"+ locks.get(0))){
                //如果是最小的節點,則表示取得鎖
                return true;
            }
    
            //如果不是最小的節點,找到比自己小1的節點
      int previousLockIndex = -1;
            for(int i = 0; i < locks.size(); i++) {
        if(lockNode.equals(locksRoot + “/” + locks.get(i))) {
                     previousLockIndex = i - 1;
            break;
        }
       }
       
       this.waitNode = locks.get(previousLockIndex);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
        return false;
    }
     
    private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException {
        Stat stat = zk.exists(locksRoot + "/" + waitNode, true);
        if(stat != null){
            this.latch = new CountDownLatch(1);
            this.latch.await(waitTime, TimeUnit.MILLISECONDS);                   this.latch = null;
        }
        return true;
}

    public void unlock() {
        try {
        // 刪除/locks/10000000000節點
        // 刪除/locks/10000000001節點
            System.out.println("unlock " + lockNode);
            zk.delete(lockNode,-1);
            lockNode = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
}

    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public LockException(String e){
            super(e);
        }
        public LockException(Exception e){
            super(e);
        }
}

// 如果有一把鎖,被多個人給競爭,此時多個人會排隊,第一個拿到鎖的人會執行,然後釋放鎖,後面的每個人都會去監聽排在自己前面的那個人創建的node上,一旦某個人釋放了鎖,排在自己後面的人就會被zookeeper給通知,一旦被通知了之後,就ok了,自己就獲取到了鎖,就可以執行代碼了

}  

分布式系統的面試題12