分佈式鎖(Zookeeper實現)

分佈式鎖

分佈式鎖,這個主要得益於 ZooKeeper 爲咱們保證了數據的強一致性。鎖服務能夠分爲兩類,一個是 保持獨佔,另外一個是 控制時序。html

1. 所謂保持獨佔,就是全部試圖來獲取這個鎖的客戶端,最終只有一個能夠成功得到這把鎖。一般的作法是把 zk 上的一個 znode 看做是一把鎖,經過 create znode 的方式來實現。全部客戶端都去建立 /distribute_lock 節點,最終成功建立的那個客戶端也即擁有了這把鎖。java

2. 控制時序,就是全部視圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全局時序了。作法和上面基本相似,只是這裏 /distributelock 已經預先存在,客戶端在它下面建立臨時有序節點(這個能夠經過節點的屬性控制:CreateMode.EPHEMERALSEQUENTIAL 來指定)。Zk 的父節點(/distribute_lock)維持一份 sequence, 保證子節點建立的時序性,從而也造成了每一個客戶端的全局時序。node

 

分佈式鎖    單純的Lock鎖或者synchronize只能解決單個jvm線程安全問題redis

分佈式 Session 一致性問題數據庫

分佈式全局id(也可使用分佈式鎖)apache

 

分佈式鎖,產生的緣由是 集羣設計模式

在單臺服務器上 如何生成訂單號(保證惟一),方案 UUid+時間戳方式, redis方式tomcat

 

生成訂單號, 秒殺搶購時候,安全

  首先預測100w訂單號,生成放在redis。客戶端下單,直接redis去獲取便可。由於redis單線程的,多個線程去獲取時候,安全呀。服務器

  實際150w用戶。當redis剩下50w訂單號時候,繼續生成補充之。 

若是在集羣狀況,UUid+時間戳。不能保證惟一性!,緣由:  

 若是單臺:

uuid+時間戳,生成的代碼邏輯:

package com.toov5.Lock;

import java.text.SimpleDateFormat;
import java.util.Date;

//生成訂單號 時間戳
public class OrderNumGenerator {
  //區分不一樣的訂單號
    private static int count = 0;
//單臺服務器,多個線程 同事生成訂單號
    public String getNumber(){
        try {
            Thread.sleep(300);
        } catch (Exception e) {
            // TODO: handle exception
        }
        SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  
        return simpt.format(new Date()) + "-" + ++count;  //時間戳後面加了 count

    }
    
}

開啓100個線程調用之:

package com.toov5.Lock;

public class OrderService implements  Runnable {
      
     private OrderNumGenerator    orderNumGenerator  = new OrderNumGenerator(); //定義成全局的
    
     public void run() {
        getNumber();     
    }
    
    public void getNumber(){
    String number =    orderNumGenerator.getNumber();
    System.out.println(Thread.currentThread().getName()+"num"+number);
    }
    
    public static void main(String[] args) {
        OrderService orderService = new OrderService();
        for (int i = 0; i <100; i++) {  //開啓100個線程
            new Thread(orderService).start();
        }    
    }
    
}

結果:

 

 多個線程共享區同一個全局變量,線程安全問題!

 

 

 解決方案就是加鎖嘛!

或者使用 lock鎖也能夠

 

public class OrderService implements Runnable {
    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
    // 使用lock鎖
    private java.util.concurrent.locks.Lock lock = new ReentrantLock();

    public void run() {
        getNumber();
    }

    public void getNumber() {
        try {
            // synchronized (this) {
            lock.lock();
            String number = orderNumGenerator.getNumber();
            System.out.println(Thread.currentThread().getName() + ",生成訂單ID:" + number);
            // }

        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        System.out.println("####生成惟一訂單號###");
        OrderService orderService = new OrderService();
        for (int i = 0; i < 100; i++) {
            new Thread(orderService).start();
        }

    }
}

 

若是是集羣環境下:

  

