zookeeper2

數據發佈訂閱/ 配置中心

實現配置信息的集中式管理和數據的動態更新java

實現配置中心有兩種模式:push 、pull。mysql

長輪訓git

zookeeper採用的是推拉相結合的方式。 客戶端向服務器端註冊本身須要關注的節點。一旦節點數據發生變化,那麼服務器端就會向客戶端github

發送watcher事件通知。客戶端收到通知後,主動到服務器端獲取更新後的數據redis

  1. 數據量比較小
  2. 數據內容在運行時會發生動態變動
  3. 集羣中的各個機器共享配置

image-20190304092212934

負載均衡

請求/數據分攤多個計算機單元上算法

image-20190304092448656

分佈式鎖

一般實現分佈式鎖有幾種方式sql

  1. redis。 setNX 存在則會返回0, 不存在
  2. 數據方式去實現apache

    建立一個表, 經過索引惟一的方式
    
    create table (id , methodname …)   methodname增長惟一索引
    
    insert 一條數據XXX   delete 語句刪除這條記錄
    
    mysql  for update 行鎖,杜佔鎖
  3. zookeeper實現

    排他鎖api

    image-20190307230710919

    利用路徑惟一安全

共享鎖(讀鎖)

​ locks當中是有序節點,控制使用權限,每個客戶端寫一個節點以後,獲取到最小節點,獲取數據,有寫的操做,優先處理寫的節點,利用節點特性

實現共享鎖,使用java api的方式

package zk.lock;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;
import java.util.List;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;


public class DistributeLock {
    //根節點
    private static final String ROOT_LOCKS = "/LOCKS";

    private ZooKeeper zooKeeper;

    //節點的數據
    private static final byte[] data = {1, 2};

    //會話超時時間
    private int sessionTimeOut;

    //記錄鎖節點id
    private String lockID;

    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public DistributeLock() throws IOException, InterruptedException {
        this.zooKeeper = ZookeeperClient.getInstance();
        this.sessionTimeOut = ZookeeperClient.getSESSIONTIMEOUT();
    }

    /**
     * 獲取鎖的方法
     *
     * @return
     */
    public synchronized boolean lock() {
        try {
            //LOCKS/000001
            lockID = zooKeeper.create(ROOT_LOCKS + "/", data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(Thread.currentThread().getName() + "->" + "成功建立了lock節點[" + lockID + ",開始競爭鎖]");

            //獲取根節點下的全部子節點
            List<String> childrenNodes = zooKeeper.getChildren(ROOT_LOCKS, true);

            //    排序,從小到大
            TreeSet<String> sortedSet = new TreeSet<>();
            for (String children : childrenNodes) {
                sortedSet.add(ROOT_LOCKS + "/" + children);
            }

            //獲取到最小的節點
            String first = sortedSet.first();
            if (lockID.equals(first)) {
                //表示當前就是最小的節點
                System.out.println(Thread.currentThread().getName() + "-->成功得到鎖,locak節點爲:【" + lockID + "]");
                return true;
            }

            SortedSet<String> lessThanLockId = sortedSet.headSet(lockID);
            if (!lessThanLockId.isEmpty()) {
                //獲取到比當前LockId這個節點更小的上一個節點
                String prevLockId = lessThanLockId.last();
                //監控上一個節點
                zooKeeper.exists(prevLockId, new LockWatcher(countDownLatch));
                //若是會話超時或者節點被刪除(釋放)了
                countDownLatch.await(sessionTimeOut, TimeUnit.MILLISECONDS);
                System.out.println(Thread.currentThread().getName() + "成功獲取鎖:【" + lockID + "】");
                return true;
            }

        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    public synchronized boolean unLock() {
        System.out.println(Thread.currentThread().getName() + "-->開始釋放鎖");
        try {
            zooKeeper.delete(lockID, -1);
            System.out.println("節點" + lockID + "被釋放了");
            return true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
        return false;
    }


    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                DistributeLock lock = null;
                try {
                    lock = new DistributeLock();
                    countDownLatch.countDown();
                    countDownLatch.await();
                    lock.lock();
                    Thread.sleep(random.nextInt(500));
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (lock != null) {
                        lock.unLock();
                    }
                }
            }).start();
        }

    }
}
package zk.lock;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;

import java.util.concurrent.CountDownLatch;


