1. 程式人生 > >分散式鎖(Zookeeper實現)

分散式鎖(Zookeeper實現)

分散式鎖

分散式鎖,這個主要得益於 ZooKeeper 為我們保證了資料的強一致性。鎖服務可以分為兩類,一個是 保持獨佔,另一個是 控制時序。

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

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

 

分散式鎖    單純的Lock鎖或者synchronize只能解決單個jvm執行緒安全問題

分散式 Session 一致性問題

分散式全域性id(也可以使用分散式鎖)

 

分散式鎖,產生的原因是 叢集

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

 

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

  首先預測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 不可以操作他!

 

分散式鎖  保證分散式領域中共享資料安全問題

 

1、資料庫實現(效率低,不推薦)

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

3、Zookeeper實現   (使用臨時節點,效率高,失效時間可以控制)

 4、Spring Cloud 實現全域性鎖(內建的)

 

業務場景

在分散式情況,生成全域性訂單號ID

產生問題

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

 

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

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

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

1.使用資料庫實現分散式鎖

缺點:效能差、執行緒出現異常時,容易出現死鎖

2.使用redis實現分散式鎖

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

3.使用zookeeper實現分散式鎖

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

什麼是分散式鎖

分散式鎖一般用在分散式系統或者多個應用中,用來控制同一任務是否執行或者任務的執行順序。在專案中,部署了多個tomcat應用,在執行定時任務時就會遇到同一任務可能執行多次的情況,我們可以藉助分散式鎖,保證在同一時間只有一個tomcat應用執行了定時任務

 

使用Zookeeper實現分散式鎖

Zookeeper實現分散式鎖原理

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

 

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

    臨時節點

    持久節點

 

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

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

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

                      

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

  如果程式一直不處理完,可能導致思索(其他的一直等待)。設定有效期~  直接close()掉 其實連線也是有有效期設定的 大家可以找下相關資料看下哦

 

    上程式碼!