     每臺jvm都有一個 count   都有 自增的代碼 操做這個 count  三個不一樣的jvm 獨立的  用戶請求 過來 映射到哪一個 就操做哪一個 

   這時候就產生分佈式鎖的問題

 

 

這時候須要分佈式鎖:共享一個count

 

 

jvm1 操做時候 其餘的jvm2 和 jvm3 不能夠操做他!

 

分佈式鎖  保證分佈式領域中共享數據安全問題

 

一、數據庫實現(效率低,不推薦)

二、redis實現(使用redission實現,可是須要考慮思索,釋放問題。繁瑣一些)

三、Zookeeper實現   (使用臨時節點,效率高,失效時間能夠控制)

 四、Spring Cloud 實現全局鎖(內置的)

 

業務場景

在分佈式狀況,生成全局訂單號ID

產生問題

在分佈式(集羣)環境下,每臺JVM不能實現同步,在分佈式場景下使用時間戳生成訂單號可能會重複

 

分佈式狀況下,怎麼解決訂單號生成不重複

  1. 使用分佈式鎖
  2. 提早生成好,訂單號,存放在redis取。獲取訂單號,直接從redis中取。

使用分佈式鎖生成訂單號技術

1.使用數據庫實現分佈式鎖

缺點:性能差、線程出現異常時,容易出現死鎖

2.使用redis實現分佈式鎖

缺點:鎖的失效時間難控制、容易產生死鎖、非阻塞式、不可重入

3.使用zookeeper實現分佈式鎖

實現相對簡單、可靠性強、使用臨時節點,失效時間容易控制

什麼是分佈式鎖

分佈式鎖通常用在分佈式系統或者多個應用中,用來控制同一任務是否執行或者任務的執行順序。在項目中,部署了多個tomcat應用,在執行定時任務時就會遇到同一任務可能執行屢次的狀況,咱們能夠藉助分佈式鎖,保證在同一時間只有一個tomcat應用執行了定時任務

 

使用Zookeeper實現分佈式鎖

Zookeeper實現分佈式鎖原理

使用zookeeper建立臨時序列節點來實現分佈式鎖,適用於順序執行的程序,大致思路就是建立臨時序列節點,找出最小的序列節點,獲取分佈式鎖,程序執行完成以後此序列節點消失,經過watch來監控節點的變化,從剩下的節點的找到最小的序列節點,獲取分佈式鎖,執行相應處理,依次類推……

 

 如何使用zk實現分佈式鎖?

    臨時節點

    持久節點

 

分佈式鎖使用 臨時節點,實現:

 

實現步驟:

多個Jvm同時在Zookeeper上建立同一個相同的節點( /Lock)

 

zk節點惟一的! 不能重複!節點類型爲臨時節點, jvm1建立成功時候,jvm2和jvm3建立節點時候會報錯,該節點已經存在。這時候 jvm2和jvm3進行等待。

                                                                                 jvm1的程序如今執行完畢,執行釋放鎖。關閉當前會話。臨時節點不復存在了而且事件通知Watcher,jvm2和jvm3繼續建立。

                      

 ps:zk強制關閉時候,通知會有延遲。可是close()方法關閉時候,延遲小

  若是程序一直不處理完,可能致使思索(其餘的一直等待)。設置有效期~  直接close()掉 其實鏈接也是有有效期設置的 你們能夠找下相關資料看下哦

 

    上代碼!

       引入Jar包:

           

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.toov5.FbsLock</groupId>
  <artifactId>FbsLock</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
		<dependency>
			<groupId>com.101tec</groupId>
			<artifactId>zkclient</artifactId>
			<version>0.10</version>
		</dependency>
	</dependencies>
  
</project>

 建立鎖的接口

package com.toov5.Lock;

public interface ExtLock {
   
    //ExtLock基於zk實現分佈式鎖
    public void  getLock();
    