public class LockWatcher implements Watcher {
    private CountDownLatch countDownLatch;

    public LockWatcher(CountDownLatch latch) {
        this.countDownLatch = latch;
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeDataChanged) {
            countDownLatch.countDown();
        }
    }
}

命名服務

master選舉

​ 7*24小時可用, 99.999%可用

master-slave模式

​ slave監聽master節點,若是master節點掛掉,slave自動接替master,心跳機制維持,出現網絡異常,slave認爲master掛掉了,可能出現雙主節點的狀況,重複處理數據

使用zookeeper解決上述問題,往某一個節點註冊master節點,只有一個可以註冊成功,註冊成功的爲master,若是失去聯繫,節點會被刪除,不會出現腦裂問題

image-20190304114318988

per實現原理講解

分佈式隊列

  1. master選舉改爲多線程(多進程)模型(master-slave) 建立三個工程,while去搶
  2. 分佈式隊列

    activeMQ、kafka、….

先進先出隊列

  1. 經過getChildren獲取指定根節點下的全部子節點,子節點就是任務
  2. 肯定本身節點在子節點中的順序
  3. 若是本身不是最小的子節點,那麼監控比本身小的上一個子節點,不然處於等待
  4. 接收watcher通知,重複流程

Barrier模式

​ 在一個節點下只能容許多少數據,只有子節點達到必定數量,才執行

curator 提供應用場景的封裝

​ curator-reciples

master/leader選舉

分佈式鎖(讀鎖、寫鎖)

分佈式隊列

LeaderLatch

寫一個master

LeaderSelector

每個應用都寫一個臨時有序節點,根據最小的節點來得到優先權

curator 提供應用場景的封裝

​ curator-reciples

master/leader選舉

分佈式鎖(讀鎖、寫鎖)

分佈式隊列

LeaderLatch

​ 寫一個master

LeaderSelector

​ 每個應用都寫一個臨時有序節點,根據最小的節點來得到優先權

zookeeper集羣角色

leader

​ leader是zookeeper集羣的核心。

  1. 事務請求的惟一調度者和處理者,保證集羣事務處理的順序性
  2. 集羣內部各個服務器的調度者

follower

  1. 處理客戶端非事務請求,以及轉發事務請求給leader服務器
  2. 參與事務請求提議(proposal)的投票(客戶端的一個事務請求,須要半數服務器投票經過之後才能通知leader commit; leader會發起一個提案,要求follower投票)
  3. 參與leader選舉的投票

observer

​ 觀察zookeeper集羣中最新狀態的變化並將這些狀態同步到observer服務器上。

​ 增長observer不影響集羣中事務處理能力,同時還能提高集羣的非事務處理能力

zookeeper的集羣組成

​ zookeeper通常是由 2n+1臺服務器組成

leader選舉

選舉算法:

​ leaderElection

​ AuthFastLeaderElection

​ FastLeaderElection

QuorumPeer startLeaderElection

源碼地址:https://github.com/apache/zoo...

須要的條件: jdk 1.7以上 、ant 、idea

FastLeaderElection

serverid : 在配置server集羣的時候,給定服務器的標識id(myid)

zxid : 服務器在運行時產生的數據ID, zxid的值越大,表示數據越新

Epoch: 選舉的輪數

server的狀態:Looking、 Following、Observering、Leading

第一次初始化啓動的時候: LOOKING

  1. 全部在集羣中的server都會推薦本身爲leader,而後把(myid、zxid、epoch)做爲廣播信息,廣播給集羣中的其餘server, 而後等待其餘服務器返回
  2. 每一個服務器都會接收來自集羣中的其餘服務器的投票。集羣中的每一個服務器在接受到投票後,開始判斷投票的有效性

    a) 判斷邏輯時鐘(Epoch) ,若是Epoch大於本身當前的Epoch,說明本身保存的Epoch是過時。更新Epoch,同時clear其餘服務器發送過來的選舉數據。判斷是否須要更新當前本身的選舉狀況

    b) 若是Epoch小於目前的Epoch,說明對方的epoch過時了,也就意味着對方服務器的選舉輪數是過時的。這個時候,只須要講本身的信息發送給對方

    c) 若是sid等於當前sid,根據規則來判斷是否有資格得到leader

    ​ 接受到來自其餘服務器的投票後,針對每個投票,都須要將別人的投票和本身的投票進行對比,zxid,zxid最大的服務器優先

  3. 統計投票