    //釋放鎖
    public void unLock();
    
}

模板方法模式

package com.toov5.Lock;

import org.I0Itec.zkclient.ZkClient;

//將重複代碼抽象到子類中(模板方法設計模式)
public abstract class ZookeeperAbstractLock implements ExtLock {
    private static final String CONNECTION="192.168.91.5:2181";
    protected ZkClient zkClient = new ZkClient(CONNECTION);
    private String lockPath="/lockPath";
    
     //獲取鎖
      public void getLock() { 
          //一、鏈接zkClient 建立一個/lock的臨時節點
          // 二、 若是節點建立成果,直接執行業務邏輯,若是節點建立失敗,進行等待 
          if (tryLock()) {
            System.out.println("#####成功獲取鎖######");
        }else {
            //進行等待
            waitLock();
        }
     
        //三、使用事件通知監聽該節點是否被刪除    ,若是是,從新進入獲取鎖的資源  
        
    }
      
   //建立失敗 進行等待
    abstract void waitLock();


    abstract boolean tryLock();
     
     
    //釋放鎖
      public void unLock() {
        //執行完畢 直接鏈接
          if (zkClient != null) {
            zkClient.close();
            System.out.println("######釋放鎖完畢######");
        }
        
    }
      
}

建立子類實現上面的 抽象方法

package com.toov5.Lock;

import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.IZkDataListener;
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {

    @Override
    boolean tryLock() {
        try {
            zkClient.createEphemeral(lockPath);
//            System.out.println("#########獲取鎖######");
            return true;
        } catch (Exception e) {
            // 若是失敗 直接catch
            return false;
        }
    }

    @Override
    void waitLock() {

        IZkDataListener iZkDataListener = new IZkDataListener() {

            // 節點被刪除
            public void handleDataDeleted(String arg0) throws Exception {
                if (countDownLatch != null) {
                    countDownLatch.countDown(); // 計數器爲0的狀況,await 後面的繼續執行
                }

            }

            // 節點被修改
            public void handleDataChange(String arg0, Object arg1) throws Exception {

            }
        };

        // 監聽事件通知
        zkClient.subscribeDataChanges(lockPath, iZkDataListener);
        // 控制程序的等待
        if (zkClient.exists(lockPath)) {  //若是 檢查出 已經被建立了 就new 而後進行等待
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.wait(); //等待時候 就不往下走了   當爲0 時候 後面的繼續執行
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
        //後面代碼繼續執行
        //爲了避免影響程序的執行 建議刪除該事件監聽 監聽完了就刪除掉
        zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);

    }

}

生產訂單號:

package com.toov5.Lock;

import java.text.SimpleDateFormat;
import java.util.Date;

//生成訂單號 時間戳
public class OrderNumGenerator {
  //區分不一樣的訂單號
    private static int count = 0;
//單臺服務器,多個線程 同事生成訂單號
    public String getNumber(){
        try {
            Thread.sleep(500);
        } catch (Exception e) {
            
        }
        SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  
        return simpt.format(new Date()) + "-" + ++count;  //時間戳後面加了 count

    }
    
}

運行方法:

package com.toov5.Lock;

public class OrderService implements Runnable {

    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator(); // 定義成全局的
    private ExtLock lock = new ZookeeperDistrbuteLock();

    public void run() {
        getNumber();
    }

    public synchronized void getNumber() { // 加鎖 保證線程安全問題 讓一個線程操做
        try {
            lock.getLock();
            String number = orderNumGenerator.getNumber();
            System.out.println(Thread.currentThread().getName() + ",number" + number);

        } catch (Exception e) {

        } finally {
            lock.unLock();
        }
    }

    public static void main(String[] args) {
//        OrderService orderService = new OrderService();
        for (int i = 0; i < 100; i++) { // 開啓100個線程
            //模擬分佈式鎖的場景
            new Thread(new OrderService()).start();
        }
    }

}

運行結果:

代碼欣賞:

相關文章
相關標籤/搜索