img

ZAB協議

拜占庭問題

一組拜占庭將軍分別各率領一支軍隊共同圍困一座城市。爲了簡化問題,將各支軍隊的行動策略限定爲進攻或撤離兩種。由於部分軍隊進攻部分軍隊撤離可能會形成災難性後果,所以各位將軍必須經過投票來達成一致策略,即全部軍隊一塊兒進攻或全部軍隊一塊兒撤離。由於各位將軍分處城市不一樣方向,他們只能經過信使互相聯繫。在投票過程當中每位將軍都將本身投票給進攻仍是撤退的信息經過信使分別通知其餘全部將軍,這樣一來每位將軍根據本身的投票和其餘全部將軍送來的信息就能夠知道共同的投票結果而決定行動策略。

系統的問題在於,將軍中可能出現叛徒,他們不只可能向較爲糟糕的策略投票,還可能選擇性地發送投票信息。假設有9位將軍投票,其中1名叛徒。8名忠誠的將軍中出現了4人投進攻,4人投撤離的狀況。這時候叛徒可能故意給4名投進攻的將領送信表示投票進攻,而給4名投撤離的將領送信表示投撤離。這樣一來在4名投進攻的將領看來,投票結果是5人投進攻,從而發起進攻;而在4名投撤離的將軍看來則是5人投撤離。這樣各支軍隊的一致協同就遭到了破壞。

因爲將軍之間須要經過信使通信,叛變將軍可能經過僞造信件來以其餘將軍的身份發送假投票。而即便在保證全部將軍忠誠的狀況下,也不能排除信使被敵人截殺,甚至被敵人間諜替換等狀況。所以很難經過保證人員可靠性及通信可靠性來解決問題。

假始那些忠誠(或是沒有出錯)的將軍仍然能經過多數決定來決定他們的戰略,便稱達到了拜占庭容錯。在此,票都會有一個默認值,若消息(票)沒有被收到,則使用此默認值來投票。

上述的故事映射到計算機系統裏,將軍便成了計算機,而信差就是通訊系統。雖然上述的問題涉及了電子化的決策支持與信息安全,卻沒辦法單純的用密碼學數字簽名來解決。由於電路錯誤仍可能影響整個加密過程,這不是密碼學與數字簽名算法在解決的問題。所以計算機就有可能將錯誤的結果提交去,亦可能致使錯誤的決策。

paxos協議主要就是如何保證在分佈式環網絡環境下,各個服務器如何達成一致最終保證數據的一致性問題

ZAB協議,基於paxos協議的一個改進。

zab協議爲分佈式協調服務zookeeper專門設計的一種支持崩潰恢復的原子廣播協議

zookeeper並無徹底採用paxos算法, 而是採用zab Zookeeper atomic broadcast

zab協議的原理

  1. 在zookeeper 的主備模式下,經過zab協議來保證集羣中各個副本數據的一致性
  2. zookeeper使用的是單一的主進程來接收並處理全部的事務請求,並採用zab協議,把數據的狀態變動以事務請求的形式廣播到其餘的節點
  3. zab協議在主備模型架構中,保證了同一時刻只能有一個主進程來廣播服務器的狀態變動
  4. 全部的事務請求必須由全局惟一的服務器來協調處理,這個的服務器叫leader,其餘的叫follower

    leader節點主要負責把客戶端的事務請求轉化成一個事務提議(proposal),並分發給集羣中的全部follower節點

    再等待全部follower節點的反饋。一旦超過半數服務器進行了正確的反饋,那麼leader就會commit這條消息

崩潰恢復

原子廣播

zab協議的工做原理

  1. 什麼狀況下zab協議會進入崩潰恢復模式(沒有接受到的數據進行同步)

    1. 當服務器啓動時
    2. 當leader服務器出現網絡中斷、崩潰或者重啓的狀況
    3. 集羣中已經不存在過半的服務器與該leader保持正常通訊
  2. zab協議進入崩潰恢復模式會作什麼

    1. 當leader出現問題,zab協議進入崩潰恢復模式,而且選舉出新的leader。當新的leader選舉出來之後,若是集羣中已經有過半機器完成了leader服務器的狀態同(數據同步),退出崩潰恢復,進入消息廣播模式
    2. 當新的機器加入到集羣中的時候,若是已經存在leader服務器,那麼新加入的服務器就會自覺進入數據恢復模式,找到leader進行數據同步

img

問題

​ 假設一個事務在leader服務器被提交了,而且已經有過半的follower返回了ack。 在leader節點把commit消息發送給follower機器以前leader服務器掛了怎麼辦

zab協議,必定須要保證已經被leader提交的事務也可以被全部follower提交

zab協議須要保證,在崩潰恢復過程當中跳過哪些已經被丟棄的事務

回顧

zookeeper數據模型

​ 臨時節點(有序)、 持久化節點(有序)

​ 臨時節點其餘節點也能看到

zookeeper是一個開源的分佈式協調框架; 數據發佈訂閱、負載均衡、集羣、master選舉。。。

原子性: 要麼同時成功、要麼同時失敗 (分佈式事務)

單一視圖: 不管客戶端鏈接到哪一個服務器,所看到的模型都是同樣

可靠性:一旦服務器端提交了一個事務而且得到了服務器端返回成功的標識,那麼這個事務所引發的服務器端的變動會一直保留

實時性: 近實時

zookeeper並非用來存儲數據的,經過監控數據狀態的變化,達到基於數據的集羣管理。分佈式

集羣配置

  1. 修改zoo.cfg

    server.id=ip:port:port 第一個Port 數據同步通訊、 第二個port :leader選舉(3181)

    id=myid (myid 參與leader選舉、 在整個集羣中表示惟一服務器的標識)

  2. dataDir目錄下 建立一個myid的文件 , 內容: server.id對應當前服務器的id號
  3. 若是增長observer 須要在第一步中, server.id=ip:port:port:observer ; peerType=observer

會話

NOT_CONNECTED - > CONNECTING ->CONNECTED ->ClOSE

數據模型

數據模型是一個樹形結構,最小的數據單元是ZNODE

臨時節點和持久化節點

臨時有序節點

持久化有序節點

狀態信息

Stat

cZxid = 0xb0000000f

ctime = Sun Aug 13 20:24:03 CST 2017

mZxid = 0xb0000000f

mtime = Sun Aug 13 20:24:03 CST 2017

pZxid = 0xb0000000f

cversion = 0

dataVersion = 0

aclVersion = 0

ephemeralOwner = 0x15dda30f72f0000

dataLength = 2

numChildren = 0

zab協議 : 若是客戶端發了一個事務請求給到leader, 而leader發送給各個follower之後,而且收到了ack,leader已經commit。 在準備ack給各個follower節點comit的時候,leader掛了,怎麼處理的。

  1. 選舉新的leader(zxid的最大值)
  2. 同步給其餘的folower

watcher

EventyType

None 客戶端與服務器端成功創建會話

NodeCreated 節點建立

NodeDeleted 節點刪除

NodeDataChanged 數據變動:數據內容

NodeChildrenChanged 子節點發生變動: 子節點刪除、新增的時候,纔會觸發

watcher的特性
一次性觸發: 事件被處理一次後,會被移除,若是須要永久監聽,則須要反覆註冊

zkClient ( 永久監聽的封裝)

curator

java api的話, zk.exists , zk.getData 建立一個watcher監聽

zookeeper序列化使用的是Jute

Acl權限的操做

保證存儲在zookeeper上的數據安全性問題

schema(ip/Digest/world/super)
受權對象(192.168.1.1/11 , root:root / world:anyone/ super)

數據存儲

內存數據和磁盤數據

zookeeper會定時把數據存儲在磁盤上。

DataDir = 存儲的是數據的快照

快照: 存儲某一個時刻全量的內存數據內容

DataLogDir 存儲事務日誌

image-20190307230812881

zxid :服務器運行時產生的日誌id

log.zxid

查看事務日誌的命令

java -cp :/mic/data/program/zookeeper-3.4.10/lib/slf4j-api-1.6.1.jar:/mic/data/program/zookeeper-3.4.10/zookeeper-3.4.10.jar org.apache.zookeeper.server.LogFormatter log.200000001

zookeeper 有三種日誌

zookeeper.out //運行日誌

快照 存儲某一時刻的全量數據

事務日誌 事務操做的日誌記錄

相關文章
相關標籤/搜索